• 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 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.annotation.FlaggedApi;
24 import android.annotation.NonNull;
25 import android.annotation.SystemApi;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.util.SparseArray;
29 
30 import com.android.net.thread.flags.Flags;
31 
32 import java.io.ByteArrayOutputStream;
33 import java.nio.ByteBuffer;
34 import java.time.Duration;
35 import java.util.Objects;
36 
37 /**
38  * Data interface for managing a Thread Pending Operational Dataset.
39  *
40  * <p>The Pending Operational Dataset represents an Operational Dataset which will become Active in
41  * a given delay. This is typically used to deploy new network parameters (e.g. Network Key or
42  * Channel) to all devices in the network.
43  *
44  * @see ThreadNetworkController#scheduleMigration
45  * @hide
46  */
47 @FlaggedApi(Flags.FLAG_THREAD_ENABLED)
48 @SystemApi
49 public final class PendingOperationalDataset implements Parcelable {
50     // Value defined in Thread spec 8.10.1.16
51     private static final int TYPE_PENDING_TIMESTAMP = 51;
52 
53     // Values defined in Thread spec 8.10.1.17
54     private static final int TYPE_DELAY_TIMER = 52;
55     private static final int LENGTH_DELAY_TIMER_BYTES = 4;
56 
57     @NonNull
58     public static final Creator<PendingOperationalDataset> CREATOR =
59             new Creator<>() {
60                 @Override
61                 public PendingOperationalDataset createFromParcel(Parcel in) {
62                     return PendingOperationalDataset.fromThreadTlvs(in.createByteArray());
63                 }
64 
65                 @Override
66                 public PendingOperationalDataset[] newArray(int size) {
67                     return new PendingOperationalDataset[size];
68                 }
69             };
70 
71     @NonNull private final ActiveOperationalDataset mActiveOpDataset;
72     @NonNull private final OperationalDatasetTimestamp mPendingTimestamp;
73     @NonNull private final Duration mDelayTimer;
74 
75     /**
76      * Creates a new {@link PendingOperationalDataset} object.
77      *
78      * @param activeOpDataset the included Active Operational Dataset
79      * @param pendingTimestamp the Pending Timestamp which represents the version of this Pending
80      *     Dataset
81      * @param delayTimer the delay after when {@code activeOpDataset} will be committed on this
82      *     device; use {@link Duration#ZERO} to tell the system to choose a reasonable value
83      *     automatically
84      */
PendingOperationalDataset( @onNull ActiveOperationalDataset activeOpDataset, @NonNull OperationalDatasetTimestamp pendingTimestamp, @NonNull Duration delayTimer)85     public PendingOperationalDataset(
86             @NonNull ActiveOperationalDataset activeOpDataset,
87             @NonNull OperationalDatasetTimestamp pendingTimestamp,
88             @NonNull Duration delayTimer) {
89         requireNonNull(activeOpDataset, "activeOpDataset cannot be null");
90         requireNonNull(pendingTimestamp, "pendingTimestamp cannot be null");
91         requireNonNull(delayTimer, "delayTimer cannot be null");
92         this.mActiveOpDataset = activeOpDataset;
93         this.mPendingTimestamp = pendingTimestamp;
94         this.mDelayTimer = delayTimer;
95     }
96 
97     /**
98      * Creates a new {@link PendingOperationalDataset} object from a series of Thread TLVs.
99      *
100      * <p>{@code tlvs} can be obtained from the value of a Thread Pending Operational Dataset TLV
101      * (see the <a href="https://www.threadgroup.org/support#specifications">Thread
102      * specification</a> for the definition) or the return value of {@link #toThreadTlvs}.
103      *
104      * @throws IllegalArgumentException if {@code tlvs} is malformed or contains an invalid Thread
105      *     TLV
106      */
107     @NonNull
fromThreadTlvs(@onNull byte[] tlvs)108     public static PendingOperationalDataset fromThreadTlvs(@NonNull byte[] tlvs) {
109         requireNonNull(tlvs, "tlvs cannot be null");
110 
111         SparseArray<byte[]> newUnknownTlvs = new SparseArray<>();
112         OperationalDatasetTimestamp pendingTimestamp = null;
113         Duration delayTimer = null;
114         ActiveOperationalDataset activeDataset = ActiveOperationalDataset.fromThreadTlvs(tlvs);
115         SparseArray<byte[]> unknownTlvs = activeDataset.getUnknownTlvs();
116         for (int i = 0; i < unknownTlvs.size(); i++) {
117             int key = unknownTlvs.keyAt(i);
118             byte[] value = unknownTlvs.valueAt(i);
119             switch (key) {
120                 case TYPE_PENDING_TIMESTAMP:
121                     pendingTimestamp = OperationalDatasetTimestamp.fromTlvValue(value);
122                     break;
123                 case TYPE_DELAY_TIMER:
124                     checkArgument(
125                             value.length == LENGTH_DELAY_TIMER_BYTES,
126                             "Invalid delay timer (length = %d, expectedLength = %d)",
127                             value.length,
128                             LENGTH_DELAY_TIMER_BYTES);
129                     int millis = ByteBuffer.wrap(value).getInt();
130                     delayTimer = Duration.ofMillis(Integer.toUnsignedLong(millis));
131                     break;
132                 default:
133                     newUnknownTlvs.put(key, value);
134                     break;
135             }
136         }
137 
138         if (pendingTimestamp == null) {
139             throw new IllegalArgumentException("Pending Timestamp is missing");
140         }
141         if (delayTimer == null) {
142             throw new IllegalArgumentException("Delay Timer is missing");
143         }
144 
145         activeDataset =
146                 new ActiveOperationalDataset.Builder(activeDataset)
147                         .setUnknownTlvs(newUnknownTlvs)
148                         .build();
149         return new PendingOperationalDataset(activeDataset, pendingTimestamp, delayTimer);
150     }
151 
152     /** Returns the Active Operational Dataset. */
153     @NonNull
getActiveOperationalDataset()154     public ActiveOperationalDataset getActiveOperationalDataset() {
155         return mActiveOpDataset;
156     }
157 
158     /** Returns the Pending Timestamp. */
159     @NonNull
getPendingTimestamp()160     public OperationalDatasetTimestamp getPendingTimestamp() {
161         return mPendingTimestamp;
162     }
163 
164     /** Returns the Delay Timer. */
165     @NonNull
getDelayTimer()166     public Duration getDelayTimer() {
167         return mDelayTimer;
168     }
169 
170     /**
171      * Converts this {@link PendingOperationalDataset} object to a series of Thread TLVs.
172      *
173      * <p>See the <a href="https://www.threadgroup.org/support#specifications">Thread
174      * specification</a> for the definition of the Thread TLV format.
175      */
176     @NonNull
toThreadTlvs()177     public byte[] toThreadTlvs() {
178         ByteArrayOutputStream dataset = new ByteArrayOutputStream();
179 
180         byte[] activeDatasetBytes = mActiveOpDataset.toThreadTlvs();
181         dataset.write(activeDatasetBytes, 0, activeDatasetBytes.length);
182 
183         dataset.write(TYPE_PENDING_TIMESTAMP);
184         byte[] pendingTimestampBytes = mPendingTimestamp.toTlvValue();
185         dataset.write(pendingTimestampBytes.length);
186         dataset.write(pendingTimestampBytes, 0, pendingTimestampBytes.length);
187 
188         dataset.write(TYPE_DELAY_TIMER);
189         byte[] delayTimerBytes = new byte[LENGTH_DELAY_TIMER_BYTES];
190         ByteBuffer.wrap(delayTimerBytes).putInt((int) mDelayTimer.toMillis());
191         dataset.write(delayTimerBytes.length);
192         dataset.write(delayTimerBytes, 0, delayTimerBytes.length);
193 
194         return dataset.toByteArray();
195     }
196 
197     @Override
equals(Object other)198     public boolean equals(Object other) {
199         if (this == other) {
200             return true;
201         } else if (!(other instanceof PendingOperationalDataset)) {
202             return false;
203         } else {
204             PendingOperationalDataset otherDataset = (PendingOperationalDataset) other;
205             return mActiveOpDataset.equals(otherDataset.mActiveOpDataset)
206                     && mPendingTimestamp.equals(otherDataset.mPendingTimestamp)
207                     && mDelayTimer.equals(otherDataset.mDelayTimer);
208         }
209     }
210 
211     @Override
hashCode()212     public int hashCode() {
213         return Objects.hash(mActiveOpDataset, mPendingTimestamp, mDelayTimer);
214     }
215 
216     @Override
toString()217     public String toString() {
218         StringBuilder sb = new StringBuilder();
219         sb.append("{activeDataset=")
220                 .append(getActiveOperationalDataset())
221                 .append(", pendingTimestamp=")
222                 .append(getPendingTimestamp())
223                 .append(", delayTimer=")
224                 .append(getDelayTimer())
225                 .append("}");
226         return sb.toString();
227     }
228 
229     @Override
describeContents()230     public int describeContents() {
231         return 0;
232     }
233 
234     @Override
writeToParcel(@onNull Parcel dest, int flags)235     public void writeToParcel(@NonNull Parcel dest, int flags) {
236         dest.writeByteArray(toThreadTlvs());
237     }
238 }
239