Merge "[Thread] move createRandomDataset to ThreadNetworkController" into main

This commit is contained in:
Kangping Dong 2023-11-09 02:05:04 +00:00 • committed by Gerrit Code Review
commit 7cca114efc
9 changed files with 362 additions and 284 deletions

View file

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

View file

@ -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.
*
* <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 @@ public final class ActiveOperationalDataset implements Parcelable {
/** @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 @@ public final class ActiveOperationalDataset implements Parcelable {
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 @@ 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.
*
* <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 @@ public final class ActiveOperationalDataset implements Parcelable {
*
* <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 @@ public final class ActiveOperationalDataset implements Parcelable {
*
* <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 @@ 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,

View file

@ -40,4 +40,5 @@ interface IThreadNetworkController {
void leave(in IOperationReceiver receiver);
int getThreadVersion();
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
}

View file

@ -113,6 +113,33 @@ public final class ThreadNetworkController {
}
}
/**
* 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 @@ 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<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;

View file

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

View file

@ -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<byte[]> 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<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 @@ 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();

View file

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

View file

@ -132,6 +132,13 @@ public class ThreadNetworkControllerTest {
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 @@ public class ThreadNetworkControllerTest {
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 @@ 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<Void> joinFuture = SettableFuture.create();
SettableFuture<Void> 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<Void> joinFuture = SettableFuture.create();
SettableFuture<Void> leaveFuture1 = SettableFuture.create();
SettableFuture<Void> 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<Void> 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);
}
}
}

View file

@ -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<byte[]> unknownTlvs = new SparseArray<>(2);
unknownTlvs.put(0x33, new byte[] {1, 2, 3});
unknownTlvs.put(0x44, new byte[] {1, 2, 3, 4});