1 /*
2  * Copyright (C) 2021 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 package android.net.sntp;
17 
18 import java.time.Duration;
19 
20 /**
21  * A type similar to {@link Timestamp64} but used when calculating the difference between two
22  * timestamps. As such, it is a signed type, but still uses 64-bits in total and so can only
23  * represent half the magnitude of {@link Timestamp64}.
24  *
25  * <p>See <a href="https://www.eecis.udel.edu/~mills/time.html">4. Time Difference Calculations</a>.
26  *
27  * @hide
28  */
29 public final class Duration64 {
30 
31     public static final Duration64 ZERO = new Duration64(0);
32     private final long mBits;
33 
Duration64(long bits)34     private Duration64(long bits) {
35         this.mBits = bits;
36     }
37 
38     /**
39      * Returns the difference between two 64-bit NTP timestamps as a {@link Duration64}, as
40      * described in the NTP spec. The times represented by the timestamps have to be within {@link
41      * Timestamp64#MAX_SECONDS_IN_ERA} (~68 years) of each other for the calculation to produce a
42      * correct answer.
43      */
between(Timestamp64 startInclusive, Timestamp64 endExclusive)44     public static Duration64 between(Timestamp64 startInclusive, Timestamp64 endExclusive) {
45         long oneBits = (startInclusive.getEraSeconds() << 32)
46                 | (startInclusive.getFractionBits() & 0xFFFF_FFFFL);
47         long twoBits = (endExclusive.getEraSeconds() << 32)
48                 | (endExclusive.getFractionBits() & 0xFFFF_FFFFL);
49         long resultBits = twoBits - oneBits;
50         return new Duration64(resultBits);
51     }
52 
53     /**
54      * Add two {@link Duration64} instances together. This performs the calculation in {@link
55      * Duration} and returns a {@link Duration} to increase the magnitude of accepted arguments,
56      * since {@link Duration64} only supports signed 32-bit seconds. The use of {@link Duration}
57      * limits precision to nanoseconds.
58      */
plus(Duration64 other)59     public Duration plus(Duration64 other) {
60         // From https://www.eecis.udel.edu/~mills/time.html:
61         // "The offset and delay calculations require sums and differences of these raw timestamp
62         // differences that can span no more than from 34 years in the future to 34 years in the
63         // past without overflow. This is a fundamental limitation in 64-bit integer calculations.
64         //
65         // In the NTPv4 reference implementation, all calculations involving offset and delay values
66         // use 64-bit floating double arithmetic, with the exception of raw timestamp subtraction,
67         // as mentioned above. The raw timestamp differences are then converted to 64-bit floating
68         // double format without loss of precision or chance of overflow in subsequent
69         // calculations."
70         //
71         // Here, we use Duration instead, which provides sufficient range, but loses precision below
72         // nanos.
73         return this.toDuration().plus(other.toDuration());
74     }
75 
76     /**
77      * Returns a {@link Duration64} equivalent of the supplied duration, if the magnitude can be
78      * represented. Because {@link Duration64} uses a fixed point type for sub-second values it
79      * cannot represent all nanosecond values precisely and so the conversion can be lossy.
80      *
81      * @throws IllegalArgumentException if the supplied duration is too big to be represented
82      */
fromDuration(Duration duration)83     public static Duration64 fromDuration(Duration duration) {
84         long seconds = duration.getSeconds();
85         if (seconds < Integer.MIN_VALUE || seconds > Integer.MAX_VALUE) {
86             throw new IllegalArgumentException();
87         }
88         long bits = (seconds << 32)
89                 | (Timestamp64.nanosToFractionBits(duration.getNano()) & 0xFFFF_FFFFL);
90         return new Duration64(bits);
91     }
92 
93     /**
94      * Returns a {@link Duration} equivalent of this duration. Because {@link Duration64} uses a
95      * fixed point type for sub-second values it can values smaller than nanosecond precision and so
96      * the conversion can be lossy.
97      */
toDuration()98     public Duration toDuration() {
99         int seconds = getSeconds();
100         int nanos = getNanos();
101         return Duration.ofSeconds(seconds, nanos);
102     }
103 
104     @Override
equals(Object o)105     public boolean equals(Object o) {
106         if (this == o) {
107             return true;
108         }
109         if (o == null || getClass() != o.getClass()) {
110             return false;
111         }
112         Duration64 that = (Duration64) o;
113         return mBits == that.mBits;
114     }
115 
116     @Override
hashCode()117     public int hashCode() {
118         return java.util.Objects.hash(mBits);
119     }
120 
121     @Override
toString()122     public String toString() {
123         Duration duration = toDuration();
124         return Long.toHexString(mBits)
125                 + "(" + duration.getSeconds() + "s " + duration.getNano() + "ns)";
126     }
127 
128     /**
129      * Returns the <em>signed</em> seconds in this duration.
130      */
getSeconds()131     public int getSeconds() {
132         return (int) (mBits >> 32);
133     }
134 
135     /**
136      * Returns the <em>unsigned</em> nanoseconds in this duration (truncated).
137      */
getNanos()138     public int getNanos() {
139         return Timestamp64.fractionBitsToNanos((int) (mBits & 0xFFFF_FFFFL));
140     }
141 }
142