From af4f1f576c22d982136a2894072cc8b0455a2a93 Mon Sep 17 00:00:00 2001 From: Kangping Dong Date: Fri, 27 Oct 2023 19:32:10 +0800 Subject: [PATCH] [Thread] move createRandomDataset to ThreadNetworkController This commit renames `createRandomDataset` to `createRandomizedDataset` and moves this method to ThreadNetworkController class. The reason for moving this method is that we rely on the service side to know which channels are currently supported by this device and there is no safe way for ActiveOperationalDataset to created a randomized full dataset by its own. Moving this method to ThreadNetworkController also allows us to support future platform-dependend configurations. Bug: 308117328 Bug: 307939803 Change-Id: I0b0b7b727c3b326ab59bb1299ace15df1ff1ad5e --- framework-t/api/system-current.txt | 2 +- .../net/thread/ActiveOperationalDataset.java | 147 +++++------------- .../net/thread/IThreadNetworkController.aidl | 1 + .../net/thread/ThreadNetworkController.java | 50 ++++++ .../ThreadNetworkControllerService.java | 104 +++++++++++++ .../cts/ActiveOperationalDatasetTest.java | 135 ++++++++-------- .../cts/PendingOperationalDatasetTest.java | 71 ++++++--- .../cts/ThreadNetworkControllerTest.java | 60 +++++-- .../thread/ActiveOperationalDatasetTest.java | 74 +-------- 9 files changed, 361 insertions(+), 283 deletions(-) diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt index 08c2e660e3..b285d85256 100644 --- a/framework-t/api/system-current.txt +++ b/framework-t/api/system-current.txt @@ -418,7 +418,6 @@ package android.net.nsd { package android.net.thread { @FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ActiveOperationalDataset implements android.os.Parcelable { - method @NonNull public static android.net.thread.ActiveOperationalDataset createRandomDataset(); method public int describeContents(); method @NonNull public static android.net.thread.ActiveOperationalDataset fromThreadTlvs(@NonNull byte[]); method @NonNull public android.net.thread.OperationalDatasetTimestamp getActiveTimestamp(); @@ -493,6 +492,7 @@ package android.net.thread { } @FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ThreadNetworkController { + method public void createRandomizedDataset(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public int getThreadVersion(); method public static boolean isAttached(int); method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void join(@NonNull android.net.thread.ActiveOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); diff --git a/thread/framework/java/android/net/thread/ActiveOperationalDataset.java b/thread/framework/java/android/net/thread/ActiveOperationalDataset.java index c9b047ae77..b74a15a8ce 100644 --- a/thread/framework/java/android/net/thread/ActiveOperationalDataset.java +++ b/thread/framework/java/android/net/thread/ActiveOperationalDataset.java @@ -16,8 +16,6 @@ package android.net.thread; -import static android.net.thread.ActiveOperationalDataset.SecurityPolicy.DEFAULT_ROTATION_TIME_HOURS; - import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkState; import static com.android.net.module.util.HexDump.dumpHexString; @@ -41,26 +39,25 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.ByteArrayOutputStream; import java.net.Inet6Address; import java.net.UnknownHostException; -import java.security.SecureRandom; -import java.time.Instant; import java.util.Arrays; -import java.util.Random; /** * Data interface for managing a Thread Active Operational Dataset. * - *

An example usage of creating an Active Operational Dataset with random parameters: + *

An example usage of creating an Active Operational Dataset with randomized parameters: * *

{@code
- * ActiveOperationalDataset activeDataset = ActiveOperationalDataset.createRandomDataset();
+ * ActiveOperationalDataset activeDataset = controller.createRandomizedDataset("MyNet");
  * }
* - *

or random Dataset with customized Network Name: + *

or randomized Dataset with customized channel: * *

{@code
  * ActiveOperationalDataset activeDataset =
- *         new ActiveOperationalDataset.Builder(ActiveOperationalDataset.createRandomDataset())
- *                 .setNetworkName("MyThreadNet").build();
+ *         new ActiveOperationalDataset.Builder(controller.createRandomizedDataset("MyNet"))
+ *                 .setChannel(CHANNEL_PAGE_24_GHZ, 17)
+ *                 .setActiveTimestamp(OperationalDatasetTimestamp.fromInstant(Instant.now()))
+ *                 .build();
  * }
* *

If the Active Operational Dataset is already known as The randomized (or default) value for each parameter: - * - *

- * - *

This method is the recommended way to create a randomized operational dataset for a new - * Thread network. It may be desired to change one or more of the generated value(s). For - * example, to use a more meaningful Network Name. To do that, create a new {@link Builder} - * object from this dataset with {@link Builder#Builder(ActiveOperationalDataset)} and override - * the value with the setters of {@link Builder}. - * - *

Note that it's highly discouraged to change the randomly generated Extended PAN ID, - * Network Key or PSKc, as it will compromise the security of a Thread network. - */ - @NonNull - public static ActiveOperationalDataset createRandomDataset() { - return createRandomDataset(new Random(Instant.now().toEpochMilli()), new SecureRandom()); - } - - /** @hide */ - @VisibleForTesting - public static ActiveOperationalDataset createRandomDataset( - Random random, SecureRandom secureRandom) { - int panId = random.nextInt(/* bound= */ 0xffff); - byte[] meshLocalPrefix = newRandomBytes(random, LENGTH_MESH_LOCAL_PREFIX_BITS / 8); - meshLocalPrefix[0] = MESH_LOCAL_PREFIX_FIRST_BYTE; - - SparseArray channelMask = new SparseArray<>(1); - channelMask.put(CHANNEL_PAGE_24_GHZ, new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0}); - - return new Builder() - .setActiveTimestamp( - new OperationalDatasetTimestamp( - /* seconds= */ 1, - /* ticks= */ 0, - /* isAuthoritativeSource= */ false)) - .setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID)) - .setPanId(panId) - .setNetworkName("THREAD-PAN-" + panId) - .setChannel( - CHANNEL_PAGE_24_GHZ, - random.nextInt(CHANNEL_MAX_24_GHZ - CHANNEL_MIN_24_GHZ + 1) - + CHANNEL_MIN_24_GHZ) - .setChannelMask(channelMask) - .setPskc(newRandomBytes(secureRandom, LENGTH_PSKC)) - .setNetworkKey(newRandomBytes(secureRandom, LENGTH_NETWORK_KEY)) - .setMeshLocalPrefix(meshLocalPrefix) - .setSecurityPolicy( - new SecurityPolicy( - DEFAULT_ROTATION_TIME_HOURS, new byte[] {(byte) 0xff, (byte) 0xf8})) - .build(); - } - - private static byte[] newRandomBytes(Random random, int length) { - byte[] result = new byte[length]; - random.nextBytes(result); - return result; - } - private static boolean areByteSparseArraysEqual( @NonNull SparseArray first, @NonNull SparseArray second) { if (first == second) { @@ -683,6 +602,20 @@ public final class ActiveOperationalDataset implements Parcelable { return sb.toString(); } + static String checkNetworkName(@NonNull String networkName) { + requireNonNull(networkName, "networkName cannot be null"); + + int nameLength = networkName.getBytes(UTF_8).length; + checkArgument( + nameLength >= LENGTH_MIN_NETWORK_NAME_BYTES + && nameLength <= LENGTH_MAX_NETWORK_NAME_BYTES, + "Invalid network name (length = %d, expectedLengthRange = [%d, %d])", + nameLength, + LENGTH_MIN_NETWORK_NAME_BYTES, + LENGTH_MAX_NETWORK_NAME_BYTES); + return networkName; + } + /** The builder for creating {@link ActiveOperationalDataset} objects. */ public static final class Builder { private OperationalDatasetTimestamp mActiveTimestamp; @@ -748,7 +681,7 @@ public final class ActiveOperationalDataset implements Parcelable { * @param networkName the name of the Thread network * @throws IllegalArgumentException if length of the UTF-8 representation of {@code * networkName} isn't in range of [{@link #LENGTH_MIN_NETWORK_NAME_BYTES}, {@link - * #LENGTH_MAX_NETWORK_NAME_BYTES}]. + * #LENGTH_MAX_NETWORK_NAME_BYTES}] */ @NonNull public Builder setNetworkName( @@ -757,26 +690,16 @@ public final class ActiveOperationalDataset implements Parcelable { min = LENGTH_MIN_NETWORK_NAME_BYTES, max = LENGTH_MAX_NETWORK_NAME_BYTES) String networkName) { - requireNonNull(networkName, "networkName cannot be null"); - - int nameLength = networkName.getBytes(UTF_8).length; - checkArgument( - nameLength >= LENGTH_MIN_NETWORK_NAME_BYTES - && nameLength <= LENGTH_MAX_NETWORK_NAME_BYTES, - "Invalid network name (length = %d, expectedLengthRange = [%d, %d])", - nameLength, - LENGTH_MIN_NETWORK_NAME_BYTES, - LENGTH_MAX_NETWORK_NAME_BYTES); - this.mNetworkName = networkName; + this.mNetworkName = checkNetworkName(networkName); return this; } /** * Sets the Extended PAN ID. * - *

Use with caution. A randomly generated Extended PAN ID should be used for real Thread + *

Use with caution. A randomized Extended PAN ID should be used for real Thread * networks. It's discouraged to call this method to override the default value created by - * {@link ActiveOperationalDataset#createRandomDataset} in production. + * {@link ThreadNetworkController#createRandomizedDataset} in production. * * @throws IllegalArgumentException if length of {@code extendedPanId} is not {@link * #LENGTH_EXTENDED_PAN_ID}. @@ -867,7 +790,7 @@ public final class ActiveOperationalDataset implements Parcelable { * *

Use with caution. A randomly generated PSKc should be used for real Thread networks. * It's discouraged to call this method to override the default value created by {@link - * ActiveOperationalDataset#createRandomDataset} in production. + * ThreadNetworkController#createRandomizedDataset} in production. * * @param pskc the key stretched version of the Commissioning Credential for the network * @throws IllegalArgumentException if length of {@code pskc} is not {@link #LENGTH_PSKC} @@ -889,7 +812,7 @@ public final class ActiveOperationalDataset implements Parcelable { * *

Use with caution, randomly generated Network Key should be used for real Thread * networks. It's discouraged to call this method to override the default value created by - * {@link ActiveOperationalDataset#createRandomDataset} in production. + * {@link ThreadNetworkController#createRandomizedDataset} in production. * * @param networkKey a 128-bit security key-derivation key for the Thread Network * @throws IllegalArgumentException if length of {@code networkKey} is not {@link @@ -930,8 +853,16 @@ public final class ActiveOperationalDataset implements Parcelable { return this; } + /** + * Sets the Mesh-Local Prefix. + * + * @param meshLocalPrefix the prefix used for realm-local traffic within the mesh + * @throws IllegalArgumentException if {@code meshLocalPrefix} doesn't start with {@code + * 0xfd} or has length other than {@code LENGTH_MESH_LOCAL_PREFIX_BITS / 8} + * @hide + */ @NonNull - private Builder setMeshLocalPrefix(byte[] meshLocalPrefix) { + public Builder setMeshLocalPrefix(byte[] meshLocalPrefix) { final int prefixLength = meshLocalPrefix.length * 8; checkArgument( prefixLength == LENGTH_MESH_LOCAL_PREFIX_BITS, diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl index 0e62b0b16f..51e4d88752 100644 --- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl +++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl @@ -40,4 +40,5 @@ interface IThreadNetworkController { void leave(in IOperationReceiver receiver); int getThreadVersion(); + void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver); } diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java index 9d6a2574a9..ec39db4260 100644 --- a/thread/framework/java/android/net/thread/ThreadNetworkController.java +++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java @@ -113,6 +113,33 @@ public final class ThreadNetworkController { } } + /** + * Creates a new Active Operational Dataset with randomized parameters. + * + *

This method is the recommended way to create a randomized dataset which can be used with + * {@link #join} to securely join this device to the specified network . It's highly discouraged + * to change the randomly generated Extended PAN ID, Network Key or PSKc, as it will compromise + * the security of a Thread network. + * + * @throws IllegalArgumentException if length of the UTF-8 representation of {@code networkName} + * isn't in range of [{@link #LENGTH_MIN_NETWORK_NAME_BYTES}, {@link + * #LENGTH_MAX_NETWORK_NAME_BYTES}] + */ + public void createRandomizedDataset( + @NonNull String networkName, + @NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver receiver) { + ActiveOperationalDataset.checkNetworkName(networkName); + requireNonNull(executor, "executor cannot be null"); + requireNonNull(receiver, "receiver cannot be null"); + try { + mControllerService.createRandomizedDataset( + networkName, new ActiveDatasetReceiverProxy(executor, receiver)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + /** Returns {@code true} if {@code deviceRole} indicates an attached state. */ public static boolean isAttached(@DeviceRole int deviceRole) { return deviceRole == DEVICE_ROLE_CHILD @@ -449,6 +476,29 @@ public final class ThreadNetworkController { executor.execute(() -> receiver.onError(new ThreadNetworkException(errorCode, errorMsg))); } + private static final class ActiveDatasetReceiverProxy + extends IActiveOperationalDatasetReceiver.Stub { + final Executor mExecutor; + final OutcomeReceiver mResultReceiver; + + ActiveDatasetReceiverProxy( + @CallbackExecutor Executor executor, + OutcomeReceiver resultReceiver) { + this.mExecutor = executor; + this.mResultReceiver = resultReceiver; + } + + @Override + public void onSuccess(ActiveOperationalDataset dataset) { + mExecutor.execute(() -> mResultReceiver.onResult(dataset)); + } + + @Override + public void onError(int errorCode, String errorMessage) { + propagateError(mExecutor, mResultReceiver, errorCode, errorMessage); + } + } + private static final class OperationReceiverProxy extends IOperationReceiver.Stub { final Executor mExecutor; final OutcomeReceiver mResultReceiver; diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java index 3470f27dff..6c9a7750ce 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java @@ -14,6 +14,13 @@ package com.android.server.thread; +import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ; +import static android.net.thread.ActiveOperationalDataset.LENGTH_EXTENDED_PAN_ID; +import static android.net.thread.ActiveOperationalDataset.LENGTH_MESH_LOCAL_PREFIX_BITS; +import static android.net.thread.ActiveOperationalDataset.LENGTH_NETWORK_KEY; +import static android.net.thread.ActiveOperationalDataset.LENGTH_PSKC; +import static android.net.thread.ActiveOperationalDataset.MESH_LOCAL_PREFIX_FIRST_BYTE; +import static android.net.thread.ActiveOperationalDataset.SecurityPolicy.DEFAULT_ROTATION_TIME_HOURS; import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED; import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3; import static android.net.thread.ThreadNetworkException.ERROR_ABORTED; @@ -52,10 +59,13 @@ import android.net.NetworkCapabilities; import android.net.NetworkProvider; import android.net.NetworkScore; import android.net.thread.ActiveOperationalDataset; +import android.net.thread.ActiveOperationalDataset.SecurityPolicy; +import android.net.thread.IActiveOperationalDatasetReceiver; import android.net.thread.IOperationReceiver; import android.net.thread.IOperationalDatasetCallback; import android.net.thread.IStateCallback; import android.net.thread.IThreadNetworkController; +import android.net.thread.OperationalDatasetTimestamp; import android.net.thread.PendingOperationalDataset; import android.net.thread.ThreadNetworkController; import android.net.thread.ThreadNetworkController.DeviceRole; @@ -66,6 +76,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.server.ServiceManagerWrapper; @@ -78,9 +89,12 @@ import com.android.server.thread.openthread.OtDaemonState; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.time.Instant; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Random; import java.util.function.Supplier; /** @@ -112,6 +126,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub private final LinkProperties mLinkProperties = new LinkProperties(); private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy(); + // TODO(b/308310823): read supported channel from Thread dameon + private final int mSupportedChannelMask = 0x07FFF800; // from channel 11 to 26 + private IOtDaemon mOtDaemon; private NetworkAgent mNetworkAgent; @@ -304,6 +321,93 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub return THREAD_VERSION_1_3; } + @Override + public void createRandomizedDataset( + String networkName, IActiveOperationalDatasetReceiver receiver) { + mHandler.post( + () -> { + ActiveOperationalDataset dataset = + createRandomizedDatasetInternal( + networkName, + mSupportedChannelMask, + Instant.now(), + new Random(), + new SecureRandom()); + try { + receiver.onSuccess(dataset); + } catch (RemoteException e) { + // The client is dead, do nothing + } + }); + } + + private static ActiveOperationalDataset createRandomizedDatasetInternal( + String networkName, + int supportedChannelMask, + Instant now, + Random random, + SecureRandom secureRandom) { + int panId = random.nextInt(/* bound= */ 0xffff); + final byte[] meshLocalPrefix = newRandomBytes(random, LENGTH_MESH_LOCAL_PREFIX_BITS / 8); + meshLocalPrefix[0] = MESH_LOCAL_PREFIX_FIRST_BYTE; + + final SparseArray channelMask = new SparseArray<>(1); + channelMask.put(CHANNEL_PAGE_24_GHZ, channelMaskToByteArray(supportedChannelMask)); + + final byte[] securityFlags = new byte[] {(byte) 0xff, (byte) 0xf8}; + + return new ActiveOperationalDataset.Builder() + .setActiveTimestamp( + new OperationalDatasetTimestamp( + now.getEpochSecond() & 0xffffffffffffL, 0, false)) + .setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID)) + .setPanId(panId) + .setNetworkName(networkName) + .setChannel(CHANNEL_PAGE_24_GHZ, selectRandomChannel(supportedChannelMask, random)) + .setChannelMask(channelMask) + .setPskc(newRandomBytes(secureRandom, LENGTH_PSKC)) + .setNetworkKey(newRandomBytes(secureRandom, LENGTH_NETWORK_KEY)) + .setMeshLocalPrefix(meshLocalPrefix) + .setSecurityPolicy(new SecurityPolicy(DEFAULT_ROTATION_TIME_HOURS, securityFlags)) + .build(); + } + + private static byte[] newRandomBytes(Random random, int length) { + byte[] result = new byte[length]; + random.nextBytes(result); + return result; + } + + private static byte[] channelMaskToByteArray(int channelMask) { + // Per Thread spec, a Channel Mask is: + // A variable-length bit mask that identifies the channels within the channel page + // (1 = selected, 0 = unselected). The channels are represented in most significant bit + // order. For example, the most significant bit of the left-most byte indicates channel 0. + // If channel 0 and channel 10 are selected, the mask would be: 80 20 00 00. For IEEE + // 802.15.4-2006 2.4 GHz PHY, the ChannelMask is 27 bits and MaskLength is 4. + // + // The pass-in channelMask represents a channel K by (channelMask & (1 << K)), so here + // needs to do bit-wise reverse to convert it to the Thread spec format in bytes. + channelMask = Integer.reverse(channelMask); + return new byte[] { + (byte) (channelMask >>> 24), + (byte) (channelMask >>> 16), + (byte) (channelMask >>> 8), + (byte) channelMask + }; + } + + private static int selectRandomChannel(int supportedChannelMask, Random random) { + int num = random.nextInt(Integer.bitCount(supportedChannelMask)); + for (int i = 0; i < 32; i++) { + if ((supportedChannelMask & 1) == 1 && (num--) == 0) { + return i; + } + supportedChannelMask >>>= 1; + } + return -1; + } + private void enforceAllCallingPermissionsGranted(String... permissions) { for (String permission : permissions) { mContext.enforceCallingPermission( diff --git a/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java b/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java index 39df21b340..0e76930fb2 100644 --- a/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java +++ b/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java @@ -70,7 +70,7 @@ public final class ActiveOperationalDatasetTest { // PAN ID: 0xD9A0 // PSKc: A245479C836D551B9CA557F7B9D351B4 // Security Policy: 672 onrcb - private static final byte[] VALID_DATASET = + private static final byte[] VALID_DATASET_TLVS = base16().decode( "0E080000000000010000000300001335060004001FFFE002" + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31" @@ -78,6 +78,9 @@ public final class ActiveOperationalDatasetTest { + "642D643961300102D9A00410A245479C836D551B9CA557F7" + "B9D351B40C0402A0FFF8"); + private static final ActiveOperationalDataset DEFAULT_DATASET = + ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS); + private static byte[] removeTlv(byte[] dataset, int type) { ByteArrayOutputStream os = new ByteArrayOutputStream(dataset.length); int i = 0; @@ -105,7 +108,8 @@ public final class ActiveOperationalDatasetTest { @Test public void parcelable_parcelingIsLossLess() { - ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET); + ActiveOperationalDataset dataset = + ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS); assertParcelingIsLossless(dataset); } @@ -126,7 +130,8 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_invalidNetworkKeyTlv_throwsIllegalArgument() { - byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_NETWORK_KEY, "05080000000000000000"); + byte[] invalidTlv = + replaceTlv(VALID_DATASET_TLVS, TYPE_NETWORK_KEY, "05080000000000000000"); assertThrows( IllegalArgumentException.class, @@ -135,7 +140,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noNetworkKeyTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_NETWORK_KEY); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_NETWORK_KEY); assertThrows( IllegalArgumentException.class, @@ -144,7 +149,8 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_invalidActiveTimestampTlv_throwsIllegalArgument() { - byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_ACTIVE_TIMESTAMP, "0E0700000000010000"); + byte[] invalidTlv = + replaceTlv(VALID_DATASET_TLVS, TYPE_ACTIVE_TIMESTAMP, "0E0700000000010000"); assertThrows( IllegalArgumentException.class, @@ -153,7 +159,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noActiveTimestampTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_ACTIVE_TIMESTAMP); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_ACTIVE_TIMESTAMP); assertThrows( IllegalArgumentException.class, @@ -162,7 +168,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_invalidNetworkNameTlv_emptyName_throwsIllegalArgument() { - byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_NETWORK_NAME, "0300"); + byte[] invalidTlv = replaceTlv(VALID_DATASET_TLVS, TYPE_NETWORK_NAME, "0300"); assertThrows( IllegalArgumentException.class, @@ -173,7 +179,9 @@ public final class ActiveOperationalDatasetTest { public void fromThreadTlvs_invalidNetworkNameTlv_tooLongName_throwsIllegalArgument() { byte[] invalidTlv = replaceTlv( - VALID_DATASET, TYPE_NETWORK_NAME, "03114142434445464748494A4B4C4D4E4F5051"); + VALID_DATASET_TLVS, + TYPE_NETWORK_NAME, + "03114142434445464748494A4B4C4D4E4F5051"); assertThrows( IllegalArgumentException.class, @@ -182,7 +190,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noNetworkNameTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_NETWORK_NAME); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_NETWORK_NAME); assertThrows( IllegalArgumentException.class, @@ -191,7 +199,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_invalidChannelTlv_channelMissing_throwsIllegalArgument() { - byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "000100"); + byte[] invalidTlv = replaceTlv(VALID_DATASET_TLVS, TYPE_CHANNEL, "000100"); assertThrows( IllegalArgumentException.class, @@ -200,7 +208,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_undefinedChannelPage_success() { - byte[] datasetTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "0003010020"); + byte[] datasetTlv = replaceTlv(VALID_DATASET_TLVS, TYPE_CHANNEL, "0003010020"); ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs(datasetTlv); @@ -210,8 +218,8 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_invalid2P4GhzChannel_throwsIllegalArgument() { - byte[] invalidTlv1 = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "000300000A"); - byte[] invalidTlv2 = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "000300001B"); + byte[] invalidTlv1 = replaceTlv(VALID_DATASET_TLVS, TYPE_CHANNEL, "000300000A"); + byte[] invalidTlv2 = replaceTlv(VALID_DATASET_TLVS, TYPE_CHANNEL, "000300001B"); assertThrows( IllegalArgumentException.class, @@ -223,7 +231,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_valid2P4GhzChannelTlv_success() { - byte[] validTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "0003000010"); + byte[] validTlv = replaceTlv(VALID_DATASET_TLVS, TYPE_CHANNEL, "0003000010"); ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs(validTlv); @@ -232,7 +240,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noChannelTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_CHANNEL); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_CHANNEL); assertThrows( IllegalArgumentException.class, @@ -241,7 +249,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_prematureEndOfChannelMaskEntry_throwsIllegalArgument() { - byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL_MASK, "350100"); + byte[] invalidTlv = replaceTlv(VALID_DATASET_TLVS, TYPE_CHANNEL_MASK, "350100"); assertThrows( IllegalArgumentException.class, @@ -250,7 +258,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_inconsistentChannelMaskLength_throwsIllegalArgument() { - byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL_MASK, "3506000500010000"); + byte[] invalidTlv = replaceTlv(VALID_DATASET_TLVS, TYPE_CHANNEL_MASK, "3506000500010000"); assertThrows( IllegalArgumentException.class, @@ -261,7 +269,7 @@ public final class ActiveOperationalDatasetTest { public void fromThreadTlvs_unsupportedChannelMaskLength_success() { ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs( - replaceTlv(VALID_DATASET, TYPE_CHANNEL_MASK, "350700050001000000")); + replaceTlv(VALID_DATASET_TLVS, TYPE_CHANNEL_MASK, "350700050001000000")); SparseArray channelMask = dataset.getChannelMask(); assertThat(channelMask.size()).isEqualTo(1); @@ -271,7 +279,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noChannelMaskTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_CHANNEL_MASK); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_CHANNEL_MASK); assertThrows( IllegalArgumentException.class, @@ -280,7 +288,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_invalidPanIdTlv_throwsIllegalArgument() { - byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_PAN_ID, "010101"); + byte[] invalidTlv = replaceTlv(VALID_DATASET_TLVS, TYPE_PAN_ID, "010101"); assertThrows( IllegalArgumentException.class, @@ -289,7 +297,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noPanIdTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_PAN_ID); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_PAN_ID); assertThrows( IllegalArgumentException.class, @@ -298,7 +306,8 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_invalidExtendedPanIdTlv_throwsIllegalArgument() { - byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_EXTENDED_PAN_ID, "020700010203040506"); + byte[] invalidTlv = + replaceTlv(VALID_DATASET_TLVS, TYPE_EXTENDED_PAN_ID, "020700010203040506"); assertThrows( IllegalArgumentException.class, @@ -307,7 +316,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noExtendedPanIdTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_EXTENDED_PAN_ID); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_EXTENDED_PAN_ID); assertThrows( IllegalArgumentException.class, @@ -317,7 +326,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_invalidPskcTlv_throwsIllegalArgument() { byte[] invalidTlv = - replaceTlv(VALID_DATASET, TYPE_PSKC, "0411000102030405060708090A0B0C0D0E0F10"); + replaceTlv(VALID_DATASET_TLVS, TYPE_PSKC, "0411000102030405060708090A0B0C0D0E0F10"); assertThrows( IllegalArgumentException.class, @@ -326,7 +335,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noPskcTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_PSKC); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_PSKC); assertThrows( IllegalArgumentException.class, @@ -336,7 +345,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_invalidMeshLocalPrefixTlv_throwsIllegalArgument() { byte[] invalidTlv = - replaceTlv(VALID_DATASET, TYPE_MESH_LOCAL_PREFIX, "0709FD0001020304050607"); + replaceTlv(VALID_DATASET_TLVS, TYPE_MESH_LOCAL_PREFIX, "0709FD0001020304050607"); assertThrows( IllegalArgumentException.class, @@ -345,7 +354,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noMeshLocalPrefixTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_MESH_LOCAL_PREFIX); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_MESH_LOCAL_PREFIX); assertThrows( IllegalArgumentException.class, @@ -354,7 +363,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_tooShortSecurityPolicyTlv_throwsIllegalArgument() { - byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_SECURITY_POLICY, "0C0101"); + byte[] invalidTlv = replaceTlv(VALID_DATASET_TLVS, TYPE_SECURITY_POLICY, "0C0101"); assertThrows( IllegalArgumentException.class, @@ -363,7 +372,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_noSecurityPolicyTlv_throwsIllegalArgument() { - byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_SECURITY_POLICY); + byte[] invalidTlv = removeTlv(VALID_DATASET_TLVS, TYPE_SECURITY_POLICY); assertThrows( IllegalArgumentException.class, @@ -429,7 +438,7 @@ public final class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_containsUnknownTlvs_unknownTlvsRetained() { - final byte[] datasetWithUnknownTlvs = addTlv(VALID_DATASET, "AA01FFBB020102"); + final byte[] datasetWithUnknownTlvs = addTlv(VALID_DATASET_TLVS, "AA01FFBB020102"); ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs(datasetWithUnknownTlvs); @@ -443,7 +452,7 @@ public final class ActiveOperationalDatasetTest { @Test public void toThreadTlvs_conversionIsLossLess() { - ActiveOperationalDataset dataset1 = ActiveOperationalDataset.createRandomDataset(); + ActiveOperationalDataset dataset1 = DEFAULT_DATASET; ActiveOperationalDataset dataset2 = ActiveOperationalDataset.fromThreadTlvs(dataset1.toThreadTlvs()); @@ -465,9 +474,7 @@ public final class ActiveOperationalDatasetTest { }; ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) - .setNetworkKey(networkKey) - .build(); + new Builder(DEFAULT_DATASET).setNetworkKey(networkKey).build(); assertThat(dataset.getNetworkKey()).isEqualTo(networkKey); } @@ -475,7 +482,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setInvalidNetworkKey_throwsIllegalArgument() { byte[] invalidNetworkKey = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); assertThrows( IllegalArgumentException.class, () -> builder.setNetworkKey(invalidNetworkKey)); @@ -486,9 +493,7 @@ public final class ActiveOperationalDatasetTest { byte[] extendedPanId = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) - .setExtendedPanId(extendedPanId) - .build(); + new Builder(DEFAULT_DATASET).setExtendedPanId(extendedPanId).build(); assertThat(dataset.getExtendedPanId()).isEqualTo(extendedPanId); } @@ -496,31 +501,28 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setInvalidExtendedPanId_throwsIllegalArgument() { byte[] extendedPanId = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); assertThrows(IllegalArgumentException.class, () -> builder.setExtendedPanId(extendedPanId)); } @Test public void builder_setValidPanId_success() { - ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) - .setPanId(0xfffe) - .build(); + ActiveOperationalDataset dataset = new Builder(DEFAULT_DATASET).setPanId(0xfffe).build(); assertThat(dataset.getPanId()).isEqualTo(0xfffe); } @Test public void builder_setInvalidPanId_throwsIllegalArgument() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); assertThrows(IllegalArgumentException.class, () -> builder.setPanId(0xffff)); } @Test public void builder_setInvalidChannel_throwsIllegalArgument() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); assertThrows(IllegalArgumentException.class, () -> builder.setChannel(0, 0)); assertThrows(IllegalArgumentException.class, () -> builder.setChannel(0, 27)); @@ -529,9 +531,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setValid2P4GhzChannel_success() { ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) - .setChannel(CHANNEL_PAGE_24_GHZ, 16) - .build(); + new Builder(DEFAULT_DATASET).setChannel(CHANNEL_PAGE_24_GHZ, 16).build(); assertThat(dataset.getChannel()).isEqualTo(16); assertThat(dataset.getChannelPage()).isEqualTo(CHANNEL_PAGE_24_GHZ); @@ -540,23 +540,21 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setValidNetworkName_success() { ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) - .setNetworkName("ot-network") - .build(); + new Builder(DEFAULT_DATASET).setNetworkName("ot-network").build(); assertThat(dataset.getNetworkName()).isEqualTo("ot-network"); } @Test public void builder_setEmptyNetworkName_throwsIllegalArgument() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); assertThrows(IllegalArgumentException.class, () -> builder.setNetworkName("")); } @Test public void builder_setTooLongNetworkName_throwsIllegalArgument() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); assertThrows( IllegalArgumentException.class, () -> builder.setNetworkName("openthread-network")); @@ -564,7 +562,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setTooLongUtf8NetworkName_throwsIllegalArgument() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); // UTF-8 encoded length of "我的线程网络" is 18 bytes which exceeds the max length assertThrows(IllegalArgumentException.class, () -> builder.setNetworkName("我的线程网络")); @@ -573,9 +571,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setValidUtf8NetworkName_success() { ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) - .setNetworkName("我的网络") - .build(); + new Builder(DEFAULT_DATASET).setNetworkName("我的网络").build(); assertThat(dataset.getNetworkName()).isEqualTo("我的网络"); } @@ -584,8 +580,7 @@ public final class ActiveOperationalDatasetTest { public void builder_setValidPskc_success() { byte[] pskc = base16().decode("A245479C836D551B9CA557F7B9D351B4"); - ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()).setPskc(pskc).build(); + ActiveOperationalDataset dataset = new Builder(DEFAULT_DATASET).setPskc(pskc).build(); assertThat(dataset.getPskc()).isEqualTo(pskc); } @@ -593,18 +588,18 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setTooLongPskc_throwsIllegalArgument() { byte[] tooLongPskc = base16().decode("A245479C836D551B9CA557F7B9D351B400"); - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); assertThrows(IllegalArgumentException.class, () -> builder.setPskc(tooLongPskc)); } @Test public void builder_setValidChannelMask_success() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); SparseArray channelMask = new SparseArray(1); channelMask.put(0, new byte[] {0x00, 0x00, 0x01, 0x00}); - ActiveOperationalDataset dataset = builder.setChannelMask(channelMask).build(); + ActiveOperationalDataset dataset = + new Builder(DEFAULT_DATASET).setChannelMask(channelMask).build(); SparseArray resultChannelMask = dataset.getChannelMask(); assertThat(resultChannelMask.size()).isEqualTo(1); @@ -613,7 +608,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setEmptyChannelMask_throwsIllegalArgument() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); assertThrows( IllegalArgumentException.class, @@ -623,7 +618,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setValidActiveTimestamp_success() { ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) + new Builder(DEFAULT_DATASET) .setActiveTimestamp( new OperationalDatasetTimestamp( /* seconds= */ 1, @@ -638,7 +633,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_wrongMeshLocalPrefixLength_throwsIllegalArguments() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); // The Mesh-Local Prefix length must be 64 bits assertThrows( @@ -656,7 +651,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_meshLocalPrefixNotStartWith0xfd_throwsIllegalArguments() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(); assertThrows( IllegalArgumentException.class, @@ -666,9 +661,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setValidMeshLocalPrefix_success() { ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) - .setMeshLocalPrefix(new IpPrefix("fd00::/64")) - .build(); + new Builder(DEFAULT_DATASET).setMeshLocalPrefix(new IpPrefix("fd00::/64")).build(); assertThat(dataset.getMeshLocalPrefix()).isEqualTo(new IpPrefix("fd00::/64")); } @@ -676,7 +669,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setValid1P2SecurityPolicy_success() { ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) + new Builder(DEFAULT_DATASET) .setSecurityPolicy( new SecurityPolicy(672, new byte[] {(byte) 0xff, (byte) 0xf8})) .build(); @@ -689,7 +682,7 @@ public final class ActiveOperationalDatasetTest { @Test public void builder_setValid1P1SecurityPolicy_success() { ActiveOperationalDataset dataset = - new Builder(ActiveOperationalDataset.createRandomDataset()) + new Builder(DEFAULT_DATASET) .setSecurityPolicy(new SecurityPolicy(672, new byte[] {(byte) 0xff})) .build(); diff --git a/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java b/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java index 7a49957f39..0bb18ce275 100644 --- a/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java +++ b/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java @@ -25,8 +25,10 @@ import static org.junit.Assert.assertThrows; import android.net.IpPrefix; import android.net.thread.ActiveOperationalDataset; +import android.net.thread.ActiveOperationalDataset.SecurityPolicy; import android.net.thread.OperationalDatasetTimestamp; import android.net.thread.PendingOperationalDataset; +import android.util.SparseArray; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -37,20 +39,36 @@ import com.google.common.testing.EqualsTester; import org.junit.Test; import org.junit.runner.RunWith; +import java.net.InetAddress; import java.time.Duration; /** Tests for {@link PendingOperationalDataset}. */ @SmallTest @RunWith(AndroidJUnit4.class) public final class PendingOperationalDatasetTest { - private static final ActiveOperationalDataset DEFAULT_ACTIVE_DATASET = - ActiveOperationalDataset.createRandomDataset(); + private static ActiveOperationalDataset createActiveDataset() throws Exception { + SparseArray channelMask = new SparseArray<>(1); + channelMask.put(0, new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0}); + + return new ActiveOperationalDataset.Builder() + .setActiveTimestamp(new OperationalDatasetTimestamp(100, 10, false)) + .setExtendedPanId(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}) + .setPanId(12345) + .setNetworkName("defaultNet") + .setChannel(0, 18) + .setChannelMask(channelMask) + .setPskc(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) + .setNetworkKey(new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + .setMeshLocalPrefix(new IpPrefix(InetAddress.getByName("fd00::1"), 64)) + .setSecurityPolicy(new SecurityPolicy(672, new byte[] {(byte) 0xff, (byte) 0xf8})) + .build(); + } @Test - public void parcelable_parcelingIsLossLess() { + public void parcelable_parcelingIsLossLess() throws Exception { PendingOperationalDataset dataset = new PendingOperationalDataset( - DEFAULT_ACTIVE_DATASET, + createActiveDataset(), new OperationalDatasetTimestamp(31536000, 200, false), Duration.ofHours(100)); @@ -58,9 +76,15 @@ public final class PendingOperationalDatasetTest { } @Test - public void equalityTests() { - ActiveOperationalDataset activeDataset1 = ActiveOperationalDataset.createRandomDataset(); - ActiveOperationalDataset activeDataset2 = ActiveOperationalDataset.createRandomDataset(); + public void equalityTests() throws Exception { + ActiveOperationalDataset activeDataset1 = + new ActiveOperationalDataset.Builder(createActiveDataset()) + .setNetworkName("net1") + .build(); + ActiveOperationalDataset activeDataset2 = + new ActiveOperationalDataset.Builder(createActiveDataset()) + .setNetworkName("net2") + .build(); new EqualsTester() .addEqualityGroup( @@ -103,14 +127,15 @@ public final class PendingOperationalDatasetTest { } @Test - public void constructor_correctValuesAreSet() { + public void constructor_correctValuesAreSet() throws Exception { + final ActiveOperationalDataset activeDataset = createActiveDataset(); PendingOperationalDataset dataset = new PendingOperationalDataset( - DEFAULT_ACTIVE_DATASET, + activeDataset, new OperationalDatasetTimestamp(31536000, 200, false), Duration.ofHours(100)); - assertThat(dataset.getActiveOperationalDataset()).isEqualTo(DEFAULT_ACTIVE_DATASET); + assertThat(dataset.getActiveOperationalDataset()).isEqualTo(activeDataset); assertThat(dataset.getPendingTimestamp()) .isEqualTo(new OperationalDatasetTimestamp(31536000, 200, false)); assertThat(dataset.getDelayTimer()).isEqualTo(Duration.ofHours(100)); @@ -166,33 +191,35 @@ public final class PendingOperationalDatasetTest { } @Test - public void fromThreadTlvs_completePendingDatasetTlvs_success() { + public void fromThreadTlvs_completePendingDatasetTlvs_success() throws Exception { + final ActiveOperationalDataset activeDataset = createActiveDataset(); + // Type Length Value // 0x33 0x08 0x0000000000010000 (Pending Timestamp TLV) // 0x34 0x04 0x0000012C (Delay Timer TLV) final byte[] pendingTimestampAndDelayTimerTlvs = base16().decode("3308000000000001000034040000012C"); final byte[] pendingDatasetTlvs = - Bytes.concat( - pendingTimestampAndDelayTimerTlvs, DEFAULT_ACTIVE_DATASET.toThreadTlvs()); + Bytes.concat(pendingTimestampAndDelayTimerTlvs, activeDataset.toThreadTlvs()); PendingOperationalDataset dataset = PendingOperationalDataset.fromThreadTlvs(pendingDatasetTlvs); - assertThat(dataset.getActiveOperationalDataset()).isEqualTo(DEFAULT_ACTIVE_DATASET); + assertThat(dataset.getActiveOperationalDataset()).isEqualTo(activeDataset); assertThat(dataset.getPendingTimestamp()) .isEqualTo(new OperationalDatasetTimestamp(1, 0, false)); assertThat(dataset.getDelayTimer()).isEqualTo(Duration.ofMillis(300)); } @Test - public void fromThreadTlvs_PendingTimestampTlvIsMissing_throwsIllegalArgument() { + public void fromThreadTlvs_PendingTimestampTlvIsMissing_throwsIllegalArgument() + throws Exception { // Type Length Value // 0x34 0x04 0x00000064 (Delay Timer TLV) final byte[] pendingTimestampAndDelayTimerTlvs = base16().decode("34040000012C"); final byte[] pendingDatasetTlvs = Bytes.concat( - pendingTimestampAndDelayTimerTlvs, DEFAULT_ACTIVE_DATASET.toThreadTlvs()); + pendingTimestampAndDelayTimerTlvs, createActiveDataset().toThreadTlvs()); assertThrows( IllegalArgumentException.class, @@ -200,13 +227,13 @@ public final class PendingOperationalDatasetTest { } @Test - public void fromThreadTlvs_delayTimerTlvIsMissing_throwsIllegalArgument() { + public void fromThreadTlvs_delayTimerTlvIsMissing_throwsIllegalArgument() throws Exception { // Type Length Value // 0x33 0x08 0x0000000000010000 (Pending Timestamp TLV) final byte[] pendingTimestampAndDelayTimerTlvs = base16().decode("33080000000000010000"); final byte[] pendingDatasetTlvs = Bytes.concat( - pendingTimestampAndDelayTimerTlvs, DEFAULT_ACTIVE_DATASET.toThreadTlvs()); + pendingTimestampAndDelayTimerTlvs, createActiveDataset().toThreadTlvs()); assertThrows( IllegalArgumentException.class, @@ -214,8 +241,8 @@ public final class PendingOperationalDatasetTest { } @Test - public void fromThreadTlvs_activeDatasetTlvs_throwsIllegalArgument() { - final byte[] activeDatasetTlvs = DEFAULT_ACTIVE_DATASET.toThreadTlvs(); + public void fromThreadTlvs_activeDatasetTlvs_throwsIllegalArgument() throws Exception { + final byte[] activeDatasetTlvs = createActiveDataset().toThreadTlvs(); assertThrows( IllegalArgumentException.class, @@ -232,10 +259,10 @@ public final class PendingOperationalDatasetTest { } @Test - public void toThreadTlvs_conversionIsLossLess() { + public void toThreadTlvs_conversionIsLossLess() throws Exception { PendingOperationalDataset dataset1 = new PendingOperationalDataset( - DEFAULT_ACTIVE_DATASET, + createActiveDataset(), new OperationalDatasetTimestamp(31536000, 200, false), Duration.ofHours(100)); diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java index cfe310c05b..e17dd02128 100644 --- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java +++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java @@ -132,6 +132,13 @@ public class ThreadNetworkControllerTest { getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); } + private static ActiveOperationalDataset newRandomizedDataset( + String networkName, ThreadNetworkController controller) throws Exception { + SettableFuture future = SettableFuture.create(); + controller.createRandomizedDataset(networkName, directExecutor(), future::set); + return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); + } + private static boolean isAttached(ThreadNetworkController controller) throws Exception { return ThreadNetworkController.isAttached(getDeviceRole(controller)); } @@ -322,7 +329,7 @@ public class ThreadNetworkControllerTest { grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED); for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset = ActiveOperationalDataset.createRandomDataset(); + ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); SettableFuture joinFuture = SettableFuture.create(); controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)); @@ -335,11 +342,11 @@ public class ThreadNetworkControllerTest { } @Test - public void join_withoutPrivilegedPermission_throwsSecurityException() { + public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception { dropPermissions(); for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset = ActiveOperationalDataset.createRandomDataset(); + ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); assertThrows( SecurityException.class, @@ -356,7 +363,7 @@ public class ThreadNetworkControllerTest { for (ThreadNetworkController controller : getAllControllers()) { ActiveOperationalDataset activeDataset1 = new ActiveOperationalDataset.Builder( - ActiveOperationalDataset.createRandomDataset()) + newRandomizedDataset("TestNet", controller)) .setNetworkKey(KEY_1) .build(); ActiveOperationalDataset activeDataset2 = @@ -385,7 +392,7 @@ public class ThreadNetworkControllerTest { grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED); for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset = ActiveOperationalDataset.createRandomDataset(); + ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); SettableFuture joinFuture = SettableFuture.create(); SettableFuture leaveFuture = SettableFuture.create(); controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)); @@ -413,7 +420,7 @@ public class ThreadNetworkControllerTest { grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED); for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset = ActiveOperationalDataset.createRandomDataset(); + ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); SettableFuture joinFuture = SettableFuture.create(); SettableFuture leaveFuture1 = SettableFuture.create(); SettableFuture leaveFuture2 = SettableFuture.create(); @@ -437,7 +444,7 @@ public class ThreadNetworkControllerTest { for (ThreadNetworkController controller : getAllControllers()) { ActiveOperationalDataset activeDataset1 = new ActiveOperationalDataset.Builder( - ActiveOperationalDataset.createRandomDataset()) + newRandomizedDataset("TestNet", controller)) .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false)) .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1}) .build(); @@ -473,7 +480,7 @@ public class ThreadNetworkControllerTest { for (ThreadNetworkController controller : getAllControllers()) { PendingOperationalDataset pendingDataset = new PendingOperationalDataset( - ActiveOperationalDataset.createRandomDataset(), + newRandomizedDataset("TestNet", controller), OperationalDatasetTimestamp.fromInstant(Instant.now()), Duration.ofSeconds(30)); SettableFuture migrateFuture = SettableFuture.create(); @@ -496,9 +503,8 @@ public class ThreadNetworkControllerTest { for (ThreadNetworkController controller : getAllControllers()) { final ActiveOperationalDataset activeDataset = new ActiveOperationalDataset.Builder( - ActiveOperationalDataset.createRandomDataset()) + newRandomizedDataset("testNet", controller)) .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false)) - .setNetworkName("testNet") .build(); ActiveOperationalDataset activeDataset1 = new ActiveOperationalDataset.Builder(activeDataset) @@ -546,9 +552,8 @@ public class ThreadNetworkControllerTest { for (ThreadNetworkController controller : getAllControllers()) { final ActiveOperationalDataset activeDataset = new ActiveOperationalDataset.Builder( - ActiveOperationalDataset.createRandomDataset()) + newRandomizedDataset("validName", controller)) .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false)) - .setNetworkName("testNet") .build(); ActiveOperationalDataset activeDataset1 = new ActiveOperationalDataset.Builder(activeDataset) @@ -588,4 +593,35 @@ public class ThreadNetworkControllerTest { assertThat(getPendingOperationalDataset(controller)).isNull(); } } + + @Test + public void createRandomizedDataset_wrongNetworkNameLength_throwsIllegalArgumentException() { + for (ThreadNetworkController controller : getAllControllers()) { + assertThrows( + IllegalArgumentException.class, + () -> controller.createRandomizedDataset("", mExecutor, dataset -> {})); + + assertThrows( + IllegalArgumentException.class, + () -> + controller.createRandomizedDataset( + "ANetNameIs17Bytes", mExecutor, dataset -> {})); + } + } + + @Test + public void createRandomizedDataset_validNetworkName_success() throws Exception { + for (ThreadNetworkController controller : getAllControllers()) { + ActiveOperationalDataset dataset = newRandomizedDataset("validName", controller); + + assertThat(dataset.getNetworkName()).isEqualTo("validName"); + assertThat(dataset.getPanId()).isLessThan(0xffff); + assertThat(dataset.getChannelMask().size()).isAtLeast(1); + assertThat(dataset.getExtendedPanId()).hasLength(8); + assertThat(dataset.getNetworkKey()).hasLength(16); + assertThat(dataset.getPskc()).hasLength(16); + assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64); + assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd); + } + } } diff --git a/thread/tests/unit/src/android/net/thread/ActiveOperationalDatasetTest.java b/thread/tests/unit/src/android/net/thread/ActiveOperationalDatasetTest.java index 78eb3d03fe..7284968337 100644 --- a/thread/tests/unit/src/android/net/thread/ActiveOperationalDatasetTest.java +++ b/thread/tests/unit/src/android/net/thread/ActiveOperationalDatasetTest.java @@ -20,14 +20,7 @@ import static com.google.common.io.BaseEncoding.base16; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import android.net.IpPrefix; import android.net.thread.ActiveOperationalDataset.Builder; import android.net.thread.ActiveOperationalDataset.SecurityPolicy; import android.util.SparseArray; @@ -61,7 +54,7 @@ public class ActiveOperationalDatasetTest { // PAN ID: 0xD9A0 // PSKc: A245479C836D551B9CA557F7B9D351B4 // Security Policy: 672 onrcb - private static final byte[] VALID_DATASET = + private static final byte[] VALID_DATASET_TLVS = base16().decode( "0E080000000000010000000300001335060004001FFFE002" + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31" @@ -83,7 +76,7 @@ public class ActiveOperationalDatasetTest { @Test public void fromThreadTlvs_containsUnknownTlvs_unknownTlvsRetained() { - byte[] datasetWithUnknownTlvs = addTlv(VALID_DATASET, "AA01FFBB020102"); + byte[] datasetWithUnknownTlvs = addTlv(VALID_DATASET_TLVS, "AA01FFBB020102"); ActiveOperationalDataset dataset1 = ActiveOperationalDataset.fromThreadTlvs(datasetWithUnknownTlvs); @@ -97,67 +90,9 @@ public class ActiveOperationalDatasetTest { assertThat(dataset2).isEqualTo(dataset1); } - @Test - public void createRandomDataset_fieldsAreRandomized() { - // Always return the max bounded value - doAnswer(invocation -> (int) invocation.getArgument(0) - 1) - .when(mockRandom) - .nextInt(anyInt()); - doAnswer( - invocation -> { - byte[] output = invocation.getArgument(0); - for (int i = 0; i < output.length; ++i) { - output[i] = (byte) (i + 10); - } - return null; - }) - .when(mockRandom) - .nextBytes(any(byte[].class)); - doAnswer( - invocation -> { - byte[] output = invocation.getArgument(0); - for (int i = 0; i < output.length; ++i) { - output[i] = (byte) (i + 30); - } - return null; - }) - .when(mockSecureRandom) - .nextBytes(any(byte[].class)); - - ActiveOperationalDataset dataset = - ActiveOperationalDataset.createRandomDataset(mockRandom, mockSecureRandom); - - assertThat(dataset.getActiveTimestamp()) - .isEqualTo(new OperationalDatasetTimestamp(1, 0, false)); - assertThat(dataset.getExtendedPanId()) - .isEqualTo(new byte[] {10, 11, 12, 13, 14, 15, 16, 17}); - assertThat(dataset.getMeshLocalPrefix()) - .isEqualTo(new IpPrefix("fd0b:0c0d:0e0f:1011::/64")); - verify(mockRandom, times(2)).nextBytes(any(byte[].class)); - assertThat(dataset.getPanId()).isEqualTo(0xfffe); // PAN ID <= 0xfffe - verify(mockRandom, times(1)).nextInt(eq(0xffff)); - assertThat(dataset.getChannel()).isEqualTo(26); - verify(mockRandom, times(1)).nextInt(eq(16)); - assertThat(dataset.getChannelPage()).isEqualTo(0); - assertThat(dataset.getChannelMask().size()).isEqualTo(1); - assertThat(dataset.getPskc()) - .isEqualTo( - new byte[] { - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45 - }); - assertThat(dataset.getNetworkKey()) - .isEqualTo( - new byte[] { - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45 - }); - verify(mockSecureRandom, times(2)).nextBytes(any(byte[].class)); - assertThat(dataset.getSecurityPolicy()) - .isEqualTo(new SecurityPolicy(672, new byte[] {(byte) 0xff, (byte) 0xf8})); - } - @Test public void builder_buildWithTooLongTlvs_throwsIllegalState() { - Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset()); + Builder builder = new Builder(ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS)); for (int i = 0; i < 10; i++) { builder.addUnknownTlv(i, new byte[20]); } @@ -167,7 +102,8 @@ public class ActiveOperationalDatasetTest { @Test public void builder_setUnknownTlvs_success() { - ActiveOperationalDataset dataset1 = ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET); + ActiveOperationalDataset dataset1 = + ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS); SparseArray unknownTlvs = new SparseArray<>(2); unknownTlvs.put(0x33, new byte[] {1, 2, 3}); unknownTlvs.put(0x44, new byte[] {1, 2, 3, 4});