1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.thread; 18 19 import static com.android.internal.util.Preconditions.checkArgument; 20 import static com.android.internal.util.Preconditions.checkState; 21 import static com.android.net.module.util.HexDump.toHexString; 22 23 import static java.nio.charset.StandardCharsets.UTF_8; 24 import static java.util.Objects.requireNonNull; 25 26 import android.annotation.FlaggedApi; 27 import android.annotation.IntRange; 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.annotation.Size; 31 import android.annotation.SystemApi; 32 import android.net.IpPrefix; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.util.SparseArray; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 39 import java.io.ByteArrayOutputStream; 40 import java.net.Inet6Address; 41 import java.net.UnknownHostException; 42 import java.util.Arrays; 43 44 /** 45 * Data interface for managing a Thread Active Operational Dataset. 46 * 47 * <p>An example usage of creating an Active Operational Dataset with randomized parameters: 48 * 49 * <pre>{@code 50 * ActiveOperationalDataset activeDataset = controller.createRandomizedDataset("MyNet"); 51 * }</pre> 52 * 53 * <p>or randomized Dataset with customized channel: 54 * 55 * <pre>{@code 56 * ActiveOperationalDataset activeDataset = 57 * new ActiveOperationalDataset.Builder(controller.createRandomizedDataset("MyNet")) 58 * .setChannel(CHANNEL_PAGE_24_GHZ, 17) 59 * .setActiveTimestamp(OperationalDatasetTimestamp.fromInstant(Instant.now())) 60 * .build(); 61 * }</pre> 62 * 63 * <p>If the Active Operational Dataset is already known as <a 64 * href="https://www.threadgroup.org">Thread TLVs</a>, you can simply use: 65 * 66 * <pre>{@code 67 * ActiveOperationalDataset activeDataset = ActiveOperationalDataset.fromThreadTlvs(datasetTlvs); 68 * }</pre> 69 * 70 * @hide 71 */ 72 @FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) 73 @SystemApi 74 public final class ActiveOperationalDataset implements Parcelable { 75 /** The maximum length of the Active Operational Dataset TLV array in bytes. */ 76 public static final int LENGTH_MAX_DATASET_TLVS = 254; 77 78 /** The length of Extended PAN ID in bytes. */ 79 public static final int LENGTH_EXTENDED_PAN_ID = 8; 80 81 /** The minimum length of Network Name as UTF-8 bytes. */ 82 public static final int LENGTH_MIN_NETWORK_NAME_BYTES = 1; 83 84 /** The maximum length of Network Name as UTF-8 bytes. */ 85 public static final int LENGTH_MAX_NETWORK_NAME_BYTES = 16; 86 87 /** The length of Network Key in bytes. */ 88 public static final int LENGTH_NETWORK_KEY = 16; 89 90 /** The length of Mesh-Local Prefix in bits. */ 91 public static final int LENGTH_MESH_LOCAL_PREFIX_BITS = 64; 92 93 /** The length of PSKc in bytes. */ 94 public static final int LENGTH_PSKC = 16; 95 96 /** The 2.4 GHz channel page. */ 97 public static final int CHANNEL_PAGE_24_GHZ = 0; 98 99 /** The minimum 2.4GHz channel. */ 100 public static final int CHANNEL_MIN_24_GHZ = 11; 101 102 /** The maximum 2.4GHz channel. */ 103 public static final int CHANNEL_MAX_24_GHZ = 26; 104 105 /** @hide */ 106 @VisibleForTesting public static final int TYPE_CHANNEL = 0; 107 108 /** @hide */ 109 @VisibleForTesting public static final int TYPE_PAN_ID = 1; 110 111 /** @hide */ 112 @VisibleForTesting public static final int TYPE_EXTENDED_PAN_ID = 2; 113 114 /** @hide */ 115 @VisibleForTesting public static final int TYPE_NETWORK_NAME = 3; 116 117 /** @hide */ 118 @VisibleForTesting public static final int TYPE_PSKC = 4; 119 120 /** @hide */ 121 @VisibleForTesting public static final int TYPE_NETWORK_KEY = 5; 122 123 /** @hide */ 124 @VisibleForTesting public static final int TYPE_MESH_LOCAL_PREFIX = 7; 125 126 /** @hide */ 127 @VisibleForTesting public static final int TYPE_SECURITY_POLICY = 12; 128 129 /** @hide */ 130 @VisibleForTesting public static final int TYPE_ACTIVE_TIMESTAMP = 14; 131 132 /** @hide */ 133 @VisibleForTesting public static final int TYPE_CHANNEL_MASK = 53; 134 135 /** @hide */ 136 public static final byte MESH_LOCAL_PREFIX_FIRST_BYTE = (byte) 0xfd; 137 138 private static final int LENGTH_CHANNEL = 3; 139 private static final int LENGTH_PAN_ID = 2; 140 141 @NonNull 142 public static final Creator<ActiveOperationalDataset> CREATOR = 143 new Creator<>() { 144 @Override 145 public ActiveOperationalDataset createFromParcel(Parcel in) { 146 return ActiveOperationalDataset.fromThreadTlvs(in.createByteArray()); 147 } 148 149 @Override 150 public ActiveOperationalDataset[] newArray(int size) { 151 return new ActiveOperationalDataset[size]; 152 } 153 }; 154 155 private final OperationalDatasetTimestamp mActiveTimestamp; 156 private final String mNetworkName; 157 private final byte[] mExtendedPanId; 158 private final int mPanId; 159 private final int mChannel; 160 private final int mChannelPage; 161 private final SparseArray<byte[]> mChannelMask; 162 private final byte[] mPskc; 163 private final byte[] mNetworkKey; 164 private final IpPrefix mMeshLocalPrefix; 165 private final SecurityPolicy mSecurityPolicy; 166 private final SparseArray<byte[]> mUnknownTlvs; 167 ActiveOperationalDataset(Builder builder)168 private ActiveOperationalDataset(Builder builder) { 169 this( 170 requireNonNull(builder.mActiveTimestamp), 171 requireNonNull(builder.mNetworkName), 172 requireNonNull(builder.mExtendedPanId), 173 requireNonNull(builder.mPanId), 174 requireNonNull(builder.mChannelPage), 175 requireNonNull(builder.mChannel), 176 requireNonNull(builder.mChannelMask), 177 requireNonNull(builder.mPskc), 178 requireNonNull(builder.mNetworkKey), 179 requireNonNull(builder.mMeshLocalPrefix), 180 requireNonNull(builder.mSecurityPolicy), 181 requireNonNull(builder.mUnknownTlvs)); 182 } 183 ActiveOperationalDataset( OperationalDatasetTimestamp activeTimestamp, String networkName, byte[] extendedPanId, int panId, int channelPage, int channel, SparseArray<byte[]> channelMask, byte[] pskc, byte[] networkKey, IpPrefix meshLocalPrefix, SecurityPolicy securityPolicy, SparseArray<byte[]> unknownTlvs)184 private ActiveOperationalDataset( 185 OperationalDatasetTimestamp activeTimestamp, 186 String networkName, 187 byte[] extendedPanId, 188 int panId, 189 int channelPage, 190 int channel, 191 SparseArray<byte[]> channelMask, 192 byte[] pskc, 193 byte[] networkKey, 194 IpPrefix meshLocalPrefix, 195 SecurityPolicy securityPolicy, 196 SparseArray<byte[]> unknownTlvs) { 197 this.mActiveTimestamp = activeTimestamp; 198 this.mNetworkName = networkName; 199 this.mExtendedPanId = extendedPanId.clone(); 200 this.mPanId = panId; 201 this.mChannel = channel; 202 this.mChannelPage = channelPage; 203 this.mChannelMask = deepCloneSparseArray(channelMask); 204 this.mPskc = pskc.clone(); 205 this.mNetworkKey = networkKey.clone(); 206 this.mMeshLocalPrefix = meshLocalPrefix; 207 this.mSecurityPolicy = securityPolicy; 208 this.mUnknownTlvs = deepCloneSparseArray(unknownTlvs); 209 } 210 211 /** 212 * Creates a new {@link ActiveOperationalDataset} object from a series of Thread TLVs. 213 * 214 * <p>{@code tlvs} can be obtained from the value of a Thread Active Operational Dataset TLV 215 * (see the <a href="https://www.threadgroup.org/support#specifications">Thread 216 * specification</a> for the definition) or the return value of {@link #toThreadTlvs}. 217 * 218 * @param tlvs a series of Thread TLVs which contain the Active Operational Dataset 219 * @return the decoded Active Operational Dataset 220 * @throws IllegalArgumentException if {@code tlvs} is malformed or the length is larger than 221 * {@link LENGTH_MAX_DATASET_TLVS} 222 */ 223 @NonNull fromThreadTlvs(@onNull byte[] tlvs)224 public static ActiveOperationalDataset fromThreadTlvs(@NonNull byte[] tlvs) { 225 requireNonNull(tlvs, "tlvs cannot be null"); 226 if (tlvs.length > LENGTH_MAX_DATASET_TLVS) { 227 throw new IllegalArgumentException( 228 String.format( 229 "tlvs length exceeds max length %d (actual is %d)", 230 LENGTH_MAX_DATASET_TLVS, tlvs.length)); 231 } 232 233 Builder builder = new Builder(); 234 int i = 0; 235 while (i < tlvs.length) { 236 int type = tlvs[i++] & 0xff; 237 if (i >= tlvs.length) { 238 throw new IllegalArgumentException( 239 String.format( 240 "Found TLV type %d at end of operational dataset with length %d", 241 type, tlvs.length)); 242 } 243 244 int length = tlvs[i++] & 0xff; 245 if (i + length > tlvs.length) { 246 throw new IllegalArgumentException( 247 String.format( 248 "Found TLV type %d with length %d which exceeds the remaining data" 249 + " in the operational dataset with length %d", 250 type, length, tlvs.length)); 251 } 252 253 initWithTlv(builder, type, Arrays.copyOfRange(tlvs, i, i + length)); 254 i += length; 255 } 256 try { 257 return builder.build(); 258 } catch (IllegalStateException e) { 259 throw new IllegalArgumentException( 260 "Failed to build the ActiveOperationalDataset object", e); 261 } 262 } 263 initWithTlv(Builder builder, int type, byte[] value)264 private static void initWithTlv(Builder builder, int type, byte[] value) { 265 // The max length of the dataset is 254 bytes, so the max length of a single TLV value is 266 // 252 (254 - 1 - 1) 267 if (value.length > LENGTH_MAX_DATASET_TLVS - 2) { 268 throw new IllegalArgumentException( 269 String.format( 270 "Length of TLV %d exceeds %d (actualLength = %d)", 271 (type & 0xff), LENGTH_MAX_DATASET_TLVS - 2, value.length)); 272 } 273 274 switch (type) { 275 case TYPE_CHANNEL: 276 checkArgument( 277 value.length == LENGTH_CHANNEL, 278 "Invalid channel (length = %d, expectedLength = %d)", 279 value.length, 280 LENGTH_CHANNEL); 281 builder.setChannel((value[0] & 0xff), ((value[1] & 0xff) << 8) | (value[2] & 0xff)); 282 break; 283 case TYPE_PAN_ID: 284 checkArgument( 285 value.length == LENGTH_PAN_ID, 286 "Invalid PAN ID (length = %d, expectedLength = %d)", 287 value.length, 288 LENGTH_PAN_ID); 289 builder.setPanId(((value[0] & 0xff) << 8) | (value[1] & 0xff)); 290 break; 291 case TYPE_EXTENDED_PAN_ID: 292 builder.setExtendedPanId(value); 293 break; 294 case TYPE_NETWORK_NAME: 295 builder.setNetworkName(new String(value, UTF_8)); 296 break; 297 case TYPE_PSKC: 298 builder.setPskc(value); 299 break; 300 case TYPE_NETWORK_KEY: 301 builder.setNetworkKey(value); 302 break; 303 case TYPE_MESH_LOCAL_PREFIX: 304 builder.setMeshLocalPrefix(value); 305 break; 306 case TYPE_SECURITY_POLICY: 307 builder.setSecurityPolicy(SecurityPolicy.fromTlvValue(value)); 308 break; 309 case TYPE_ACTIVE_TIMESTAMP: 310 builder.setActiveTimestamp(OperationalDatasetTimestamp.fromTlvValue(value)); 311 break; 312 case TYPE_CHANNEL_MASK: 313 builder.setChannelMask(decodeChannelMask(value)); 314 break; 315 default: 316 builder.addUnknownTlv(type & 0xff, value); 317 break; 318 } 319 } 320 decodeChannelMask(byte[] tlvValue)321 private static SparseArray<byte[]> decodeChannelMask(byte[] tlvValue) { 322 SparseArray<byte[]> channelMask = new SparseArray<>(); 323 int i = 0; 324 while (i < tlvValue.length) { 325 int channelPage = tlvValue[i++] & 0xff; 326 if (i >= tlvValue.length) { 327 throw new IllegalArgumentException( 328 "Invalid channel mask - channel mask length is missing"); 329 } 330 331 int maskLength = tlvValue[i++] & 0xff; 332 if (i + maskLength > tlvValue.length) { 333 throw new IllegalArgumentException( 334 String.format( 335 "Invalid channel mask - channel mask is incomplete " 336 + "(offset = %d, length = %d, totalLength = %d)", 337 i, maskLength, tlvValue.length)); 338 } 339 340 channelMask.put(channelPage, Arrays.copyOfRange(tlvValue, i, i + maskLength)); 341 i += maskLength; 342 } 343 return channelMask; 344 } 345 encodeChannelMask( SparseArray<byte[]> channelMask, ByteArrayOutputStream outputStream)346 private static void encodeChannelMask( 347 SparseArray<byte[]> channelMask, ByteArrayOutputStream outputStream) { 348 ByteArrayOutputStream entryStream = new ByteArrayOutputStream(); 349 350 for (int i = 0; i < channelMask.size(); i++) { 351 int key = channelMask.keyAt(i); 352 byte[] value = channelMask.get(key); 353 entryStream.write(key); 354 entryStream.write(value.length); 355 entryStream.write(value, 0, value.length); 356 } 357 358 byte[] entries = entryStream.toByteArray(); 359 360 outputStream.write(TYPE_CHANNEL_MASK); 361 outputStream.write(entries.length); 362 outputStream.write(entries, 0, entries.length); 363 } 364 areByteSparseArraysEqual( @onNull SparseArray<byte[]> first, @NonNull SparseArray<byte[]> second)365 private static boolean areByteSparseArraysEqual( 366 @NonNull SparseArray<byte[]> first, @NonNull SparseArray<byte[]> second) { 367 if (first == second) { 368 return true; 369 } else if (first == null || second == null) { 370 return false; 371 } else if (first.size() != second.size()) { 372 return false; 373 } else { 374 for (int i = 0; i < first.size(); i++) { 375 int firstKey = first.keyAt(i); 376 int secondKey = second.keyAt(i); 377 if (firstKey != secondKey) { 378 return false; 379 } 380 381 byte[] firstValue = first.valueAt(i); 382 byte[] secondValue = second.valueAt(i); 383 if (!Arrays.equals(firstValue, secondValue)) { 384 return false; 385 } 386 } 387 return true; 388 } 389 } 390 391 /** An easy-to-use wrapper of {@link Arrays#deepHashCode}. */ deepHashCode(Object... values)392 private static int deepHashCode(Object... values) { 393 return Arrays.deepHashCode(values); 394 } 395 396 /** 397 * Converts this {@link ActiveOperationalDataset} object to a series of Thread TLVs. 398 * 399 * <p>See the <a href="https://www.threadgroup.org/support#specifications">Thread 400 * specification</a> for the definition of the Thread TLV format. 401 * 402 * @return a series of Thread TLVs which contain this Active Operational Dataset 403 */ 404 @NonNull toThreadTlvs()405 public byte[] toThreadTlvs() { 406 ByteArrayOutputStream dataset = new ByteArrayOutputStream(); 407 408 dataset.write(TYPE_ACTIVE_TIMESTAMP); 409 byte[] activeTimestampBytes = mActiveTimestamp.toTlvValue(); 410 dataset.write(activeTimestampBytes.length); 411 dataset.write(activeTimestampBytes, 0, activeTimestampBytes.length); 412 413 dataset.write(TYPE_NETWORK_NAME); 414 byte[] networkNameBytes = mNetworkName.getBytes(UTF_8); 415 dataset.write(networkNameBytes.length); 416 dataset.write(networkNameBytes, 0, networkNameBytes.length); 417 418 dataset.write(TYPE_EXTENDED_PAN_ID); 419 dataset.write(mExtendedPanId.length); 420 dataset.write(mExtendedPanId, 0, mExtendedPanId.length); 421 422 dataset.write(TYPE_PAN_ID); 423 dataset.write(LENGTH_PAN_ID); 424 dataset.write(mPanId >> 8); 425 dataset.write(mPanId); 426 427 dataset.write(TYPE_CHANNEL); 428 dataset.write(LENGTH_CHANNEL); 429 dataset.write(mChannelPage); 430 dataset.write(mChannel >> 8); 431 dataset.write(mChannel); 432 433 encodeChannelMask(mChannelMask, dataset); 434 435 dataset.write(TYPE_PSKC); 436 dataset.write(mPskc.length); 437 dataset.write(mPskc, 0, mPskc.length); 438 439 dataset.write(TYPE_NETWORK_KEY); 440 dataset.write(mNetworkKey.length); 441 dataset.write(mNetworkKey, 0, mNetworkKey.length); 442 443 dataset.write(TYPE_MESH_LOCAL_PREFIX); 444 dataset.write(mMeshLocalPrefix.getPrefixLength() / 8); 445 dataset.write(mMeshLocalPrefix.getRawAddress(), 0, mMeshLocalPrefix.getPrefixLength() / 8); 446 447 dataset.write(TYPE_SECURITY_POLICY); 448 byte[] securityPolicyBytes = mSecurityPolicy.toTlvValue(); 449 dataset.write(securityPolicyBytes.length); 450 dataset.write(securityPolicyBytes, 0, securityPolicyBytes.length); 451 452 for (int i = 0; i < mUnknownTlvs.size(); i++) { 453 byte[] value = mUnknownTlvs.valueAt(i); 454 dataset.write(mUnknownTlvs.keyAt(i)); 455 dataset.write(value.length); 456 dataset.write(value, 0, value.length); 457 } 458 459 return dataset.toByteArray(); 460 } 461 462 /** Returns the Active Timestamp. */ 463 @NonNull getActiveTimestamp()464 public OperationalDatasetTimestamp getActiveTimestamp() { 465 return mActiveTimestamp; 466 } 467 468 /** Returns the Network Name. */ 469 @NonNull 470 @Size(min = LENGTH_MIN_NETWORK_NAME_BYTES, max = LENGTH_MAX_NETWORK_NAME_BYTES) getNetworkName()471 public String getNetworkName() { 472 return mNetworkName; 473 } 474 475 /** Returns the Extended PAN ID. */ 476 @NonNull 477 @Size(LENGTH_EXTENDED_PAN_ID) getExtendedPanId()478 public byte[] getExtendedPanId() { 479 return mExtendedPanId.clone(); 480 } 481 482 /** Returns the PAN ID. */ 483 @IntRange(from = 0, to = 0xfffe) getPanId()484 public int getPanId() { 485 return mPanId; 486 } 487 488 /** Returns the Channel. */ 489 @IntRange(from = 0, to = 65535) getChannel()490 public int getChannel() { 491 return mChannel; 492 } 493 494 /** Returns the Channel Page. */ 495 @IntRange(from = 0, to = 255) getChannelPage()496 public int getChannelPage() { 497 return mChannelPage; 498 } 499 500 /** 501 * Returns the Channel masks. For the returned {@link SparseArray}, the key is the Channel Page 502 * and the value is the Channel Mask. 503 */ 504 @NonNull 505 @Size(min = 1) getChannelMask()506 public SparseArray<byte[]> getChannelMask() { 507 return deepCloneSparseArray(mChannelMask); 508 } 509 deepCloneSparseArray(SparseArray<byte[]> src)510 private static SparseArray<byte[]> deepCloneSparseArray(SparseArray<byte[]> src) { 511 SparseArray<byte[]> dst = new SparseArray<>(src.size()); 512 for (int i = 0; i < src.size(); i++) { 513 dst.put(src.keyAt(i), src.valueAt(i).clone()); 514 } 515 return dst; 516 } 517 518 /** Returns the PSKc. */ 519 @NonNull 520 @Size(LENGTH_PSKC) getPskc()521 public byte[] getPskc() { 522 return mPskc.clone(); 523 } 524 525 /** Returns the Network Key. */ 526 @NonNull 527 @Size(LENGTH_NETWORK_KEY) getNetworkKey()528 public byte[] getNetworkKey() { 529 return mNetworkKey.clone(); 530 } 531 532 /** 533 * Returns the Mesh-local Prefix. The length of the returned prefix is always {@link 534 * #LENGTH_MESH_LOCAL_PREFIX_BITS}. 535 */ 536 @NonNull getMeshLocalPrefix()537 public IpPrefix getMeshLocalPrefix() { 538 return mMeshLocalPrefix; 539 } 540 541 /** Returns the Security Policy. */ 542 @NonNull getSecurityPolicy()543 public SecurityPolicy getSecurityPolicy() { 544 return mSecurityPolicy; 545 } 546 547 /** 548 * Returns Thread TLVs which are not recognized by this device. The returned {@link SparseArray} 549 * associates TLV values to their keys. 550 * 551 * @hide 552 */ 553 @NonNull getUnknownTlvs()554 public SparseArray<byte[]> getUnknownTlvs() { 555 return deepCloneSparseArray(mUnknownTlvs); 556 } 557 558 @Override describeContents()559 public int describeContents() { 560 return 0; 561 } 562 563 @Override writeToParcel(@onNull Parcel dest, int flags)564 public void writeToParcel(@NonNull Parcel dest, int flags) { 565 dest.writeByteArray(toThreadTlvs()); 566 } 567 568 @Override equals(Object other)569 public boolean equals(Object other) { 570 if (other == this) { 571 return true; 572 } else if (!(other instanceof ActiveOperationalDataset)) { 573 return false; 574 } else { 575 ActiveOperationalDataset otherDataset = (ActiveOperationalDataset) other; 576 return mActiveTimestamp.equals(otherDataset.mActiveTimestamp) 577 && mNetworkName.equals(otherDataset.mNetworkName) 578 && Arrays.equals(mExtendedPanId, otherDataset.mExtendedPanId) 579 && mPanId == otherDataset.mPanId 580 && mChannelPage == otherDataset.mChannelPage 581 && mChannel == otherDataset.mChannel 582 && areByteSparseArraysEqual(mChannelMask, otherDataset.mChannelMask) 583 && Arrays.equals(mPskc, otherDataset.mPskc) 584 && Arrays.equals(mNetworkKey, otherDataset.mNetworkKey) 585 && mMeshLocalPrefix.equals(otherDataset.mMeshLocalPrefix) 586 && mSecurityPolicy.equals(otherDataset.mSecurityPolicy) 587 && areByteSparseArraysEqual(mUnknownTlvs, otherDataset.mUnknownTlvs); 588 } 589 } 590 591 @Override hashCode()592 public int hashCode() { 593 return deepHashCode( 594 mActiveTimestamp, 595 mNetworkName, 596 mExtendedPanId, 597 mPanId, 598 mChannel, 599 mChannelPage, 600 mChannelMask, 601 mPskc, 602 mNetworkKey, 603 mMeshLocalPrefix, 604 mSecurityPolicy); 605 } 606 607 @Override toString()608 public String toString() { 609 StringBuilder sb = new StringBuilder(); 610 sb.append("{networkName=") 611 .append(getNetworkName()) 612 .append(", extendedPanId=") 613 .append(toHexString(getExtendedPanId())) 614 .append(", panId=") 615 .append(getPanId()) 616 .append(", channel=") 617 .append(getChannel()) 618 .append(", activeTimestamp=") 619 .append(getActiveTimestamp()) 620 .append("}"); 621 return sb.toString(); 622 } 623 checkNetworkName(@onNull String networkName)624 static String checkNetworkName(@NonNull String networkName) { 625 requireNonNull(networkName, "networkName cannot be null"); 626 627 int nameLength = networkName.getBytes(UTF_8).length; 628 checkArgument( 629 nameLength >= LENGTH_MIN_NETWORK_NAME_BYTES 630 && nameLength <= LENGTH_MAX_NETWORK_NAME_BYTES, 631 "Invalid network name (length = %d, expectedLengthRange = [%d, %d])", 632 nameLength, 633 LENGTH_MIN_NETWORK_NAME_BYTES, 634 LENGTH_MAX_NETWORK_NAME_BYTES); 635 return networkName; 636 } 637 638 /** The builder for creating {@link ActiveOperationalDataset} objects. */ 639 public static final class Builder { 640 private OperationalDatasetTimestamp mActiveTimestamp; 641 private String mNetworkName; 642 private byte[] mExtendedPanId; 643 private Integer mPanId; 644 private Integer mChannel; 645 private Integer mChannelPage; 646 private SparseArray<byte[]> mChannelMask; 647 private byte[] mPskc; 648 private byte[] mNetworkKey; 649 private IpPrefix mMeshLocalPrefix; 650 private SecurityPolicy mSecurityPolicy; 651 private SparseArray<byte[]> mUnknownTlvs; 652 653 /** 654 * Creates a {@link Builder} object with values from an {@link ActiveOperationalDataset} 655 * object. 656 */ Builder(@onNull ActiveOperationalDataset activeOpDataset)657 public Builder(@NonNull ActiveOperationalDataset activeOpDataset) { 658 requireNonNull(activeOpDataset, "activeOpDataset cannot be null"); 659 660 this.mActiveTimestamp = activeOpDataset.mActiveTimestamp; 661 this.mNetworkName = activeOpDataset.mNetworkName; 662 this.mExtendedPanId = activeOpDataset.mExtendedPanId.clone(); 663 this.mPanId = activeOpDataset.mPanId; 664 this.mChannel = activeOpDataset.mChannel; 665 this.mChannelPage = activeOpDataset.mChannelPage; 666 this.mChannelMask = deepCloneSparseArray(activeOpDataset.mChannelMask); 667 this.mPskc = activeOpDataset.mPskc.clone(); 668 this.mNetworkKey = activeOpDataset.mNetworkKey.clone(); 669 this.mMeshLocalPrefix = activeOpDataset.mMeshLocalPrefix; 670 this.mSecurityPolicy = activeOpDataset.mSecurityPolicy; 671 this.mUnknownTlvs = deepCloneSparseArray(activeOpDataset.mUnknownTlvs); 672 } 673 674 /** 675 * Creates an empty {@link Builder} object. 676 * 677 * <p>An empty builder cannot build a new {@link ActiveOperationalDataset} object. The 678 * Active Operational Dataset parameters must be set with setters of this builder. 679 */ Builder()680 public Builder() { 681 mChannelMask = new SparseArray<>(); 682 mUnknownTlvs = new SparseArray<>(); 683 } 684 685 /** 686 * Sets the Active Timestamp. 687 * 688 * @param activeTimestamp Active Timestamp of the Operational Dataset 689 */ 690 @NonNull setActiveTimestamp(@onNull OperationalDatasetTimestamp activeTimestamp)691 public Builder setActiveTimestamp(@NonNull OperationalDatasetTimestamp activeTimestamp) { 692 requireNonNull(activeTimestamp, "activeTimestamp cannot be null"); 693 this.mActiveTimestamp = activeTimestamp; 694 return this; 695 } 696 697 /** 698 * Sets the Network Name. 699 * 700 * @param networkName the name of the Thread network 701 * @throws IllegalArgumentException if length of the UTF-8 representation of {@code 702 * networkName} isn't in range of [{@link #LENGTH_MIN_NETWORK_NAME_BYTES}, {@link 703 * #LENGTH_MAX_NETWORK_NAME_BYTES}] 704 */ 705 @NonNull setNetworkName( @onNull @ize min = LENGTH_MIN_NETWORK_NAME_BYTES, max = LENGTH_MAX_NETWORK_NAME_BYTES) String networkName)706 public Builder setNetworkName( 707 @NonNull 708 @Size( 709 min = LENGTH_MIN_NETWORK_NAME_BYTES, 710 max = LENGTH_MAX_NETWORK_NAME_BYTES) 711 String networkName) { 712 this.mNetworkName = checkNetworkName(networkName); 713 return this; 714 } 715 716 /** 717 * Sets the Extended PAN ID. 718 * 719 * <p>Use with caution. A randomized Extended PAN ID should be used for real Thread 720 * networks. It's discouraged to call this method to override the default value created by 721 * {@link ThreadNetworkController#createRandomizedDataset} in production. 722 * 723 * @throws IllegalArgumentException if length of {@code extendedPanId} is not {@link 724 * #LENGTH_EXTENDED_PAN_ID}. 725 */ 726 @NonNull setExtendedPanId( @onNull @izeLENGTH_EXTENDED_PAN_ID) byte[] extendedPanId)727 public Builder setExtendedPanId( 728 @NonNull @Size(LENGTH_EXTENDED_PAN_ID) byte[] extendedPanId) { 729 requireNonNull(extendedPanId, "extendedPanId cannot be null"); 730 checkArgument( 731 extendedPanId.length == LENGTH_EXTENDED_PAN_ID, 732 "Invalid extended PAN ID (length = %d, expectedLength = %d)", 733 extendedPanId.length, 734 LENGTH_EXTENDED_PAN_ID); 735 this.mExtendedPanId = extendedPanId.clone(); 736 return this; 737 } 738 739 /** 740 * Sets the PAN ID. 741 * 742 * @throws IllegalArgumentException if {@code panId} is not in range of 0x0-0xfffe 743 */ 744 @NonNull setPanId(@ntRangefrom = 0, to = 0xfffe) int panId)745 public Builder setPanId(@IntRange(from = 0, to = 0xfffe) int panId) { 746 checkArgument( 747 panId >= 0 && panId <= 0xfffe, 748 "PAN ID exceeds allowed range (panid = %d, allowedRange = [0x0, 0xffff])", 749 panId); 750 this.mPanId = panId; 751 return this; 752 } 753 754 /** 755 * Sets the Channel Page and Channel. 756 * 757 * <p>Channel Pages other than {@link #CHANNEL_PAGE_24_GHZ} are undefined and may lead to 758 * unexpected behavior if it's applied to Thread devices. 759 * 760 * @throws IllegalArgumentException if invalid channel is specified for the {@code 761 * channelPage} 762 */ 763 @NonNull setChannel( @ntRangefrom = 0, to = 255) int page, @IntRange(from = 0, to = 65535) int channel)764 public Builder setChannel( 765 @IntRange(from = 0, to = 255) int page, 766 @IntRange(from = 0, to = 65535) int channel) { 767 checkArgument( 768 page >= 0 && page <= 255, 769 "Invalid channel page (page = %d, allowedRange = [0, 255])", 770 page); 771 if (page == CHANNEL_PAGE_24_GHZ) { 772 checkArgument( 773 channel >= CHANNEL_MIN_24_GHZ && channel <= CHANNEL_MAX_24_GHZ, 774 "Invalid channel %d in page %d (allowedChannelRange = [%d, %d])", 775 channel, 776 page, 777 CHANNEL_MIN_24_GHZ, 778 CHANNEL_MAX_24_GHZ); 779 } else { 780 checkArgument( 781 channel >= 0 && channel <= 65535, 782 "Invalid channel %d in page %d " 783 + "(channel = %d, allowedChannelRange = [0, 65535])", 784 channel, 785 page, 786 channel); 787 } 788 789 this.mChannelPage = page; 790 this.mChannel = channel; 791 return this; 792 } 793 794 /** 795 * Sets the Channel Mask. 796 * 797 * @throws IllegalArgumentException if {@code channelMask} is empty 798 */ 799 @NonNull setChannelMask(@onNull @izemin = 1) SparseArray<byte[]> channelMask)800 public Builder setChannelMask(@NonNull @Size(min = 1) SparseArray<byte[]> channelMask) { 801 requireNonNull(channelMask, "channelMask cannot be null"); 802 checkArgument(channelMask.size() > 0, "channelMask is empty"); 803 this.mChannelMask = deepCloneSparseArray(channelMask); 804 return this; 805 } 806 807 /** 808 * Sets the PSKc. 809 * 810 * <p>Use with caution. A randomly generated PSKc should be used for real Thread networks. 811 * It's discouraged to call this method to override the default value created by {@link 812 * ThreadNetworkController#createRandomizedDataset} in production. 813 * 814 * @param pskc the key stretched version of the Commissioning Credential for the network 815 * @throws IllegalArgumentException if length of {@code pskc} is not {@link #LENGTH_PSKC} 816 */ 817 @NonNull setPskc(@onNull @izeLENGTH_PSKC) byte[] pskc)818 public Builder setPskc(@NonNull @Size(LENGTH_PSKC) byte[] pskc) { 819 requireNonNull(pskc, "pskc cannot be null"); 820 checkArgument( 821 pskc.length == LENGTH_PSKC, 822 "Invalid PSKc length (length = %d, expectedLength = %d)", 823 pskc.length, 824 LENGTH_PSKC); 825 this.mPskc = pskc.clone(); 826 return this; 827 } 828 829 /** 830 * Sets the Network Key. 831 * 832 * <p>Use with caution, randomly generated Network Key should be used for real Thread 833 * networks. It's discouraged to call this method to override the default value created by 834 * {@link ThreadNetworkController#createRandomizedDataset} in production. 835 * 836 * @param networkKey a 128-bit security key-derivation key for the Thread Network 837 * @throws IllegalArgumentException if length of {@code networkKey} is not {@link 838 * #LENGTH_NETWORK_KEY} 839 */ 840 @NonNull setNetworkKey(@onNull @izeLENGTH_NETWORK_KEY) byte[] networkKey)841 public Builder setNetworkKey(@NonNull @Size(LENGTH_NETWORK_KEY) byte[] networkKey) { 842 requireNonNull(networkKey, "networkKey cannot be null"); 843 checkArgument( 844 networkKey.length == LENGTH_NETWORK_KEY, 845 "Invalid network key length (length = %d, expectedLength = %d)", 846 networkKey.length, 847 LENGTH_NETWORK_KEY); 848 this.mNetworkKey = networkKey.clone(); 849 return this; 850 } 851 852 /** 853 * Sets the Mesh-Local Prefix. 854 * 855 * @param meshLocalPrefix the prefix used for realm-local traffic within the mesh 856 * @throws IllegalArgumentException if prefix length of {@code meshLocalPrefix} isn't {@link 857 * #LENGTH_MESH_LOCAL_PREFIX_BITS} or {@code meshLocalPrefix} doesn't start with {@code 858 * 0xfd} 859 */ 860 @NonNull setMeshLocalPrefix(@onNull IpPrefix meshLocalPrefix)861 public Builder setMeshLocalPrefix(@NonNull IpPrefix meshLocalPrefix) { 862 requireNonNull(meshLocalPrefix, "meshLocalPrefix cannot be null"); 863 checkArgument( 864 meshLocalPrefix.getPrefixLength() == LENGTH_MESH_LOCAL_PREFIX_BITS, 865 "Invalid mesh-local prefix length (length = %d, expectedLength = %d)", 866 meshLocalPrefix.getPrefixLength(), 867 LENGTH_MESH_LOCAL_PREFIX_BITS); 868 checkArgument( 869 meshLocalPrefix.getRawAddress()[0] == MESH_LOCAL_PREFIX_FIRST_BYTE, 870 "Mesh-local prefix must start with 0xfd: " + meshLocalPrefix); 871 this.mMeshLocalPrefix = meshLocalPrefix; 872 return this; 873 } 874 875 /** 876 * Sets the Mesh-Local Prefix. 877 * 878 * @param meshLocalPrefix the prefix used for realm-local traffic within the mesh 879 * @throws IllegalArgumentException if {@code meshLocalPrefix} doesn't start with {@code 880 * 0xfd} or has length other than {@code LENGTH_MESH_LOCAL_PREFIX_BITS / 8} 881 * @hide 882 */ 883 @NonNull setMeshLocalPrefix(byte[] meshLocalPrefix)884 public Builder setMeshLocalPrefix(byte[] meshLocalPrefix) { 885 final int prefixLength = meshLocalPrefix.length * 8; 886 checkArgument( 887 prefixLength == LENGTH_MESH_LOCAL_PREFIX_BITS, 888 "Invalid mesh-local prefix length (length = %d, expectedLength = %d)", 889 prefixLength, 890 LENGTH_MESH_LOCAL_PREFIX_BITS); 891 byte[] ip6RawAddress = new byte[16]; 892 System.arraycopy(meshLocalPrefix, 0, ip6RawAddress, 0, meshLocalPrefix.length); 893 try { 894 return setMeshLocalPrefix( 895 new IpPrefix(Inet6Address.getByAddress(ip6RawAddress), prefixLength)); 896 } catch (UnknownHostException e) { 897 // Can't happen because numeric address is provided 898 throw new AssertionError(e); 899 } 900 } 901 902 /** Sets the Security Policy. */ 903 @NonNull setSecurityPolicy(@onNull SecurityPolicy securityPolicy)904 public Builder setSecurityPolicy(@NonNull SecurityPolicy securityPolicy) { 905 requireNonNull(securityPolicy, "securityPolicy cannot be null"); 906 this.mSecurityPolicy = securityPolicy; 907 return this; 908 } 909 910 /** 911 * Sets additional unknown TLVs. 912 * 913 * @hide 914 */ 915 @NonNull setUnknownTlvs(@onNull SparseArray<byte[]> unknownTlvs)916 public Builder setUnknownTlvs(@NonNull SparseArray<byte[]> unknownTlvs) { 917 requireNonNull(unknownTlvs, "unknownTlvs cannot be null"); 918 mUnknownTlvs = deepCloneSparseArray(unknownTlvs); 919 return this; 920 } 921 922 /** Adds one more unknown TLV. @hide */ 923 @VisibleForTesting 924 @NonNull addUnknownTlv(int type, byte[] value)925 public Builder addUnknownTlv(int type, byte[] value) { 926 mUnknownTlvs.put(type, value); 927 return this; 928 } 929 930 /** 931 * Creates a new {@link ActiveOperationalDataset} object. 932 * 933 * @throws IllegalStateException if any of the fields isn't set or the total length exceeds 934 * {@link #LENGTH_MAX_DATASET_TLVS} bytes 935 */ 936 @NonNull build()937 public ActiveOperationalDataset build() { 938 checkState(mActiveTimestamp != null, "Active Timestamp is missing"); 939 checkState(mNetworkName != null, "Network Name is missing"); 940 checkState(mExtendedPanId != null, "Extended PAN ID is missing"); 941 checkState(mPanId != null, "PAN ID is missing"); 942 checkState(mChannel != null, "Channel is missing"); 943 checkState(mChannelPage != null, "Channel Page is missing"); 944 checkState(mChannelMask.size() != 0, "Channel Mask is missing"); 945 checkState(mPskc != null, "PSKc is missing"); 946 checkState(mNetworkKey != null, "Network Key is missing"); 947 checkState(mMeshLocalPrefix != null, "Mesh Local Prefix is missing"); 948 checkState(mSecurityPolicy != null, "Security Policy is missing"); 949 950 int length = getTotalDatasetLength(); 951 if (length > LENGTH_MAX_DATASET_TLVS) { 952 throw new IllegalStateException( 953 String.format( 954 "Total dataset length exceeds max length %d (actual is %d)", 955 LENGTH_MAX_DATASET_TLVS, length)); 956 } 957 958 return new ActiveOperationalDataset(this); 959 } 960 getTotalDatasetLength()961 private int getTotalDatasetLength() { 962 int length = 963 2 * 9 // 9 fields with 1 byte of type and 1 byte of length 964 + OperationalDatasetTimestamp.LENGTH_TIMESTAMP 965 + mNetworkName.getBytes(UTF_8).length 966 + LENGTH_EXTENDED_PAN_ID 967 + LENGTH_PAN_ID 968 + LENGTH_CHANNEL 969 + LENGTH_PSKC 970 + LENGTH_NETWORK_KEY 971 + LENGTH_MESH_LOCAL_PREFIX_BITS / 8 972 + mSecurityPolicy.toTlvValue().length; 973 974 for (int i = 0; i < mChannelMask.size(); i++) { 975 length += 2 + mChannelMask.valueAt(i).length; 976 } 977 978 // For the type and length bytes of the Channel Mask TLV because the masks are encoded 979 // as TLVs in TLV. 980 length += 2; 981 982 for (int i = 0; i < mUnknownTlvs.size(); i++) { 983 length += 2 + mUnknownTlvs.valueAt(i).length; 984 } 985 986 return length; 987 } 988 } 989 990 /** 991 * The Security Policy of Thread Operational Dataset which provides an administrator with a way 992 * to enable or disable certain security related behaviors. 993 */ 994 public static final class SecurityPolicy { 995 /** The default Rotation Time in hours. */ 996 public static final int DEFAULT_ROTATION_TIME_HOURS = 672; 997 998 /** The minimum length of Security Policy flags in bytes. */ 999 public static final int LENGTH_MIN_SECURITY_POLICY_FLAGS = 1; 1000 1001 /** The length of Rotation Time TLV value in bytes. */ 1002 private static final int LENGTH_SECURITY_POLICY_ROTATION_TIME = 2; 1003 1004 private final int mRotationTimeHours; 1005 private final byte[] mFlags; 1006 1007 /** 1008 * Creates a new {@link SecurityPolicy} object. 1009 * 1010 * @param rotationTimeHours the value for Thread key rotation in hours. Must be in range of 1011 * 0x1-0xffff. 1012 * @param flags security policy flags with length of either 1 byte for Thread 1.1 or 2 bytes 1013 * for Thread 1.2 or higher. 1014 * @throws IllegalArgumentException if {@code rotationTimeHours} is not in range of 1015 * 0x1-0xffff or length of {@code flags} is smaller than {@link 1016 * #LENGTH_MIN_SECURITY_POLICY_FLAGS}. 1017 */ SecurityPolicy( @ntRangefrom = 0x1, to = 0xffff) int rotationTimeHours, @NonNull @Size(min = LENGTH_MIN_SECURITY_POLICY_FLAGS) byte[] flags)1018 public SecurityPolicy( 1019 @IntRange(from = 0x1, to = 0xffff) int rotationTimeHours, 1020 @NonNull @Size(min = LENGTH_MIN_SECURITY_POLICY_FLAGS) byte[] flags) { 1021 requireNonNull(flags, "flags cannot be null"); 1022 checkArgument( 1023 rotationTimeHours >= 1 && rotationTimeHours <= 0xffff, 1024 "Rotation time exceeds allowed range (rotationTimeHours = %d, allowedRange =" 1025 + " [0x1, 0xffff])", 1026 rotationTimeHours); 1027 checkArgument( 1028 flags.length >= LENGTH_MIN_SECURITY_POLICY_FLAGS, 1029 "Invalid security policy flags length (length = %d, minimumLength = %d)", 1030 flags.length, 1031 LENGTH_MIN_SECURITY_POLICY_FLAGS); 1032 this.mRotationTimeHours = rotationTimeHours; 1033 this.mFlags = flags.clone(); 1034 } 1035 1036 /** 1037 * Creates a new {@link SecurityPolicy} object from the Security Policy TLV value. 1038 * 1039 * @hide 1040 */ 1041 @VisibleForTesting 1042 @NonNull fromTlvValue(byte[] encodedSecurityPolicy)1043 public static SecurityPolicy fromTlvValue(byte[] encodedSecurityPolicy) { 1044 checkArgument( 1045 encodedSecurityPolicy.length 1046 >= LENGTH_SECURITY_POLICY_ROTATION_TIME 1047 + LENGTH_MIN_SECURITY_POLICY_FLAGS, 1048 "Invalid Security Policy TLV length (length = %d, minimumLength = %d)", 1049 encodedSecurityPolicy.length, 1050 LENGTH_SECURITY_POLICY_ROTATION_TIME + LENGTH_MIN_SECURITY_POLICY_FLAGS); 1051 1052 return new SecurityPolicy( 1053 ((encodedSecurityPolicy[0] & 0xff) << 8) | (encodedSecurityPolicy[1] & 0xff), 1054 Arrays.copyOfRange( 1055 encodedSecurityPolicy, 1056 LENGTH_SECURITY_POLICY_ROTATION_TIME, 1057 encodedSecurityPolicy.length)); 1058 } 1059 1060 /** 1061 * Converts this {@link SecurityPolicy} object to Security Policy TLV value. 1062 * 1063 * @hide 1064 */ 1065 @VisibleForTesting 1066 @NonNull toTlvValue()1067 public byte[] toTlvValue() { 1068 ByteArrayOutputStream result = new ByteArrayOutputStream(); 1069 result.write(mRotationTimeHours >> 8); 1070 result.write(mRotationTimeHours); 1071 result.write(mFlags, 0, mFlags.length); 1072 return result.toByteArray(); 1073 } 1074 1075 /** Returns the Security Policy Rotation Time in hours. */ 1076 @IntRange(from = 0x1, to = 0xffff) getRotationTimeHours()1077 public int getRotationTimeHours() { 1078 return mRotationTimeHours; 1079 } 1080 1081 /** Returns 1 byte flags for Thread 1.1 or 2 bytes flags for Thread 1.2. */ 1082 @NonNull 1083 @Size(min = LENGTH_MIN_SECURITY_POLICY_FLAGS) getFlags()1084 public byte[] getFlags() { 1085 return mFlags.clone(); 1086 } 1087 1088 @Override equals(@ullable Object other)1089 public boolean equals(@Nullable Object other) { 1090 if (this == other) { 1091 return true; 1092 } else if (!(other instanceof SecurityPolicy)) { 1093 return false; 1094 } else { 1095 SecurityPolicy otherSecurityPolicy = (SecurityPolicy) other; 1096 return mRotationTimeHours == otherSecurityPolicy.mRotationTimeHours 1097 && Arrays.equals(mFlags, otherSecurityPolicy.mFlags); 1098 } 1099 } 1100 1101 @Override hashCode()1102 public int hashCode() { 1103 return deepHashCode(mRotationTimeHours, mFlags); 1104 } 1105 1106 @Override toString()1107 public String toString() { 1108 StringBuilder sb = new StringBuilder(); 1109 sb.append("{rotation=") 1110 .append(mRotationTimeHours) 1111 .append(", flags=") 1112 .append(toHexString(mFlags)) 1113 .append("}"); 1114 return sb.toString(); 1115 } 1116 } 1117 } 1118