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