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