Merge "[Thread] add documentation for Pending Dataset constructor" into main
diff --git a/framework-t/api/OWNERS b/framework-t/api/OWNERS
index 607f85a..8ef735c 100644
--- a/framework-t/api/OWNERS
+++ b/framework-t/api/OWNERS
@@ -1,2 +1,3 @@
file:platform/packages/modules/Connectivity:main:/nearby/OWNERS
file:platform/packages/modules/Connectivity:main:/remoteauth/OWNERS
+file:platform/packages/modules/Connectivity:main:/thread/OWNERS
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 08c2e66..b285d85 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -418,7 +418,6 @@
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 @@
}
@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<android.net.thread.ActiveOperationalDataset,android.net.thread.ThreadNetworkException>);
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<java.lang.Void,android.net.thread.ThreadNetworkException>);
diff --git a/framework/src/android/net/LocalNetworkConfig.java b/framework/src/android/net/LocalNetworkConfig.java
index fca7fd1..e1b33e8 100644
--- a/framework/src/android/net/LocalNetworkConfig.java
+++ b/framework/src/android/net/LocalNetworkConfig.java
@@ -82,6 +82,15 @@
dest.writeParcelable(mDownstreamMulticastRoutingConfig, flags);
}
+ @Override
+ public String toString() {
+ return "LocalNetworkConfig{"
+ + "UpstreamSelector=" + mUpstreamSelector
+ + ", UpstreamMulticastConfig=" + mUpstreamMulticastRoutingConfig
+ + ", DownstreamMulticastConfig=" + mDownstreamMulticastRoutingConfig
+ + '}';
+ }
+
public static final @NonNull Creator<LocalNetworkConfig> CREATOR = new Creator<>() {
public LocalNetworkConfig createFromParcel(Parcel in) {
final NetworkRequest upstreamSelector = in.readParcelable(null);
diff --git a/framework/src/android/net/MulticastRoutingConfig.java b/framework/src/android/net/MulticastRoutingConfig.java
index ebd9fc5..6f4ab11 100644
--- a/framework/src/android/net/MulticastRoutingConfig.java
+++ b/framework/src/android/net/MulticastRoutingConfig.java
@@ -159,6 +159,24 @@
}
};
+ @Override
+ public String toString() {
+ return "MulticastRoutingConfig{"
+ + "ForwardingMode=" + forwardingModeToString(mForwardingMode)
+ + ", MinScope=" + mMinScope
+ + ", ListeningAddresses=" + mListeningAddresses
+ + '}';
+ }
+
+ private static String forwardingModeToString(final int forwardingMode) {
+ switch (forwardingMode) {
+ case FORWARD_NONE: return "NONE";
+ case FORWARD_SELECTED: return "SELECTED";
+ case FORWARD_WITH_MIN_SCOPE: return "WITH_MIN_SCOPE";
+ default: return "UNKNOWN";
+ }
+ }
+
public static class Builder {
@MulticastForwardingMode
private final int mForwardingMode;
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 47e0bd9..7d1644e 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -9006,6 +9006,9 @@
return;
}
+ if (VDBG) {
+ Log.v(TAG, "Update local network config " + nai.network.netId + " : " + newConfig);
+ }
final LocalNetworkConfig.Builder configBuilder = new LocalNetworkConfig.Builder();
// TODO : apply the diff for multicast routing.
configBuilder.setUpstreamMulticastRoutingConfig(
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
index 235f7de..1dab548 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
@@ -29,6 +29,7 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_THREAD
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
import android.net.NetworkScore
@@ -472,4 +473,55 @@
cb.expect<Lost>(localAgent.network)
cb.assertNoCallback()
}
+
+ @Test
+ fun testLocalNetworkUnwanted_withUpstream() {
+ doTestLocalNetworkUnwanted(true)
+ }
+
+ @Test
+ fun testLocalNetworkUnwanted_withoutUpstream() {
+ doTestLocalNetworkUnwanted(false)
+ }
+
+ fun doTestLocalNetworkUnwanted(haveUpstream: Boolean) {
+ deps.setBuildSdk(VERSION_V)
+
+ val nr = NetworkRequest.Builder().addCapability(NET_CAPABILITY_LOCAL_NETWORK).build()
+ val requestCb = TestableNetworkCallback()
+ cm.requestNetwork(nr, requestCb)
+ val listenCb = TestableNetworkCallback()
+ cm.registerNetworkCallback(nr, listenCb)
+
+ val upstream = if (haveUpstream) {
+ Agent(score = keepScore(), lp = lp("wifi0"),
+ nc = nc(TRANSPORT_WIFI)).also { it.connect() }
+ } else {
+ null
+ }
+
+ // Set up a local agent.
+ val lnc = LocalNetworkConfig.Builder().apply {
+ if (haveUpstream) {
+ setUpstreamSelector(NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build())
+ }
+ }.build()
+ val localAgent = Agent(nc = nc(TRANSPORT_THREAD, NET_CAPABILITY_LOCAL_NETWORK),
+ lp = lp("local0"),
+ lnc = lnc,
+ score = FromS(NetworkScore.Builder().build())
+ )
+ localAgent.connect()
+
+ requestCb.expectAvailableCallbacks(localAgent.network,
+ validated = false, upstream = upstream?.network)
+ listenCb.expectAvailableCallbacks(localAgent.network,
+ validated = false, upstream = upstream?.network)
+
+ cm.unregisterNetworkCallback(requestCb)
+
+ listenCb.expect<Lost>()
+ }
}
diff --git a/thread/framework/java/android/net/thread/ActiveOperationalDataset.java b/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
index c9b047a..b74a15a 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 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.
*
- * <p>An example usage of creating an Active Operational Dataset with random parameters:
+ * <p>An example usage of creating an Active Operational Dataset with randomized parameters:
*
* <pre>{@code
- * ActiveOperationalDataset activeDataset = ActiveOperationalDataset.createRandomDataset();
+ * ActiveOperationalDataset activeDataset = controller.createRandomizedDataset("MyNet");
* }</pre>
*
- * <p>or random Dataset with customized Network Name:
+ * <p>or randomized Dataset with customized channel:
*
* <pre>{@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();
* }</pre>
*
* <p>If the Active Operational Dataset is already known as <a
@@ -116,7 +113,9 @@
/** @hide */
@VisibleForTesting public static final int TYPE_CHANNEL_MASK = 53;
- private static final byte MESH_LOCAL_PREFIX_FIRST_BYTE = (byte) 0xfd;
+ /** @hide */
+ public static final byte MESH_LOCAL_PREFIX_FIRST_BYTE = (byte) 0xfd;
+
private static final int LENGTH_CHANNEL = 3;
private static final int LENGTH_PAN_ID = 2;
@@ -344,86 +343,6 @@
outputStream.write(entries, 0, entries.length);
}
- /**
- * Creates a new {@link ActiveOperationalDataset} object with randomized or default parameters.
- *
- * <p>The randomized (or default) value for each parameter:
- *
- * <ul>
- * <li>{@code Active Timestamp} defaults to {@code new OperationalDatasetTimestamp(1, 0,
- * false)}
- * <li>{@code Network Name} defaults to "THREAD-PAN-<PAN ID decimal>", for example
- * "THREAD-PAN-12345"
- * <li>{@code Extended PAN ID} filled with randomly generated bytes
- * <li>{@code PAN ID} randomly generated integer in range of [0, 0xfffe]
- * <li>{@code Channel Page} defaults to {@link #CHANNEL_PAGE_24_GHZ}
- * <li>{@code Channel} randomly selected channel in range of [{@link #CHANNEL_MIN_24_GHZ},
- * {@link #CHANNEL_MAX_24_GHZ}]
- * <li>{@code Channel Mask} all bits from {@link #CHANNEL_MIN_24_GHZ} to {@link
- * #CHANNEL_MAX_24_GHZ} are set to {@code true}
- * <li>{@code PSKc} filled with bytes generated by secure random generator
- * <li>{@code Network Key} filled with bytes generated by secure random generator
- * <li>{@code Mesh-local Prefix} filled with randomly generated bytes except that the first
- * byte is always set to {@code 0xfd}
- * <li>{@code Security Policy} defaults to {@code new SecurityPolicy(
- * DEFAULT_ROTATION_TIME_HOURS, new byte[]{(byte)0xff, (byte)0xf8})}. This is the default
- * values required by the Thread 1.2 specification
- * </ul>
- *
- * <p>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}.
- *
- * <p>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<byte[]> 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<byte[]> first, @NonNull SparseArray<byte[]> second) {
if (first == second) {
@@ -683,6 +602,20 @@
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 @@
* @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 @@
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.
*
- * <p>Use with caution. A randomly generated Extended PAN ID should be used for real Thread
+ * <p>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 @@
*
* <p>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 @@
*
* <p>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 @@
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 0e62b0b..51e4d88 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -40,4 +40,5 @@
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 9d6a257..ec39db4 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -113,6 +113,33 @@
}
}
+ /**
+ * Creates a new Active Operational Dataset with randomized parameters.
+ *
+ * <p>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<ActiveOperationalDataset, ThreadNetworkException> 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 @@
executor.execute(() -> receiver.onError(new ThreadNetworkException(errorCode, errorMsg)));
}
+ private static final class ActiveDatasetReceiverProxy
+ extends IActiveOperationalDatasetReceiver.Stub {
+ final Executor mExecutor;
+ final OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> mResultReceiver;
+
+ ActiveDatasetReceiverProxy(
+ @CallbackExecutor Executor executor,
+ OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> 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<Void, ThreadNetworkException> mResultReceiver;
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 3470f27..6c9a775 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.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.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 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 @@
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 @@
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<byte[]> 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 39df21b..0e76930 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 @@
// 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 @@
+ "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 @@
@Test
public void parcelable_parcelingIsLossLess() {
- ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET);
+ ActiveOperationalDataset dataset =
+ ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS);
assertParcelingIsLossless(dataset);
}
@@ -126,7 +130,8 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 void fromThreadTlvs_unsupportedChannelMaskLength_success() {
ActiveOperationalDataset dataset =
ActiveOperationalDataset.fromThreadTlvs(
- replaceTlv(VALID_DATASET, TYPE_CHANNEL_MASK, "350700050001000000"));
+ replaceTlv(VALID_DATASET_TLVS, TYPE_CHANNEL_MASK, "350700050001000000"));
SparseArray<byte[]> channelMask = dataset.getChannelMask();
assertThat(channelMask.size()).isEqualTo(1);
@@ -271,7 +279,7 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@Test
public void toThreadTlvs_conversionIsLossLess() {
- ActiveOperationalDataset dataset1 = ActiveOperationalDataset.createRandomDataset();
+ ActiveOperationalDataset dataset1 = DEFAULT_DATASET;
ActiveOperationalDataset dataset2 =
ActiveOperationalDataset.fromThreadTlvs(dataset1.toThreadTlvs());
@@ -465,9 +474,7 @@
};
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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 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 @@
@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<byte[]> channelMask = new SparseArray<byte[]>(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<byte[]> resultChannelMask = dataset.getChannelMask();
assertThat(resultChannelMask.size()).isEqualTo(1);
@@ -613,7 +608,7 @@
@Test
public void builder_setEmptyChannelMask_throwsIllegalArgument() {
- Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
+ Builder builder = new Builder();
assertThrows(
IllegalArgumentException.class,
@@ -623,7 +618,7 @@
@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 @@
@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 @@
@Test
public void builder_meshLocalPrefixNotStartWith0xfd_throwsIllegalArguments() {
- Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
+ Builder builder = new Builder();
assertThrows(
IllegalArgumentException.class,
@@ -666,9 +661,7 @@
@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 @@
@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 @@
@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 7a49957..0bb18ce 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 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 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<byte[]> 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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 cfe310c..e17dd02 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 @@
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
+ private static ActiveOperationalDataset newRandomizedDataset(
+ String networkName, ThreadNetworkController controller) throws Exception {
+ SettableFuture<ActiveOperationalDataset> 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 @@
grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = ActiveOperationalDataset.createRandomDataset();
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
SettableFuture<Void> joinFuture = SettableFuture.create();
controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
@@ -335,11 +342,11 @@
}
@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 @@
for (ThreadNetworkController controller : getAllControllers()) {
ActiveOperationalDataset activeDataset1 =
new ActiveOperationalDataset.Builder(
- ActiveOperationalDataset.createRandomDataset())
+ newRandomizedDataset("TestNet", controller))
.setNetworkKey(KEY_1)
.build();
ActiveOperationalDataset activeDataset2 =
@@ -385,7 +392,7 @@
grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = ActiveOperationalDataset.createRandomDataset();
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
SettableFuture<Void> joinFuture = SettableFuture.create();
SettableFuture<Void> leaveFuture = SettableFuture.create();
controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
@@ -413,7 +420,7 @@
grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = ActiveOperationalDataset.createRandomDataset();
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
SettableFuture<Void> joinFuture = SettableFuture.create();
SettableFuture<Void> leaveFuture1 = SettableFuture.create();
SettableFuture<Void> leaveFuture2 = SettableFuture.create();
@@ -437,7 +444,7 @@
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 @@
for (ThreadNetworkController controller : getAllControllers()) {
PendingOperationalDataset pendingDataset =
new PendingOperationalDataset(
- ActiveOperationalDataset.createRandomDataset(),
+ newRandomizedDataset("TestNet", controller),
OperationalDatasetTimestamp.fromInstant(Instant.now()),
Duration.ofSeconds(30));
SettableFuture<Void> migrateFuture = SettableFuture.create();
@@ -496,9 +503,8 @@
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 @@
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 @@
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 78eb3d0..7284968 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.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 @@
// 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 @@
@Test
public void fromThreadTlvs_containsUnknownTlvs_unknownTlvsRetained() {
- byte[] datasetWithUnknownTlvs = addTlv(VALID_DATASET, "AA01FFBB020102");
+ byte[] datasetWithUnknownTlvs = addTlv(VALID_DATASET_TLVS, "AA01FFBB020102");
ActiveOperationalDataset dataset1 =
ActiveOperationalDataset.fromThreadTlvs(datasetWithUnknownTlvs);
@@ -98,66 +91,8 @@
}
@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 @@
@Test
public void builder_setUnknownTlvs_success() {
- ActiveOperationalDataset dataset1 = ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET);
+ ActiveOperationalDataset dataset1 =
+ ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS);
SparseArray<byte[]> unknownTlvs = new SparseArray<>(2);
unknownTlvs.put(0x33, new byte[] {1, 2, 3});
unknownTlvs.put(0x44, new byte[] {1, 2, 3, 4});