• 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.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