Merge "[Thread] add Thread Operational Dataset API" into main
This commit is contained in:
@@ -417,6 +417,81 @@ 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();
|
||||
method @IntRange(from=0, to=65535) public int getChannel();
|
||||
method @NonNull @Size(min=1) public android.util.SparseArray<byte[]> getChannelMask();
|
||||
method @IntRange(from=0, to=255) public int getChannelPage();
|
||||
method @NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_EXTENDED_PAN_ID) public byte[] getExtendedPanId();
|
||||
method @NonNull public android.net.IpPrefix getMeshLocalPrefix();
|
||||
method @NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_NETWORK_KEY) public byte[] getNetworkKey();
|
||||
method @NonNull @Size(min=android.net.thread.ActiveOperationalDataset.LENGTH_MIN_NETWORK_NAME_BYTES, max=android.net.thread.ActiveOperationalDataset.LENGTH_MAX_NETWORK_NAME_BYTES) public String getNetworkName();
|
||||
method @IntRange(from=0, to=65534) public int getPanId();
|
||||
method @NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_PSKC) public byte[] getPskc();
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.SecurityPolicy getSecurityPolicy();
|
||||
method @NonNull public byte[] toThreadTlvs();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field public static final int CHANNEL_MAX_24_GHZ = 26; // 0x1a
|
||||
field public static final int CHANNEL_MIN_24_GHZ = 11; // 0xb
|
||||
field public static final int CHANNEL_PAGE_24_GHZ = 0; // 0x0
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.net.thread.ActiveOperationalDataset> CREATOR;
|
||||
field public static final int LENGTH_EXTENDED_PAN_ID = 8; // 0x8
|
||||
field public static final int LENGTH_MAX_DATASET_TLVS = 254; // 0xfe
|
||||
field public static final int LENGTH_MAX_NETWORK_NAME_BYTES = 16; // 0x10
|
||||
field public static final int LENGTH_MESH_LOCAL_PREFIX_BITS = 64; // 0x40
|
||||
field public static final int LENGTH_MIN_NETWORK_NAME_BYTES = 1; // 0x1
|
||||
field public static final int LENGTH_NETWORK_KEY = 16; // 0x10
|
||||
field public static final int LENGTH_PSKC = 16; // 0x10
|
||||
}
|
||||
|
||||
public static final class ActiveOperationalDataset.Builder {
|
||||
ctor public ActiveOperationalDataset.Builder(@NonNull android.net.thread.ActiveOperationalDataset);
|
||||
ctor public ActiveOperationalDataset.Builder();
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset build();
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setActiveTimestamp(@NonNull android.net.thread.OperationalDatasetTimestamp);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setChannel(@IntRange(from=0, to=255) int, @IntRange(from=0, to=65535) int);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setChannelMask(@NonNull @Size(min=1) android.util.SparseArray<byte[]>);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setExtendedPanId(@NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_EXTENDED_PAN_ID) byte[]);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setMeshLocalPrefix(@NonNull android.net.IpPrefix);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setNetworkKey(@NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_NETWORK_KEY) byte[]);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setNetworkName(@NonNull @Size(min=android.net.thread.ActiveOperationalDataset.LENGTH_MIN_NETWORK_NAME_BYTES, max=android.net.thread.ActiveOperationalDataset.LENGTH_MAX_NETWORK_NAME_BYTES) String);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setPanId(@IntRange(from=0, to=65534) int);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setPskc(@NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_PSKC) byte[]);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setSecurityPolicy(@NonNull android.net.thread.ActiveOperationalDataset.SecurityPolicy);
|
||||
}
|
||||
|
||||
public static final class ActiveOperationalDataset.SecurityPolicy {
|
||||
ctor public ActiveOperationalDataset.SecurityPolicy(@IntRange(from=1, to=65535) int, @NonNull @Size(min=android.net.thread.ActiveOperationalDataset.SecurityPolicy.LENGTH_MIN_SECURITY_POLICY_FLAGS) byte[]);
|
||||
method @NonNull @Size(min=android.net.thread.ActiveOperationalDataset.SecurityPolicy.LENGTH_MIN_SECURITY_POLICY_FLAGS) public byte[] getFlags();
|
||||
method @IntRange(from=1, to=65535) public int getRotationTimeHours();
|
||||
field public static final int DEFAULT_ROTATION_TIME_HOURS = 672; // 0x2a0
|
||||
field public static final int LENGTH_MIN_SECURITY_POLICY_FLAGS = 1; // 0x1
|
||||
}
|
||||
|
||||
@FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class OperationalDatasetTimestamp {
|
||||
ctor public OperationalDatasetTimestamp(@IntRange(from=0, to=281474976710655L) long, @IntRange(from=0, to=32767) int, boolean);
|
||||
method @NonNull public static android.net.thread.OperationalDatasetTimestamp fromInstant(@NonNull java.time.Instant);
|
||||
method @IntRange(from=0, to=281474976710655L) public long getSeconds();
|
||||
method @IntRange(from=0, to=32767) public int getTicks();
|
||||
method public boolean isAuthoritativeSource();
|
||||
method @NonNull public java.time.Instant toInstant();
|
||||
}
|
||||
|
||||
@FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class PendingOperationalDataset implements android.os.Parcelable {
|
||||
ctor public PendingOperationalDataset(@NonNull android.net.thread.ActiveOperationalDataset, @NonNull android.net.thread.OperationalDatasetTimestamp, @NonNull java.time.Duration);
|
||||
method public int describeContents();
|
||||
method @NonNull public static android.net.thread.PendingOperationalDataset fromThreadTlvs(@NonNull byte[]);
|
||||
method @NonNull public android.net.thread.ActiveOperationalDataset getActiveOperationalDataset();
|
||||
method @NonNull public java.time.Duration getDelayTimer();
|
||||
method @NonNull public android.net.thread.OperationalDatasetTimestamp getPendingTimestamp();
|
||||
method @NonNull public byte[] toThreadTlvs();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.net.thread.PendingOperationalDataset> CREATOR;
|
||||
}
|
||||
|
||||
@FlaggedApi("com.android.net.thread.flags.thread_enabled") public class ThreadNetworkController {
|
||||
method public int getThreadVersion();
|
||||
field public static final int THREAD_VERSION_1_3 = 4; // 0x4
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.thread;
|
||||
|
||||
parcelable ActiveOperationalDataset;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.thread;
|
||||
|
||||
import static com.android.internal.util.Preconditions.checkArgument;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.FlaggedApi;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The timestamp of Thread Operational Dataset.
|
||||
*
|
||||
* @see ActiveOperationalDataset
|
||||
* @see PendingOperationalDataset
|
||||
* @hide
|
||||
*/
|
||||
@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED)
|
||||
@SystemApi
|
||||
public final class OperationalDatasetTimestamp {
|
||||
/** @hide */
|
||||
public static final int LENGTH_TIMESTAMP = Long.BYTES;
|
||||
|
||||
private static final long TICKS_UPPER_BOUND = 0x8000;
|
||||
|
||||
private final Instant mInstant;
|
||||
private final boolean mIsAuthoritativeSource;
|
||||
|
||||
/**
|
||||
* Creates a new {@link OperationalDatasetTimestamp} object from an {@link Instant}.
|
||||
*
|
||||
* <p>The {@code seconds} is set to {@code instant.getEpochSecond()}, {@code ticks} is set to
|
||||
* {@link instant#getNano()} based on frequency of 32768 Hz, and {@code isAuthoritativeSource}
|
||||
* is set to {@code true}.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code instant.getEpochSecond()} is larger than {@code
|
||||
* 0xffffffffffffL}
|
||||
*/
|
||||
@NonNull
|
||||
public static OperationalDatasetTimestamp fromInstant(@NonNull Instant instant) {
|
||||
return new OperationalDatasetTimestamp(instant, /* isAuthoritativeSource= */ true);
|
||||
}
|
||||
|
||||
/** Converts this {@link OperationalDatasetTimestamp} object to an {@link Instant}. */
|
||||
@NonNull
|
||||
public Instant toInstant() {
|
||||
return mInstant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link OperationalDatasetTimestamp} object from the OperationalDatasetTimestamp
|
||||
* TLV value.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public static OperationalDatasetTimestamp fromTlvValue(@NonNull byte[] encodedTimestamp) {
|
||||
requireNonNull(encodedTimestamp, "encodedTimestamp cannot be null");
|
||||
checkArgument(
|
||||
encodedTimestamp.length == LENGTH_TIMESTAMP,
|
||||
"Invalid Thread OperationalDatasetTimestamp length (length = %d,"
|
||||
+ " expectedLength=%d)",
|
||||
encodedTimestamp.length,
|
||||
LENGTH_TIMESTAMP);
|
||||
long longTimestamp = ByteBuffer.wrap(encodedTimestamp).getLong();
|
||||
return new OperationalDatasetTimestamp(
|
||||
(longTimestamp >> 16) & 0x0000ffffffffffffL,
|
||||
(int) ((longTimestamp >> 1) & 0x7fffL),
|
||||
(longTimestamp & 0x01) != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this {@link OperationalDatasetTimestamp} object to Thread TLV value.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public byte[] toTlvValue() {
|
||||
byte[] tlv = new byte[LENGTH_TIMESTAMP];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(tlv);
|
||||
long encodedValue =
|
||||
(mInstant.getEpochSecond() << 16)
|
||||
| ((mInstant.getNano() * TICKS_UPPER_BOUND / 1000000000L) << 1)
|
||||
| (mIsAuthoritativeSource ? 1 : 0);
|
||||
buffer.putLong(encodedValue);
|
||||
return tlv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link OperationalDatasetTimestamp} object.
|
||||
*
|
||||
* @param seconds the value encodes a Unix Time value. Must be in the range of
|
||||
* 0x0-0xffffffffffffL
|
||||
* @param ticks the value encodes the fractional Unix Time value in 32.768 kHz resolution. Must
|
||||
* be in the range of 0x0-0x7fff
|
||||
* @param isAuthoritativeSource the flag indicates the time was obtained from an authoritative
|
||||
* source: either NTP (Network Time Protocol), GPS (Global Positioning System), cell
|
||||
* network, or other method
|
||||
* @throws IllegalArgumentException if the {@code seconds} is not in range of
|
||||
* 0x0-0xffffffffffffL or {@code ticks} is not in range of 0x0-0x7fff
|
||||
*/
|
||||
public OperationalDatasetTimestamp(
|
||||
@IntRange(from = 0x0, to = 0xffffffffffffL) long seconds,
|
||||
@IntRange(from = 0x0, to = 0x7fff) int ticks,
|
||||
boolean isAuthoritativeSource) {
|
||||
this(makeInstant(seconds, ticks), isAuthoritativeSource);
|
||||
}
|
||||
|
||||
private static Instant makeInstant(long seconds, int ticks) {
|
||||
checkArgument(
|
||||
seconds >= 0 && seconds <= 0xffffffffffffL,
|
||||
"seconds exceeds allowed range (seconds = %d,"
|
||||
+ " allowedRange = [0x0, 0xffffffffffffL])",
|
||||
seconds);
|
||||
checkArgument(
|
||||
ticks >= 0 && ticks <= 0x7fff,
|
||||
"ticks exceeds allowed ranged (ticks = %d, allowedRange" + " = [0x0, 0x7fff])",
|
||||
ticks);
|
||||
long nanos = Math.round((double) ticks * 1000000000L / TICKS_UPPER_BOUND);
|
||||
return Instant.ofEpochSecond(seconds, nanos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link OperationalDatasetTimestamp} object.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code instant.getEpochSecond()} is larger than {@code
|
||||
* 0xffffffffffffL}
|
||||
*/
|
||||
private OperationalDatasetTimestamp(@NonNull Instant instant, boolean isAuthoritativeSource) {
|
||||
requireNonNull(instant, "instant cannot be null");
|
||||
long seconds = instant.getEpochSecond();
|
||||
checkArgument(
|
||||
seconds >= 0 && seconds <= 0xffffffffffffL,
|
||||
"instant seconds exceeds allowed range (seconds = %d, allowedRange = [0x0,"
|
||||
+ " 0xffffffffffffL])",
|
||||
seconds);
|
||||
mInstant = instant;
|
||||
mIsAuthoritativeSource = isAuthoritativeSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rounded ticks converted from the nano seconds.
|
||||
*
|
||||
* <p>Note that rhe return value can be as large as {@code TICKS_UPPER_BOUND}.
|
||||
*/
|
||||
private static int getRoundedTicks(long nanos) {
|
||||
return (int) Math.round((double) nanos * TICKS_UPPER_BOUND / 1000000000L);
|
||||
}
|
||||
|
||||
/** Returns the seconds portion of the timestamp. */
|
||||
public @IntRange(from = 0x0, to = 0xffffffffffffL) long getSeconds() {
|
||||
return mInstant.getEpochSecond() + getRoundedTicks(mInstant.getNano()) / TICKS_UPPER_BOUND;
|
||||
}
|
||||
|
||||
/** Returns the ticks portion of the timestamp. */
|
||||
public @IntRange(from = 0x0, to = 0x7fff) int getTicks() {
|
||||
// the rounded ticks can be 0x8000 if mInstant.getNano() >= 999984742
|
||||
return (int) (getRoundedTicks(mInstant.getNano()) % TICKS_UPPER_BOUND);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if the timestamp comes from an authoritative source. */
|
||||
public boolean isAuthoritativeSource() {
|
||||
return mIsAuthoritativeSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{seconds=")
|
||||
.append(getSeconds())
|
||||
.append(", ticks=")
|
||||
.append(getTicks())
|
||||
.append(", isAuthoritativeSource=")
|
||||
.append(isAuthoritativeSource())
|
||||
.append(", instant=")
|
||||
.append(toInstant())
|
||||
.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
} else if (!(other instanceof OperationalDatasetTimestamp)) {
|
||||
return false;
|
||||
} else {
|
||||
OperationalDatasetTimestamp otherTimestamp = (OperationalDatasetTimestamp) other;
|
||||
return mInstant.equals(otherTimestamp.mInstant)
|
||||
&& mIsAuthoritativeSource == otherTimestamp.mIsAuthoritativeSource;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mInstant, mIsAuthoritativeSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.thread;
|
||||
|
||||
parcelable PendingOperationalDataset;
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.thread;
|
||||
|
||||
import static com.android.internal.util.Preconditions.checkArgument;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.FlaggedApi;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Data interface for managing a Thread Pending Operational Dataset.
|
||||
*
|
||||
* <p>The Pending Operational Dataset represents an Operational Dataset which will become Active in
|
||||
* a given delay. This is typically used to deploy new network parameters (e.g. Network Key or
|
||||
* Channel) to all devices in the network.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED)
|
||||
@SystemApi
|
||||
public final class PendingOperationalDataset implements Parcelable {
|
||||
// Value defined in Thread spec 8.10.1.16
|
||||
private static final int TYPE_PENDING_TIMESTAMP = 51;
|
||||
|
||||
// Values defined in Thread spec 8.10.1.17
|
||||
private static final int TYPE_DELAY_TIMER = 52;
|
||||
private static final int LENGTH_DELAY_TIMER_BYTES = 4;
|
||||
|
||||
@NonNull
|
||||
public static final Creator<PendingOperationalDataset> CREATOR =
|
||||
new Creator<>() {
|
||||
@Override
|
||||
public PendingOperationalDataset createFromParcel(Parcel in) {
|
||||
return PendingOperationalDataset.fromThreadTlvs(in.createByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingOperationalDataset[] newArray(int size) {
|
||||
return new PendingOperationalDataset[size];
|
||||
}
|
||||
};
|
||||
|
||||
@NonNull private final ActiveOperationalDataset mActiveOpDataset;
|
||||
@NonNull private final OperationalDatasetTimestamp mPendingTimestamp;
|
||||
@NonNull private final Duration mDelayTimer;
|
||||
|
||||
/** Creates a new {@link PendingOperationalDataset} object. */
|
||||
public PendingOperationalDataset(
|
||||
@NonNull ActiveOperationalDataset activeOpDataset,
|
||||
@NonNull OperationalDatasetTimestamp pendingTimestamp,
|
||||
@NonNull Duration delayTimer) {
|
||||
requireNonNull(activeOpDataset, "activeOpDataset cannot be null");
|
||||
requireNonNull(pendingTimestamp, "pendingTimestamp cannot be null");
|
||||
requireNonNull(delayTimer, "delayTimer cannot be null");
|
||||
this.mActiveOpDataset = activeOpDataset;
|
||||
this.mPendingTimestamp = pendingTimestamp;
|
||||
this.mDelayTimer = delayTimer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PendingOperationalDataset} object from a series of Thread TLVs.
|
||||
*
|
||||
* <p>{@code tlvs} can be obtained from the value of a Thread Pending Operational Dataset TLV
|
||||
* (see the <a href="https://www.threadgroup.org/support#specifications">Thread
|
||||
* specification</a> for the definition) or the return value of {@link #toThreadTlvs}.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code tlvs} is malformed or contains an invalid Thread
|
||||
* TLV
|
||||
*/
|
||||
@NonNull
|
||||
public static PendingOperationalDataset fromThreadTlvs(@NonNull byte[] tlvs) {
|
||||
requireNonNull(tlvs, "tlvs cannot be null");
|
||||
|
||||
SparseArray<byte[]> newUnknownTlvs = new SparseArray<>();
|
||||
OperationalDatasetTimestamp pendingTimestamp = null;
|
||||
Duration delayTimer = null;
|
||||
ActiveOperationalDataset activeDataset = ActiveOperationalDataset.fromThreadTlvs(tlvs);
|
||||
SparseArray<byte[]> unknownTlvs = activeDataset.getUnknownTlvs();
|
||||
for (int i = 0; i < unknownTlvs.size(); i++) {
|
||||
int key = unknownTlvs.keyAt(i);
|
||||
byte[] value = unknownTlvs.valueAt(i);
|
||||
switch (key) {
|
||||
case TYPE_PENDING_TIMESTAMP:
|
||||
pendingTimestamp = OperationalDatasetTimestamp.fromTlvValue(value);
|
||||
break;
|
||||
case TYPE_DELAY_TIMER:
|
||||
checkArgument(
|
||||
value.length == LENGTH_DELAY_TIMER_BYTES,
|
||||
"Invalid delay timer (length = %d, expectedLength = %d)",
|
||||
value.length,
|
||||
LENGTH_DELAY_TIMER_BYTES);
|
||||
int millis = ByteBuffer.wrap(value).getInt();
|
||||
delayTimer = Duration.ofMillis(Integer.toUnsignedLong(millis));
|
||||
break;
|
||||
default:
|
||||
newUnknownTlvs.put(key, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingTimestamp == null) {
|
||||
throw new IllegalArgumentException("Pending Timestamp is missing");
|
||||
}
|
||||
if (delayTimer == null) {
|
||||
throw new IllegalArgumentException("Delay Timer is missing");
|
||||
}
|
||||
|
||||
activeDataset =
|
||||
new ActiveOperationalDataset.Builder(activeDataset)
|
||||
.setUnknownTlvs(newUnknownTlvs)
|
||||
.build();
|
||||
return new PendingOperationalDataset(activeDataset, pendingTimestamp, delayTimer);
|
||||
}
|
||||
|
||||
/** Returns the Active Operational Dataset. */
|
||||
@NonNull
|
||||
public ActiveOperationalDataset getActiveOperationalDataset() {
|
||||
return mActiveOpDataset;
|
||||
}
|
||||
|
||||
/** Returns the Pending Timestamp. */
|
||||
@NonNull
|
||||
public OperationalDatasetTimestamp getPendingTimestamp() {
|
||||
return mPendingTimestamp;
|
||||
}
|
||||
|
||||
/** Returns the Delay Timer. */
|
||||
@NonNull
|
||||
public Duration getDelayTimer() {
|
||||
return mDelayTimer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this {@link PendingOperationalDataset} object to a series of Thread TLVs.
|
||||
*
|
||||
* <p>See the <a href="https://www.threadgroup.org/support#specifications">Thread
|
||||
* specification</a> for the definition of the Thread TLV format.
|
||||
*/
|
||||
@NonNull
|
||||
public byte[] toThreadTlvs() {
|
||||
ByteArrayOutputStream dataset = new ByteArrayOutputStream();
|
||||
|
||||
byte[] activeDatasetBytes = mActiveOpDataset.toThreadTlvs();
|
||||
dataset.write(activeDatasetBytes, 0, activeDatasetBytes.length);
|
||||
|
||||
dataset.write(TYPE_PENDING_TIMESTAMP);
|
||||
byte[] pendingTimestampBytes = mPendingTimestamp.toTlvValue();
|
||||
dataset.write(pendingTimestampBytes.length);
|
||||
dataset.write(pendingTimestampBytes, 0, pendingTimestampBytes.length);
|
||||
|
||||
dataset.write(TYPE_DELAY_TIMER);
|
||||
byte[] delayTimerBytes = new byte[LENGTH_DELAY_TIMER_BYTES];
|
||||
ByteBuffer.wrap(delayTimerBytes).putInt((int) mDelayTimer.toMillis());
|
||||
dataset.write(delayTimerBytes.length);
|
||||
dataset.write(delayTimerBytes, 0, delayTimerBytes.length);
|
||||
|
||||
return dataset.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
} else if (!(other instanceof PendingOperationalDataset)) {
|
||||
return false;
|
||||
} else {
|
||||
PendingOperationalDataset otherDataset = (PendingOperationalDataset) other;
|
||||
return mActiveOpDataset.equals(otherDataset.mActiveOpDataset)
|
||||
&& mPendingTimestamp.equals(otherDataset.mPendingTimestamp)
|
||||
&& mDelayTimer.equals(otherDataset.mDelayTimer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mActiveOpDataset, mPendingTimestamp, mDelayTimer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{activeDataset=")
|
||||
.append(getActiveOperationalDataset())
|
||||
.append(", pendingTimestamp=")
|
||||
.append(getPendingTimestamp())
|
||||
.append(", delayTimer=")
|
||||
.append(getDelayTimer())
|
||||
.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeByteArray(toThreadTlvs());
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ android_test {
|
||||
"androidx.test.ext.junit",
|
||||
"compatibility-device-util-axt",
|
||||
"ctstestrunner-axt",
|
||||
"guava-android-testlib",
|
||||
"net-tests-utils",
|
||||
"truth",
|
||||
],
|
||||
|
||||
@@ -47,5 +47,7 @@
|
||||
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="android.net.thread.cts" />
|
||||
<!-- Ignores tests introduced by guava-android-testlib -->
|
||||
<option name="exclude-annotation" value="org.junit.Ignore"/>
|
||||
</test>
|
||||
</configuration>
|
||||
|
||||
@@ -0,0 +1,737 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.thread.cts;
|
||||
|
||||
import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
|
||||
|
||||
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
|
||||
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.net.IpPrefix;
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.ActiveOperationalDataset.Builder;
|
||||
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
|
||||
import android.net.thread.OperationalDatasetTimestamp;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.testing.EqualsTester;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** CTS tests for {@link ActiveOperationalDataset}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class ActiveOperationalDatasetTest {
|
||||
private static final int TYPE_ACTIVE_TIMESTAMP = 14;
|
||||
private static final int TYPE_CHANNEL = 0;
|
||||
private static final int TYPE_CHANNEL_MASK = 53;
|
||||
private static final int TYPE_EXTENDED_PAN_ID = 2;
|
||||
private static final int TYPE_MESH_LOCAL_PREFIX = 7;
|
||||
private static final int TYPE_NETWORK_KEY = 5;
|
||||
private static final int TYPE_NETWORK_NAME = 3;
|
||||
private static final int TYPE_PAN_ID = 1;
|
||||
private static final int TYPE_PSKC = 4;
|
||||
private static final int TYPE_SECURITY_POLICY = 12;
|
||||
|
||||
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
|
||||
// Active Timestamp: 1
|
||||
// Channel: 19
|
||||
// Channel Mask: 0x07FFF800
|
||||
// Ext PAN ID: ACC214689BC40BDF
|
||||
// Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
|
||||
// Network Key: F26B3153760F519A63BAFDDFFC80D2AF
|
||||
// Network Name: OpenThread-d9a0
|
||||
// PAN ID: 0xD9A0
|
||||
// PSKc: A245479C836D551B9CA557F7B9D351B4
|
||||
// Security Policy: 672 onrcb
|
||||
private static final byte[] VALID_DATASET =
|
||||
base16().decode(
|
||||
"0E080000000000010000000300001335060004001FFFE002"
|
||||
+ "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
|
||||
+ "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
|
||||
+ "642D643961300102D9A00410A245479C836D551B9CA557F7"
|
||||
+ "B9D351B40C0402A0FFF8");
|
||||
|
||||
private static byte[] removeTlv(byte[] dataset, int type) {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(dataset.length);
|
||||
int i = 0;
|
||||
while (i < dataset.length) {
|
||||
int ty = dataset[i++] & 0xff;
|
||||
byte length = dataset[i++];
|
||||
if (ty != type) {
|
||||
byte[] value = Arrays.copyOfRange(dataset, i, i + length);
|
||||
os.write(ty);
|
||||
os.write(length);
|
||||
os.writeBytes(value);
|
||||
}
|
||||
i += length;
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
private static byte[] addTlv(byte[] dataset, String tlvHex) {
|
||||
return Bytes.concat(dataset, base16().decode(tlvHex));
|
||||
}
|
||||
|
||||
private static byte[] replaceTlv(byte[] dataset, int type, String newTlvHex) {
|
||||
return addTlv(removeTlv(dataset, type), newTlvHex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parcelable_parcelingIsLossLess() {
|
||||
ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET);
|
||||
|
||||
assertParcelingIsLossless(dataset);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_tooLongTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = new byte[255];
|
||||
invalidTlv[0] = (byte) 0xff;
|
||||
|
||||
// This is invalid because the TLV has max total length of 254 bytes and the value length
|
||||
// can't exceeds 252 ( = 254 - 1 - 1)
|
||||
invalidTlv[1] = (byte) 253;
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalidNetworkKeyTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_NETWORK_KEY, "05080000000000000000");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noNetworkKeyTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_NETWORK_KEY);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalidActiveTimestampTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_ACTIVE_TIMESTAMP, "0E0700000000010000");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noActiveTimestampTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_ACTIVE_TIMESTAMP);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalidNetworkNameTlv_emptyName_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_NETWORK_NAME, "0300");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalidNetworkNameTlv_tooLongName_throwsIllegalArgument() {
|
||||
byte[] invalidTlv =
|
||||
replaceTlv(
|
||||
VALID_DATASET, TYPE_NETWORK_NAME, "03114142434445464748494A4B4C4D4E4F5051");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noNetworkNameTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_NETWORK_NAME);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalidChannelTlv_channelMissing_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "000100");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_undefinedChannelPage_success() {
|
||||
byte[] datasetTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "0003010020");
|
||||
|
||||
ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs(datasetTlv);
|
||||
|
||||
assertThat(dataset.getChannelPage()).isEqualTo(0x01);
|
||||
assertThat(dataset.getChannel()).isEqualTo(0x20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalid2P4GhzChannel_throwsIllegalArgument() {
|
||||
byte[] invalidTlv1 = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "000300000A");
|
||||
byte[] invalidTlv2 = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "000300001B");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv1));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_valid2P4GhzChannelTlv_success() {
|
||||
byte[] validTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL, "0003000010");
|
||||
|
||||
ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs(validTlv);
|
||||
|
||||
assertThat(dataset.getChannel()).isEqualTo(16);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noChannelTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_CHANNEL);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_prematureEndOfChannelMaskEntry_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL_MASK, "350100");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_inconsistentChannelMaskLength_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_CHANNEL_MASK, "3506000500010000");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_unsupportedChannelMaskLength_success() {
|
||||
ActiveOperationalDataset dataset =
|
||||
ActiveOperationalDataset.fromThreadTlvs(
|
||||
replaceTlv(VALID_DATASET, TYPE_CHANNEL_MASK, "350700050001000000"));
|
||||
|
||||
SparseArray<byte[]> channelMask = dataset.getChannelMask();
|
||||
assertThat(channelMask.size()).isEqualTo(1);
|
||||
assertThat(channelMask.get(CHANNEL_PAGE_24_GHZ))
|
||||
.isEqualTo(new byte[] {0x00, 0x01, 0x00, 0x00, 0x00});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noChannelMaskTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_CHANNEL_MASK);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalidPanIdTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_PAN_ID, "010101");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noPanIdTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_PAN_ID);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalidExtendedPanIdTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_EXTENDED_PAN_ID, "020700010203040506");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noExtendedPanIdTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_EXTENDED_PAN_ID);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalidPskcTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv =
|
||||
replaceTlv(VALID_DATASET, TYPE_PSKC, "0411000102030405060708090A0B0C0D0E0F10");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noPskcTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_PSKC);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_invalidMeshLocalPrefixTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv =
|
||||
replaceTlv(VALID_DATASET, TYPE_MESH_LOCAL_PREFIX, "0709FD0001020304050607");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noMeshLocalPrefixTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_MESH_LOCAL_PREFIX);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_tooShortSecurityPolicyTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = replaceTlv(VALID_DATASET, TYPE_SECURITY_POLICY, "0C0101");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_noSecurityPolicyTlv_throwsIllegalArgument() {
|
||||
byte[] invalidTlv = removeTlv(VALID_DATASET, TYPE_SECURITY_POLICY);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(invalidTlv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_lengthAndDataMissing_throwsIllegalArgument() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(new byte[] {(byte) 0x00}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_prematureEndOfData_throwsIllegalArgument() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ActiveOperationalDataset.fromThreadTlvs(new byte[] {0x00, 0x03, 0x00, 0x00}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_validFullDataset_success() {
|
||||
// A valid Thread active operational dataset:
|
||||
// Active Timestamp: 1
|
||||
// Channel: 19
|
||||
// Channel Mask: 0x07FFF800
|
||||
// Ext PAN ID: ACC214689BC40BDF
|
||||
// Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
|
||||
// Network Key: F26B3153760F519A63BAFDDFFC80D2AF
|
||||
// Network Name: OpenThread-d9a0
|
||||
// PAN ID: 0xD9A0
|
||||
// PSKc: A245479C836D551B9CA557F7B9D351B4
|
||||
// Security Policy: 672 onrcb
|
||||
byte[] validDatasetTlv =
|
||||
base16().decode(
|
||||
"0E080000000000010000000300001335060004001FFFE002"
|
||||
+ "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
|
||||
+ "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
|
||||
+ "642D643961300102D9A00410A245479C836D551B9CA557F7"
|
||||
+ "B9D351B40C0402A0FFF8");
|
||||
|
||||
ActiveOperationalDataset dataset = ActiveOperationalDataset.fromThreadTlvs(validDatasetTlv);
|
||||
|
||||
assertThat(dataset.getNetworkKey())
|
||||
.isEqualTo(base16().decode("F26B3153760F519A63BAFDDFFC80D2AF"));
|
||||
assertThat(dataset.getPanId()).isEqualTo(0xd9a0);
|
||||
assertThat(dataset.getExtendedPanId()).isEqualTo(base16().decode("ACC214689BC40BDF"));
|
||||
assertThat(dataset.getChannel()).isEqualTo(19);
|
||||
assertThat(dataset.getNetworkName()).isEqualTo("OpenThread-d9a0");
|
||||
assertThat(dataset.getPskc())
|
||||
.isEqualTo(base16().decode("A245479C836D551B9CA557F7B9D351B4"));
|
||||
assertThat(dataset.getActiveTimestamp())
|
||||
.isEqualTo(new OperationalDatasetTimestamp(1, 0, false));
|
||||
SparseArray<byte[]> channelMask = dataset.getChannelMask();
|
||||
assertThat(channelMask.size()).isEqualTo(1);
|
||||
assertThat(channelMask.get(CHANNEL_PAGE_24_GHZ))
|
||||
.isEqualTo(new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0});
|
||||
assertThat(dataset.getMeshLocalPrefix())
|
||||
.isEqualTo(new IpPrefix("fd64:db12:25f4:7e0b::/64"));
|
||||
assertThat(dataset.getSecurityPolicy())
|
||||
.isEqualTo(new SecurityPolicy(672, new byte[] {(byte) 0xff, (byte) 0xf8}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_containsUnknownTlvs_unknownTlvsRetained() {
|
||||
final byte[] datasetWithUnknownTlvs = addTlv(VALID_DATASET, "AA01FFBB020102");
|
||||
|
||||
ActiveOperationalDataset dataset =
|
||||
ActiveOperationalDataset.fromThreadTlvs(datasetWithUnknownTlvs);
|
||||
|
||||
byte[] newDatasetTlvs = dataset.toThreadTlvs();
|
||||
String newDatasetTlvsHex = base16().encode(newDatasetTlvs);
|
||||
assertThat(newDatasetTlvs.length).isEqualTo(datasetWithUnknownTlvs.length);
|
||||
assertThat(newDatasetTlvsHex).contains("AA01FF");
|
||||
assertThat(newDatasetTlvsHex).contains("BB020102");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toThreadTlvs_conversionIsLossLess() {
|
||||
ActiveOperationalDataset dataset1 = ActiveOperationalDataset.createRandomDataset();
|
||||
|
||||
ActiveOperationalDataset dataset2 =
|
||||
ActiveOperationalDataset.fromThreadTlvs(dataset1.toThreadTlvs());
|
||||
|
||||
assertThat(dataset2).isEqualTo(dataset1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_buildWithdefaultValues_throwsIllegalState() {
|
||||
assertThrows(IllegalStateException.class, () -> new Builder().build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValidNetworkKey_success() {
|
||||
final byte[] networkKey =
|
||||
new byte[] {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
|
||||
0x0d, 0x0e, 0x0f
|
||||
};
|
||||
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setNetworkKey(networkKey)
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getNetworkKey()).isEqualTo(networkKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setInvalidNetworkKey_throwsIllegalArgument() {
|
||||
byte[] invalidNetworkKey = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> builder.setNetworkKey(invalidNetworkKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValidExtendedPanId_success() {
|
||||
byte[] extendedPanId = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
|
||||
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setExtendedPanId(extendedPanId)
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getExtendedPanId()).isEqualTo(extendedPanId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setInvalidExtendedPanId_throwsIllegalArgument() {
|
||||
byte[] extendedPanId = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setExtendedPanId(extendedPanId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValidPanId_success() {
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setPanId(0xfffe)
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getPanId()).isEqualTo(0xfffe);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setInvalidPanId_throwsIllegalArgument() {
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setPanId(0xffff));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setInvalidChannel_throwsIllegalArgument() {
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setChannel(0, 0));
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setChannel(0, 27));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValid2P4GhzChannel_success() {
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setChannel(CHANNEL_PAGE_24_GHZ, 16)
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getChannel()).isEqualTo(16);
|
||||
assertThat(dataset.getChannelPage()).isEqualTo(CHANNEL_PAGE_24_GHZ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValidNetworkName_success() {
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setNetworkName("ot-network")
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getNetworkName()).isEqualTo("ot-network");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setEmptyNetworkName_throwsIllegalArgument() {
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setNetworkName(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setTooLongNetworkName_throwsIllegalArgument() {
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> builder.setNetworkName("openthread-network"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setTooLongUtf8NetworkName_throwsIllegalArgument() {
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
// UTF-8 encoded length of "我的线程网络" is 18 bytes which exceeds the max length
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setNetworkName("我的线程网络"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValidUtf8NetworkName_success() {
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setNetworkName("我的网络")
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getNetworkName()).isEqualTo("我的网络");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValidPskc_success() {
|
||||
byte[] pskc = base16().decode("A245479C836D551B9CA557F7B9D351B4");
|
||||
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset()).setPskc(pskc).build();
|
||||
|
||||
assertThat(dataset.getPskc()).isEqualTo(pskc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setTooLongPskc_throwsIllegalArgument() {
|
||||
byte[] tooLongPskc = base16().decode("A245479C836D551B9CA557F7B9D351B400");
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
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();
|
||||
|
||||
SparseArray<byte[]> resultChannelMask = dataset.getChannelMask();
|
||||
assertThat(resultChannelMask.size()).isEqualTo(1);
|
||||
assertThat(resultChannelMask.get(0)).isEqualTo(new byte[] {0x00, 0x00, 0x01, 0x00});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setEmptyChannelMask_throwsIllegalArgument() {
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> builder.setChannelMask(new SparseArray<byte[]>()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValidActiveTimestamp_success() {
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setActiveTimestamp(
|
||||
new OperationalDatasetTimestamp(
|
||||
/* seconds= */ 1,
|
||||
/* ticks= */ 0,
|
||||
/* isAuthoritativeSource= */ true))
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getActiveTimestamp().getSeconds()).isEqualTo(1);
|
||||
assertThat(dataset.getActiveTimestamp().getTicks()).isEqualTo(0);
|
||||
assertThat(dataset.getActiveTimestamp().isAuthoritativeSource()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_wrongMeshLocalPrefixLength_throwsIllegalArguments() {
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
// The Mesh-Local Prefix length must be 64 bits
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> builder.setMeshLocalPrefix(new IpPrefix("fd00::/32")));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> builder.setMeshLocalPrefix(new IpPrefix("fd00::/96")));
|
||||
|
||||
// The Mesh-Local Prefix must start with 0xfd
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> builder.setMeshLocalPrefix(new IpPrefix("fc00::/64")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_meshLocalPrefixNotStartWith0xfd_throwsIllegalArguments() {
|
||||
Builder builder = new Builder(ActiveOperationalDataset.createRandomDataset());
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> builder.setMeshLocalPrefix(new IpPrefix("fc00::/64")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValidMeshLocalPrefix_success() {
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setMeshLocalPrefix(new IpPrefix("fd00::/64"))
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getMeshLocalPrefix()).isEqualTo(new IpPrefix("fd00::/64"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValid1P2SecurityPolicy_success() {
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setSecurityPolicy(
|
||||
new SecurityPolicy(672, new byte[] {(byte) 0xff, (byte) 0xf8}))
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getSecurityPolicy().getRotationTimeHours()).isEqualTo(672);
|
||||
assertThat(dataset.getSecurityPolicy().getFlags())
|
||||
.isEqualTo(new byte[] {(byte) 0xff, (byte) 0xf8});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setValid1P1SecurityPolicy_success() {
|
||||
ActiveOperationalDataset dataset =
|
||||
new Builder(ActiveOperationalDataset.createRandomDataset())
|
||||
.setSecurityPolicy(new SecurityPolicy(672, new byte[] {(byte) 0xff}))
|
||||
.build();
|
||||
|
||||
assertThat(dataset.getSecurityPolicy().getRotationTimeHours()).isEqualTo(672);
|
||||
assertThat(dataset.getSecurityPolicy().getFlags()).isEqualTo(new byte[] {(byte) 0xff});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityPolicy_invalidRotationTime_throwsIllegalArguments() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> new SecurityPolicy(0, new byte[] {(byte) 0xff, (byte) 0xf8}));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> new SecurityPolicy(0x1ffff, new byte[] {(byte) 0xff, (byte) 0xf8}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityPolicy_emptyFlags_throwsIllegalArguments() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new SecurityPolicy(672, new byte[] {}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityPolicy_tooLongFlags_success() {
|
||||
SecurityPolicy securityPolicy =
|
||||
new SecurityPolicy(672, new byte[] {0, 1, 2, 3, 4, 5, 6, 7});
|
||||
|
||||
assertThat(securityPolicy.getFlags()).isEqualTo(new byte[] {0, 1, 2, 3, 4, 5, 6, 7});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityPolicy_equals() {
|
||||
new EqualsTester()
|
||||
.addEqualityGroup(
|
||||
new SecurityPolicy(672, new byte[] {(byte) 0xff, (byte) 0xf8}),
|
||||
new SecurityPolicy(672, new byte[] {(byte) 0xff, (byte) 0xf8}))
|
||||
.addEqualityGroup(
|
||||
new SecurityPolicy(1, new byte[] {(byte) 0xff}),
|
||||
new SecurityPolicy(1, new byte[] {(byte) 0xff}))
|
||||
.addEqualityGroup(
|
||||
new SecurityPolicy(1, new byte[] {(byte) 0xff, (byte) 0xf8}),
|
||||
new SecurityPolicy(1, new byte[] {(byte) 0xff, (byte) 0xf8}))
|
||||
.testEquals();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.thread.cts;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.net.thread.OperationalDatasetTimestamp;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.google.common.testing.EqualsTester;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/** Tests for {@link OperationalDatasetTimestamp}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class OperationalDatasetTimestampTest {
|
||||
@Test
|
||||
public void fromInstant_tooLargeInstant_throwsIllegalArgument() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
OperationalDatasetTimestamp.fromInstant(
|
||||
Instant.ofEpochSecond(0xffffffffffffL + 1L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromInstant_ticksIsRounded() {
|
||||
Instant instant = Instant.ofEpochSecond(100L);
|
||||
|
||||
// 32767.5 / 32768 * 1000000000 = 999984741.2109375 and given the `ticks` is rounded, so
|
||||
// the `ticks` should be 32767 for 999984741 and 0 (carried over to seconds) for 999984742.
|
||||
OperationalDatasetTimestamp timestampTicks32767 =
|
||||
OperationalDatasetTimestamp.fromInstant(instant.plusNanos(999984741));
|
||||
OperationalDatasetTimestamp timestampTicks0 =
|
||||
OperationalDatasetTimestamp.fromInstant(instant.plusNanos(999984742));
|
||||
|
||||
assertThat(timestampTicks32767.getSeconds()).isEqualTo(100L);
|
||||
assertThat(timestampTicks0.getSeconds()).isEqualTo(101L);
|
||||
assertThat(timestampTicks32767.getTicks()).isEqualTo(32767);
|
||||
assertThat(timestampTicks0.getTicks()).isEqualTo(0);
|
||||
assertThat(timestampTicks32767.isAuthoritativeSource()).isTrue();
|
||||
assertThat(timestampTicks0.isAuthoritativeSource()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toInstant_nanosIsRounded() {
|
||||
// 32767 / 32768 * 1000000000 = 999969482.421875
|
||||
assertThat(new OperationalDatasetTimestamp(100L, 32767, false).toInstant().getNano())
|
||||
.isEqualTo(999969482);
|
||||
|
||||
// 32766 / 32768 * 1000000000 = 999938964.84375
|
||||
assertThat(new OperationalDatasetTimestamp(100L, 32766, false).toInstant().getNano())
|
||||
.isEqualTo(999938965);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toInstant_onlyAuthoritativeSourceDiscarded() {
|
||||
OperationalDatasetTimestamp timestamp1 =
|
||||
new OperationalDatasetTimestamp(100L, 0x7fff, false);
|
||||
|
||||
OperationalDatasetTimestamp timestamp2 =
|
||||
OperationalDatasetTimestamp.fromInstant(timestamp1.toInstant());
|
||||
|
||||
assertThat(timestamp2.getSeconds()).isEqualTo(100L);
|
||||
assertThat(timestamp2.getTicks()).isEqualTo(0x7fff);
|
||||
assertThat(timestamp2.isAuthoritativeSource()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructor_tooLargeSeconds_throwsIllegalArguments() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
new OperationalDatasetTimestamp(
|
||||
/* seconds= */ 0x0001112233445566L,
|
||||
/* ticks= */ 0,
|
||||
/* isAuthoritativeSource= */ true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructor_tooLargeTicks_throwsIllegalArguments() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
new OperationalDatasetTimestamp(
|
||||
/* seconds= */ 0x01L,
|
||||
/* ticks= */ 0x8000,
|
||||
/* isAuthoritativeSource= */ true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalityTests() {
|
||||
new EqualsTester()
|
||||
.addEqualityGroup(
|
||||
new OperationalDatasetTimestamp(100, 100, false),
|
||||
new OperationalDatasetTimestamp(100, 100, false))
|
||||
.addEqualityGroup(
|
||||
new OperationalDatasetTimestamp(0, 0, false),
|
||||
new OperationalDatasetTimestamp(0, 0, false))
|
||||
.addEqualityGroup(
|
||||
new OperationalDatasetTimestamp(0xffffffffffffL, 0x7fff, true),
|
||||
new OperationalDatasetTimestamp(0xffffffffffffL, 0x7fff, true))
|
||||
.testEquals();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.thread.cts;
|
||||
|
||||
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
|
||||
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.net.IpPrefix;
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.OperationalDatasetTimestamp;
|
||||
import android.net.thread.PendingOperationalDataset;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.testing.EqualsTester;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
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();
|
||||
|
||||
@Test
|
||||
public void parcelable_parcelingIsLossLess() {
|
||||
PendingOperationalDataset dataset =
|
||||
new PendingOperationalDataset(
|
||||
DEFAULT_ACTIVE_DATASET,
|
||||
new OperationalDatasetTimestamp(31536000, 200, false),
|
||||
Duration.ofHours(100));
|
||||
|
||||
assertParcelingIsLossless(dataset);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalityTests() {
|
||||
ActiveOperationalDataset activeDataset1 = ActiveOperationalDataset.createRandomDataset();
|
||||
ActiveOperationalDataset activeDataset2 = ActiveOperationalDataset.createRandomDataset();
|
||||
|
||||
new EqualsTester()
|
||||
.addEqualityGroup(
|
||||
new PendingOperationalDataset(
|
||||
activeDataset1,
|
||||
new OperationalDatasetTimestamp(31536000, 100, false),
|
||||
Duration.ofMillis(0)),
|
||||
new PendingOperationalDataset(
|
||||
activeDataset1,
|
||||
new OperationalDatasetTimestamp(31536000, 100, false),
|
||||
Duration.ofMillis(0)))
|
||||
.addEqualityGroup(
|
||||
new PendingOperationalDataset(
|
||||
activeDataset2,
|
||||
new OperationalDatasetTimestamp(31536000, 100, false),
|
||||
Duration.ofMillis(0)),
|
||||
new PendingOperationalDataset(
|
||||
activeDataset2,
|
||||
new OperationalDatasetTimestamp(31536000, 100, false),
|
||||
Duration.ofMillis(0)))
|
||||
.addEqualityGroup(
|
||||
new PendingOperationalDataset(
|
||||
activeDataset2,
|
||||
new OperationalDatasetTimestamp(15768000, 0, false),
|
||||
Duration.ofMillis(0)),
|
||||
new PendingOperationalDataset(
|
||||
activeDataset2,
|
||||
new OperationalDatasetTimestamp(15768000, 0, false),
|
||||
Duration.ofMillis(0)))
|
||||
.addEqualityGroup(
|
||||
new PendingOperationalDataset(
|
||||
activeDataset2,
|
||||
new OperationalDatasetTimestamp(15768000, 0, false),
|
||||
Duration.ofMillis(100)),
|
||||
new PendingOperationalDataset(
|
||||
activeDataset2,
|
||||
new OperationalDatasetTimestamp(15768000, 0, false),
|
||||
Duration.ofMillis(100)))
|
||||
.testEquals();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructor_correctValuesAreSet() {
|
||||
PendingOperationalDataset dataset =
|
||||
new PendingOperationalDataset(
|
||||
DEFAULT_ACTIVE_DATASET,
|
||||
new OperationalDatasetTimestamp(31536000, 200, false),
|
||||
Duration.ofHours(100));
|
||||
|
||||
assertThat(dataset.getActiveOperationalDataset()).isEqualTo(DEFAULT_ACTIVE_DATASET);
|
||||
assertThat(dataset.getPendingTimestamp())
|
||||
.isEqualTo(new OperationalDatasetTimestamp(31536000, 200, false));
|
||||
assertThat(dataset.getDelayTimer()).isEqualTo(Duration.ofHours(100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_openthreadTlvs_success() {
|
||||
// An example Pending Operational Dataset which is generated with OpenThread CLI:
|
||||
// Pending Timestamp: 2
|
||||
// Active Timestamp: 1
|
||||
// Channel: 26
|
||||
// Channel Mask: 0x07fff800
|
||||
// Delay: 46354
|
||||
// Ext PAN ID: a74182f4d3f4de41
|
||||
// Mesh Local Prefix: fd46:c1b9:e159:5574::/64
|
||||
// Network Key: ed916e454d96fd00184f10a6f5c9e1d3
|
||||
// Network Name: OpenThread-bff8
|
||||
// PAN ID: 0xbff8
|
||||
// PSKc: 264f78414adc683191863d968f72d1b7
|
||||
// Security Policy: 672 onrc
|
||||
final byte[] OPENTHREAD_PENDING_DATASET_TLVS =
|
||||
base16().lowerCase()
|
||||
.decode(
|
||||
"0e0800000000000100003308000000000002000034040000b51200030000"
|
||||
+ "1a35060004001fffe00208a74182f4d3f4de410708fd46c1b9"
|
||||
+ "e15955740510ed916e454d96fd00184f10a6f5c9e1d3030f4f"
|
||||
+ "70656e5468726561642d626666380102bff80410264f78414a"
|
||||
+ "dc683191863d968f72d1b70c0402a0f7f8");
|
||||
|
||||
PendingOperationalDataset pendingDataset =
|
||||
PendingOperationalDataset.fromThreadTlvs(OPENTHREAD_PENDING_DATASET_TLVS);
|
||||
|
||||
ActiveOperationalDataset activeDataset = pendingDataset.getActiveOperationalDataset();
|
||||
assertThat(pendingDataset.getPendingTimestamp().getSeconds()).isEqualTo(2L);
|
||||
assertThat(activeDataset.getActiveTimestamp().getSeconds()).isEqualTo(1L);
|
||||
assertThat(activeDataset.getChannel()).isEqualTo(26);
|
||||
assertThat(activeDataset.getChannelMask().get(0))
|
||||
.isEqualTo(new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0});
|
||||
assertThat(pendingDataset.getDelayTimer().toMillis()).isEqualTo(46354);
|
||||
assertThat(activeDataset.getExtendedPanId())
|
||||
.isEqualTo(base16().lowerCase().decode("a74182f4d3f4de41"));
|
||||
assertThat(activeDataset.getMeshLocalPrefix())
|
||||
.isEqualTo(new IpPrefix("fd46:c1b9:e159:5574::/64"));
|
||||
assertThat(activeDataset.getNetworkKey())
|
||||
.isEqualTo(base16().lowerCase().decode("ed916e454d96fd00184f10a6f5c9e1d3"));
|
||||
assertThat(activeDataset.getNetworkName()).isEqualTo("OpenThread-bff8");
|
||||
assertThat(activeDataset.getPanId()).isEqualTo(0xbff8);
|
||||
assertThat(activeDataset.getPskc())
|
||||
.isEqualTo(base16().lowerCase().decode("264f78414adc683191863d968f72d1b7"));
|
||||
assertThat(activeDataset.getSecurityPolicy().getRotationTimeHours()).isEqualTo(672);
|
||||
assertThat(activeDataset.getSecurityPolicy().getFlags())
|
||||
.isEqualTo(new byte[] {(byte) 0xf7, (byte) 0xf8});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_completePendingDatasetTlvs_success() {
|
||||
// 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());
|
||||
|
||||
PendingOperationalDataset dataset =
|
||||
PendingOperationalDataset.fromThreadTlvs(pendingDatasetTlvs);
|
||||
|
||||
assertThat(dataset.getActiveOperationalDataset()).isEqualTo(DEFAULT_ACTIVE_DATASET);
|
||||
assertThat(dataset.getPendingTimestamp())
|
||||
.isEqualTo(new OperationalDatasetTimestamp(1, 0, false));
|
||||
assertThat(dataset.getDelayTimer()).isEqualTo(Duration.ofMillis(300));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_PendingTimestampTlvIsMissing_throwsIllegalArgument() {
|
||||
// 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());
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> PendingOperationalDataset.fromThreadTlvs(pendingDatasetTlvs));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_delayTimerTlvIsMissing_throwsIllegalArgument() {
|
||||
// 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());
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> PendingOperationalDataset.fromThreadTlvs(pendingDatasetTlvs));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_activeDatasetTlvs_throwsIllegalArgument() {
|
||||
final byte[] activeDatasetTlvs = DEFAULT_ACTIVE_DATASET.toThreadTlvs();
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> PendingOperationalDataset.fromThreadTlvs(activeDatasetTlvs));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_malformedTlvs_throwsIllegalArgument() {
|
||||
final byte[] invalidTlvs = new byte[] {0x00};
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> PendingOperationalDataset.fromThreadTlvs(invalidTlvs));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toThreadTlvs_conversionIsLossLess() {
|
||||
PendingOperationalDataset dataset1 =
|
||||
new PendingOperationalDataset(
|
||||
DEFAULT_ACTIVE_DATASET,
|
||||
new OperationalDatasetTimestamp(31536000, 200, false),
|
||||
Duration.ofHours(100));
|
||||
|
||||
PendingOperationalDataset dataset2 =
|
||||
PendingOperationalDataset.fromThreadTlvs(dataset1.toThreadTlvs());
|
||||
|
||||
assertThat(dataset2).isEqualTo(dataset1);
|
||||
}
|
||||
}
|
||||
50
thread/tests/unit/Android.bp
Normal file
50
thread/tests/unit/Android.bp
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Copyright (C) 2023 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
android_test {
|
||||
name: "ThreadNetworkUnitTests",
|
||||
min_sdk_version: "33",
|
||||
sdk_version: "module_current",
|
||||
manifest: "AndroidManifest.xml",
|
||||
test_config: "AndroidTest.xml",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
],
|
||||
test_suites: [
|
||||
"general-tests",
|
||||
],
|
||||
static_libs: [
|
||||
"androidx.test.ext.junit",
|
||||
"compatibility-device-util-axt",
|
||||
"ctstestrunner-axt",
|
||||
"framework-connectivity-pre-jarjar",
|
||||
"framework-connectivity-t-pre-jarjar",
|
||||
"guava-android-testlib",
|
||||
"net-tests-utils",
|
||||
"truth-prebuilt",
|
||||
],
|
||||
libs: [
|
||||
"android.test.base",
|
||||
"android.test.runner",
|
||||
],
|
||||
// Test coverage system runs on different devices. Need to
|
||||
// compile for all architectures.
|
||||
compile_multilib: "both",
|
||||
}
|
||||
30
thread/tests/unit/AndroidManifest.xml
Normal file
30
thread/tests/unit/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android.net.thread.unittests">
|
||||
|
||||
<application android:debuggable="true">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="android.net.thread.unittests"
|
||||
android:label="Unit tests for android.net.thread" />
|
||||
</manifest>
|
||||
33
thread/tests/unit/AndroidTest.xml
Normal file
33
thread/tests/unit/AndroidTest.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<configuration description="Config for Thread network unit test cases">
|
||||
<option name="test-tag" value="ThreadNetworkUnitTests" />
|
||||
<option name="test-suite-tag" value="apct" />
|
||||
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="test-file-name" value="ThreadNetworkUnitTests.apk" />
|
||||
<option name="check-min-sdk" value="true" />
|
||||
<option name="cleanup-apks" value="true" />
|
||||
</target_preparer>
|
||||
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="android.net.thread.unittests" />
|
||||
<!-- Ignores tests introduced by guava-android-testlib -->
|
||||
<option name="exclude-annotation" value="org.junit.Ignore"/>
|
||||
</test>
|
||||
</configuration>
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.thread;
|
||||
|
||||
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;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
/** Unit tests for {@link ActiveOperationalDataset}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ActiveOperationalDatasetTest {
|
||||
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
|
||||
// Active Timestamp: 1
|
||||
// Channel: 19
|
||||
// Channel Mask: 0x07FFF800
|
||||
// Ext PAN ID: ACC214689BC40BDF
|
||||
// Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
|
||||
// Network Key: F26B3153760F519A63BAFDDFFC80D2AF
|
||||
// Network Name: OpenThread-d9a0
|
||||
// PAN ID: 0xD9A0
|
||||
// PSKc: A245479C836D551B9CA557F7B9D351B4
|
||||
// Security Policy: 672 onrcb
|
||||
private static final byte[] VALID_DATASET =
|
||||
base16().decode(
|
||||
"0E080000000000010000000300001335060004001FFFE002"
|
||||
+ "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
|
||||
+ "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
|
||||
+ "642D643961300102D9A00410A245479C836D551B9CA557F7"
|
||||
+ "B9D351B40C0402A0FFF8");
|
||||
|
||||
@Mock private Random mockRandom;
|
||||
@Mock private SecureRandom mockSecureRandom;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
private static byte[] addTlv(byte[] dataset, String tlvHex) {
|
||||
return Bytes.concat(dataset, base16().decode(tlvHex));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromThreadTlvs_containsUnknownTlvs_unknownTlvsRetained() {
|
||||
byte[] datasetWithUnknownTlvs = addTlv(VALID_DATASET, "AA01FFBB020102");
|
||||
|
||||
ActiveOperationalDataset dataset1 =
|
||||
ActiveOperationalDataset.fromThreadTlvs(datasetWithUnknownTlvs);
|
||||
ActiveOperationalDataset dataset2 =
|
||||
ActiveOperationalDataset.fromThreadTlvs(dataset1.toThreadTlvs());
|
||||
|
||||
SparseArray<byte[]> unknownTlvs = dataset2.getUnknownTlvs();
|
||||
assertThat(unknownTlvs.size()).isEqualTo(2);
|
||||
assertThat(unknownTlvs.get(0xAA)).isEqualTo(new byte[] {(byte) 0xFF});
|
||||
assertThat(unknownTlvs.get(0xBB)).isEqualTo(new byte[] {0x01, 0x02});
|
||||
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());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
builder.addUnknownTlv(i, new byte[20]);
|
||||
}
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> new Builder().build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builder_setUnknownTlvs_success() {
|
||||
ActiveOperationalDataset dataset1 = ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET);
|
||||
SparseArray<byte[]> unknownTlvs = new SparseArray<>(2);
|
||||
unknownTlvs.put(0x33, new byte[] {1, 2, 3});
|
||||
unknownTlvs.put(0x44, new byte[] {1, 2, 3, 4});
|
||||
|
||||
ActiveOperationalDataset dataset2 =
|
||||
new ActiveOperationalDataset.Builder(dataset1).setUnknownTlvs(unknownTlvs).build();
|
||||
|
||||
assertThat(dataset1.getUnknownTlvs().size()).isEqualTo(0);
|
||||
assertThat(dataset2.getUnknownTlvs().size()).isEqualTo(2);
|
||||
assertThat(dataset2.getUnknownTlvs().get(0x33)).isEqualTo(new byte[] {1, 2, 3});
|
||||
assertThat(dataset2.getUnknownTlvs().get(0x44)).isEqualTo(new byte[] {1, 2, 3, 4});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityPolicy_fromTooShortTlvValue_throwsIllegalArgument() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> SecurityPolicy.fromTlvValue(new byte[] {0x01}));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> SecurityPolicy.fromTlvValue(new byte[] {0x01, 0x02}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityPolicy_toTlvValue_conversionIsLossLess() {
|
||||
SecurityPolicy policy1 = new SecurityPolicy(200, new byte[] {(byte) 0xFF, (byte) 0xF8});
|
||||
|
||||
SecurityPolicy policy2 = SecurityPolicy.fromTlvValue(policy1.toTlvValue());
|
||||
|
||||
assertThat(policy2).isEqualTo(policy1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.thread;
|
||||
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit tests for {@link OperationalDatasetTimestamp}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class OperationalDatasetTimestampTest {
|
||||
@Test
|
||||
public void fromTlvValue_invalidTimestamp_throwsIllegalArguments() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> OperationalDatasetTimestamp.fromTlvValue(new byte[7]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromTlvValue_goodValue_success() {
|
||||
OperationalDatasetTimestamp timestamp =
|
||||
OperationalDatasetTimestamp.fromTlvValue(base16().decode("FFEEDDCCBBAA9989"));
|
||||
|
||||
assertThat(timestamp.getSeconds()).isEqualTo(0xFFEEDDCCBBAAL);
|
||||
// 0x9989 is 0x4CC4 << 1 + 1
|
||||
assertThat(timestamp.getTicks()).isEqualTo(0x4CC4);
|
||||
assertThat(timestamp.isAuthoritativeSource()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toTlvValue_conversionIsLossLess() {
|
||||
OperationalDatasetTimestamp timestamp1 = new OperationalDatasetTimestamp(100L, 10, true);
|
||||
|
||||
OperationalDatasetTimestamp timestamp2 =
|
||||
OperationalDatasetTimestamp.fromTlvValue(timestamp1.toTlvValue());
|
||||
|
||||
assertThat(timestamp2).isEqualTo(timestamp1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user