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