From e2ab96f3c7233f8259fa3e7ea663f9106474bec6 Mon Sep 17 00:00:00 2001 From: Yakun Xu Date: Tue, 18 Jun 2024 13:44:09 +0800 Subject: [PATCH] Thread: fill in ticks in active timestamp This commit fills in the ticks of active timestamp of randomly generated dataset as defined in Thread spec. Bug: 347838683 Change-Id: I5d8ba3ca7728e1890996fdd64198f7bc90bfb1fa --- .../thread/OperationalDatasetTimestamp.java | 23 ++++++++- .../ThreadNetworkControllerService.java | 4 +- .../OperationalDatasetTimestampTest.java | 13 +++++ .../ThreadNetworkControllerServiceTest.java | 51 +++++++++++++++++++ 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java index 520acbd443..cecb4e9965 100644 --- a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java +++ b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java @@ -65,11 +65,32 @@ public final class OperationalDatasetTimestamp { */ @NonNull public static OperationalDatasetTimestamp fromInstant(@NonNull Instant instant) { + return OperationalDatasetTimestamp.fromInstant(instant, true /* isAuthoritativeSource */); + } + + /** + * Creates a new {@link OperationalDatasetTimestamp} object from an {@link Instant}. + * + *

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}. + * + *

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 0c77deef08..653f8378aa 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java @@ -884,9 +884,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub 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 2244a899c3..11c78e34be 100644 --- a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java +++ b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java @@ -40,6 +40,19 @@ public final class OperationalDatasetTimestampTest { () -> OperationalDatasetTimestamp.fromTlvValue(new byte[7])); } + @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 = 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 2f58943907..b060a5adf2 100644 --- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java @@ -98,6 +98,8 @@ import org.mockito.MockitoSession; 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 @@ public final class ThreadNetworkControllerServiceTest { assertThat(activeDataset.getActiveTimestamp().isAuthoritativeSource()).isFalse(); } + @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 {