• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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