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.IntRange; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.SystemApi; 28 29 import com.android.net.thread.flags.Flags; 30 31 import java.nio.ByteBuffer; 32 import java.time.Instant; 33 import java.util.Objects; 34 35 /** 36 * The timestamp of Thread Operational Dataset. 37 * 38 * @see ActiveOperationalDataset 39 * @see PendingOperationalDataset 40 * @hide 41 */ 42 @FlaggedApi(Flags.FLAG_THREAD_ENABLED) 43 @SystemApi 44 public final class OperationalDatasetTimestamp { 45 /** @hide */ 46 public static final int LENGTH_TIMESTAMP = Long.BYTES; 47 48 private static final int TICKS_UPPER_BOUND = 0x8000; 49 50 private final long mSeconds; 51 private final int mTicks; 52 private final boolean mIsAuthoritativeSource; 53 54 /** 55 * Creates a new {@link OperationalDatasetTimestamp} object from an {@link Instant}. 56 * 57 * <p>The {@code seconds} is set to {@code instant.getEpochSecond()}, {@code ticks} is set to 58 * {@link instant#getNano()} based on frequency of 32768 Hz, and {@code isAuthoritativeSource} 59 * is set to {@code true}. 60 * 61 * <p>Note that this conversion can lose precision and a value returned by {@link #toInstant} 62 * may not equal exactly the {@code instant}. 63 * 64 * @throws IllegalArgumentException if {@code instant.getEpochSecond()} is larger than {@code 65 * 0xffffffffffffL} 66 * @see toInstant 67 */ 68 @NonNull fromInstant(@onNull Instant instant)69 public static OperationalDatasetTimestamp fromInstant(@NonNull Instant instant) { 70 return OperationalDatasetTimestamp.fromInstant(instant, true /* isAuthoritativeSource */); 71 } 72 73 /** 74 * Creates a new {@link OperationalDatasetTimestamp} object from an {@link Instant}. 75 * 76 * <p>The {@code seconds} is set to {@code instant.getEpochSecond()}, {@code ticks} is set to 77 * {@link instant#getNano()} based on frequency of 32768 Hz, and {@code isAuthoritativeSource} 78 * is set to {@code isAuthoritativeSource}. 79 * 80 * <p>Note that this conversion can lose precision and a value returned by {@link #toInstant} 81 * may not equal exactly the {@code instant}. 82 * 83 * @throws IllegalArgumentException if {@code instant.getEpochSecond()} is larger than {@code 84 * 0xffffffffffffL} 85 * @see toInstant 86 * @hide 87 */ 88 @NonNull fromInstant( @onNull Instant instant, boolean isAuthoritativeSource)89 public static OperationalDatasetTimestamp fromInstant( 90 @NonNull Instant instant, boolean isAuthoritativeSource) { 91 int ticks = getRoundedTicks(instant.getNano()); 92 long seconds = instant.getEpochSecond() + ticks / TICKS_UPPER_BOUND; 93 // the rounded ticks can be 0x8000 if instant.getNano() >= 999984742 94 ticks = ticks % TICKS_UPPER_BOUND; 95 return new OperationalDatasetTimestamp(seconds, ticks, isAuthoritativeSource); 96 } 97 98 /** 99 * Converts this {@link OperationalDatasetTimestamp} object to an {@link Instant}. 100 * 101 * <p>Note that the return value may not equal exactly the {@code instant} if this object is 102 * created with {@link #fromInstant}. 103 * 104 * @see fromInstant 105 */ 106 @NonNull toInstant()107 public Instant toInstant() { 108 long nanos = Math.round((double) mTicks * 1000000000L / TICKS_UPPER_BOUND); 109 return Instant.ofEpochSecond(mSeconds, nanos); 110 } 111 112 /** 113 * Creates a new {@link OperationalDatasetTimestamp} object from the OperationalDatasetTimestamp 114 * TLV value. 115 * 116 * @hide 117 */ 118 @NonNull fromTlvValue(@onNull byte[] encodedTimestamp)119 public static OperationalDatasetTimestamp fromTlvValue(@NonNull byte[] encodedTimestamp) { 120 requireNonNull(encodedTimestamp, "encodedTimestamp cannot be null"); 121 checkArgument( 122 encodedTimestamp.length == LENGTH_TIMESTAMP, 123 "Invalid Thread OperationalDatasetTimestamp length (length = %d," 124 + " expectedLength=%d)", 125 encodedTimestamp.length, 126 LENGTH_TIMESTAMP); 127 long longTimestamp = ByteBuffer.wrap(encodedTimestamp).getLong(); 128 return new OperationalDatasetTimestamp( 129 (longTimestamp >> 16) & 0x0000ffffffffffffL, 130 (int) ((longTimestamp >> 1) & 0x7fffL), 131 (longTimestamp & 0x01) != 0); 132 } 133 134 /** 135 * Converts this {@link OperationalDatasetTimestamp} object to Thread TLV value. 136 * 137 * @hide 138 */ 139 @NonNull toTlvValue()140 public byte[] toTlvValue() { 141 byte[] tlv = new byte[LENGTH_TIMESTAMP]; 142 ByteBuffer buffer = ByteBuffer.wrap(tlv); 143 long encodedValue = (mSeconds << 16) | (mTicks << 1) | (mIsAuthoritativeSource ? 1 : 0); 144 buffer.putLong(encodedValue); 145 return tlv; 146 } 147 148 /** 149 * Creates a new {@link OperationalDatasetTimestamp} object. 150 * 151 * @param seconds the value encodes a Unix Time value. Must be in the range of 152 * 0x0-0xffffffffffffL 153 * @param ticks the value encodes the fractional Unix Time value in 32.768 kHz resolution. Must 154 * be in the range of 0x0-0x7fff 155 * @param isAuthoritativeSource the flag indicates the time was obtained from an authoritative 156 * source: either NTP (Network Time Protocol), GPS (Global Positioning System), cell 157 * network, or other method 158 * @throws IllegalArgumentException if the {@code seconds} is not in range of 159 * 0x0-0xffffffffffffL or {@code ticks} is not in range of 0x0-0x7fff 160 */ OperationalDatasetTimestamp( @ntRangefrom = 0x0, to = 0xffffffffffffL) long seconds, @IntRange(from = 0x0, to = 0x7fff) int ticks, boolean isAuthoritativeSource)161 public OperationalDatasetTimestamp( 162 @IntRange(from = 0x0, to = 0xffffffffffffL) long seconds, 163 @IntRange(from = 0x0, to = 0x7fff) int ticks, 164 boolean isAuthoritativeSource) { 165 checkArgument( 166 seconds >= 0 && seconds <= 0xffffffffffffL, 167 "seconds exceeds allowed range (seconds = %d," 168 + " allowedRange = [0x0, 0xffffffffffffL])", 169 seconds); 170 checkArgument( 171 ticks >= 0 && ticks <= 0x7fff, 172 "ticks exceeds allowed ranged (ticks = %d, allowedRange" + " = [0x0, 0x7fff])", 173 ticks); 174 mSeconds = seconds; 175 mTicks = ticks; 176 mIsAuthoritativeSource = isAuthoritativeSource; 177 } 178 179 /** 180 * Returns the rounded ticks converted from the nano seconds. 181 * 182 * <p>Note that rhe return value can be as large as {@code TICKS_UPPER_BOUND}. 183 */ getRoundedTicks(long nanos)184 private static int getRoundedTicks(long nanos) { 185 return (int) Math.round((double) nanos * TICKS_UPPER_BOUND / 1000000000L); 186 } 187 188 /** Returns the seconds portion of the timestamp. */ getSeconds()189 public @IntRange(from = 0x0, to = 0xffffffffffffL) long getSeconds() { 190 return mSeconds; 191 } 192 193 /** Returns the ticks portion of the timestamp. */ getTicks()194 public @IntRange(from = 0x0, to = 0x7fff) int getTicks() { 195 return mTicks; 196 } 197 198 /** Returns {@code true} if the timestamp comes from an authoritative source. */ isAuthoritativeSource()199 public boolean isAuthoritativeSource() { 200 return mIsAuthoritativeSource; 201 } 202 203 @Override toString()204 public String toString() { 205 StringBuilder sb = new StringBuilder(); 206 sb.append("{seconds=") 207 .append(getSeconds()) 208 .append(", ticks=") 209 .append(getTicks()) 210 .append(", isAuthoritativeSource=") 211 .append(isAuthoritativeSource()) 212 .append(", instant=") 213 .append(toInstant()) 214 .append("}"); 215 return sb.toString(); 216 } 217 218 @Override equals(@ullable Object other)219 public boolean equals(@Nullable Object other) { 220 if (this == other) { 221 return true; 222 } else if (!(other instanceof OperationalDatasetTimestamp)) { 223 return false; 224 } else { 225 OperationalDatasetTimestamp otherTimestamp = (OperationalDatasetTimestamp) other; 226 return mSeconds == otherTimestamp.mSeconds 227 && mTicks == otherTimestamp.mTicks 228 && mIsAuthoritativeSource == otherTimestamp.mIsAuthoritativeSource; 229 } 230 } 231 232 @Override hashCode()233 public int hashCode() { 234 return Objects.hash(mSeconds, mTicks, mIsAuthoritativeSource); 235 } 236 } 237