1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.sntp;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import java.time.Instant;
import java.util.Objects;
import java.util.Random;
/**
* The 64-bit type ("timestamp") that NTP uses to represent a point in time. It only holds the
* lowest 32-bits of the number of seconds since 1900-01-01 00:00:00. Consequently, to turn an
* instance into an unambiguous point in time the era number must be known. Era zero runs from
* 1900-01-01 00:00:00 to a date in 2036.
*
* It stores sub-second values using a 32-bit fixed point type, so it can resolve values smaller
* than a nanosecond, but is imprecise (i.e. it truncates).
*
* See also <a href=https://www.eecis.udel.edu/~mills/y2k.html>NTP docs</a>.
*
* @hide
*/
public final class Timestamp64 {
public static final Timestamp64 ZERO = fromComponents(0, 0);
static final int SUB_MILLIS_BITS_TO_RANDOMIZE = 32 - 10;
// Number of seconds between Jan 1, 1900 and Jan 1, 1970
// 70 years plus 17 leap days
static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;
static final long MAX_SECONDS_IN_ERA = 0xFFFF_FFFFL;
static final long SECONDS_IN_ERA = MAX_SECONDS_IN_ERA + 1;
static final int NANOS_PER_SECOND = 1_000_000_000;
/** Creates a {@link Timestamp64} from the seconds and fraction components. */
public static Timestamp64 fromComponents(long eraSeconds, int fractionBits) {
return new Timestamp64(eraSeconds, fractionBits);
}
/** Creates a {@link Timestamp64} by decoding a string in the form "e4dc720c.4d4fc9eb". */
public static Timestamp64 fromString(String string) {
final int requiredLength = 17;
if (string.length() != requiredLength || string.charAt(8) != '.') {
throw new IllegalArgumentException(string);
}
String eraSecondsString = string.substring(0, 8);
String fractionString = string.substring(9);
long eraSeconds = Long.parseLong(eraSecondsString, 16);
// Use parseLong() because the type is unsigned. Integer.parseInt() will reject 0x70000000
// or above as being out of range.
long fractionBitsAsLong = Long.parseLong(fractionString, 16);
if (fractionBitsAsLong < 0 || fractionBitsAsLong > 0xFFFFFFFFL) {
throw new IllegalArgumentException("Invalid fractionBits:" + fractionString);
}
return new Timestamp64(eraSeconds, (int) fractionBitsAsLong);
}
/**
* Converts an {@link Instant} into a {@link Timestamp64}. This is lossy: Timestamp64 only
* contains the number of seconds in a given era, but the era is not stored. Also, sub-second
* values are not stored precisely.
*/
public static Timestamp64 fromInstant(Instant instant) {
long ntpEraSeconds = instant.getEpochSecond() + OFFSET_1900_TO_1970;
if (ntpEraSeconds < 0) {
ntpEraSeconds = SECONDS_IN_ERA - (-ntpEraSeconds % SECONDS_IN_ERA);
}
ntpEraSeconds %= SECONDS_IN_ERA;
long nanos = instant.getNano();
int fractionBits = nanosToFractionBits(nanos);
return new Timestamp64(ntpEraSeconds, fractionBits);
}
private final long mEraSeconds;
private final int mFractionBits;
private Timestamp64(long eraSeconds, int fractionBits) {
if (eraSeconds < 0 || eraSeconds > MAX_SECONDS_IN_ERA) {
throw new IllegalArgumentException(
"Invalid parameters. seconds=" + eraSeconds + ", fraction=" + fractionBits);
}
this.mEraSeconds = eraSeconds;
this.mFractionBits = fractionBits;
}
/** Returns the number of seconds in the NTP era. */
public long getEraSeconds() {
return mEraSeconds;
}
/** Returns the fraction of a second as 32-bit, unsigned fixed-point bits. */
public int getFractionBits() {
return mFractionBits;
}
@Override
public String toString() {
return TextUtils.formatSimple("%08x.%08x", mEraSeconds, mFractionBits);
}
/** Returns the instant represented by this value in the specified NTP era. */
public Instant toInstant(int ntpEra) {
long secondsSinceEpoch = mEraSeconds - OFFSET_1900_TO_1970;
secondsSinceEpoch += ntpEra * SECONDS_IN_ERA;
int nanos = fractionBitsToNanos(mFractionBits);
return Instant.ofEpochSecond(secondsSinceEpoch, nanos);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Timestamp64 that = (Timestamp64) o;
return mEraSeconds == that.mEraSeconds && mFractionBits == that.mFractionBits;
}
@Override
public int hashCode() {
return Objects.hash(mEraSeconds, mFractionBits);
}
static int fractionBitsToNanos(int fractionBits) {
long fractionBitsLong = fractionBits & 0xFFFF_FFFFL;
return (int) ((fractionBitsLong * NANOS_PER_SECOND) >>> 32);
}
static int nanosToFractionBits(long nanos) {
if (nanos > NANOS_PER_SECOND) {
throw new IllegalArgumentException();
}
return (int) ((nanos << 32) / NANOS_PER_SECOND);
}
/**
* Randomizes the fraction bits that represent sub-millisecond values. i.e. the randomization
* won't change the number of milliseconds represented after truncation. This is used to
* implement the part of the NTP spec that calls for clients with millisecond accuracy clocks
* to send randomized LSB values rather than zeros.
*/
public Timestamp64 randomizeSubMillis(Random random) {
int randomizedFractionBits =
randomizeLowestBits(random, this.mFractionBits, SUB_MILLIS_BITS_TO_RANDOMIZE);
return new Timestamp64(mEraSeconds, randomizedFractionBits);
}
/**
* Randomizes the specified number of LSBs in {@code value} by using replacement bits from
* {@code Random.getNextInt()}.
*/
@VisibleForTesting
public static int randomizeLowestBits(Random random, int value, int bitsToRandomize) {
if (bitsToRandomize < 1 || bitsToRandomize >= Integer.SIZE) {
// There's no point in randomizing all bits or none of the bits.
throw new IllegalArgumentException(Integer.toString(bitsToRandomize));
}
int upperBitMask = 0xFFFF_FFFF << bitsToRandomize;
int lowerBitMask = ~upperBitMask;
int randomValue = random.nextInt();
return (value & upperBitMask) | (randomValue & lowerBitMask);
}
}
|