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});