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