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