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