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
This commit is contained in:
Yakun Xu 2024-06-18 13:44:09 +08:00
commit e2ab96f3c7
4 changed files with 87 additions and 4 deletions

View file

@ -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}.
*
* <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);
}
/**

View file

@ -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)

View file

@ -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 =

View file

@ -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 {