File: SntpClient.java

package info (click to toggle)
android-platform-frameworks-base 1%3A14~beta1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 326,084 kB
  • sloc: java: 2,032,373; xml: 343,016; cpp: 304,181; python: 3,683; ansic: 2,090; sh: 1,871; makefile: 120; sed: 19
file content (364 lines) | stat: -rw-r--r-- 14,439 bytes parent folder | download | duplicates (2)
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/*
 * Copyright (C) 2008 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;

import android.compat.annotation.UnsupportedAppUsage;
import android.net.sntp.Duration64;
import android.net.sntp.Timestamp64;
import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.TrafficStatsConstants;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.Random;
import java.util.function.Supplier;

/**
 * {@hide}
 *
 * Simple SNTP client class for retrieving network time.
 *
 * Sample usage:
 * <pre>SntpClient client = new SntpClient();
 * if (client.requestTime("time.foo.com")) {
 *     long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
 * }
 * </pre>
 */
public class SntpClient {
    private static final String TAG = "SntpClient";
    private static final boolean DBG = true;

    private static final int REFERENCE_TIME_OFFSET = 16;
    private static final int ORIGINATE_TIME_OFFSET = 24;
    private static final int RECEIVE_TIME_OFFSET = 32;
    private static final int TRANSMIT_TIME_OFFSET = 40;
    private static final int NTP_PACKET_SIZE = 48;

    public static final int STANDARD_NTP_PORT = 123;
    private static final int NTP_MODE_CLIENT = 3;
    private static final int NTP_MODE_SERVER = 4;
    private static final int NTP_MODE_BROADCAST = 5;
    private static final int NTP_VERSION = 3;

    private static final int NTP_LEAP_NOSYNC = 3;
    private static final int NTP_STRATUM_DEATH = 0;
    private static final int NTP_STRATUM_MAX = 15;

    // The source of the current system clock time, replaceable for testing.
    private final Supplier<Instant> mSystemTimeSupplier;

    private final Random mRandom;

    // The last offset calculated from an NTP server response
    private long mClockOffset;

    // The last system time computed from an NTP server response
    private long mNtpTime;

    // The value of SystemClock.elapsedRealtime() corresponding to mNtpTime / mClockOffset
    private long mNtpTimeReference;

    // The round trip (network) time in milliseconds
    private long mRoundTripTime;

    private static class InvalidServerReplyException extends Exception {
        public InvalidServerReplyException(String message) {
            super(message);
        }
    }

    @UnsupportedAppUsage
    public SntpClient() {
        this(Instant::now, defaultRandom());
    }

    @VisibleForTesting
    public SntpClient(Supplier<Instant> systemTimeSupplier, Random random) {
        mSystemTimeSupplier = Objects.requireNonNull(systemTimeSupplier);
        mRandom = Objects.requireNonNull(random);
    }

    /**
     * Sends an SNTP request to the given host and processes the response.
     *
     * @param host host name of the server.
     * @param port port of the server.
     * @param timeout network timeout in milliseconds. the timeout doesn't include the DNS lookup
     *                time, and it applies to each individual query to the resolved addresses of
     *                the NTP server.
     * @param network network over which to send the request.
     * @return true if the transaction was successful.
     */
    public boolean requestTime(String host, int port, int timeout, Network network) {
        final Network networkForResolv = network.getPrivateDnsBypassingCopy();
        try {
            final InetAddress[] addresses = networkForResolv.getAllByName(host);
            for (int i = 0; i < addresses.length; i++) {
                if (requestTime(addresses[i], port, timeout, networkForResolv)) {
                    return true;
                }
            }
        } catch (UnknownHostException e) {
            Log.w(TAG, "Unknown host: " + host);
            EventLogTags.writeNtpFailure(host, e.toString());
        }

        if (DBG) Log.d(TAG, "request time failed");
        return false;
    }

    public boolean requestTime(InetAddress address, int port, int timeout, Network network) {
        DatagramSocket socket = null;
        final int oldTag = TrafficStats.getAndSetThreadStatsTag(
                TrafficStatsConstants.TAG_SYSTEM_NTP);
        try {
            socket = new DatagramSocket();
            network.bindSocket(socket);
            socket.setSoTimeout(timeout);
            byte[] buffer = new byte[NTP_PACKET_SIZE];
            DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, port);

            // set mode = 3 (client) and version = 3
            // mode is in low 3 bits of first byte
            // version is in bits 3-5 of first byte
            buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);

            // get current time and write it to the request packet
            final Instant requestTime = mSystemTimeSupplier.get();
            final Timestamp64 requestTimestamp = Timestamp64.fromInstant(requestTime);

            final Timestamp64 randomizedRequestTimestamp =
                    requestTimestamp.randomizeSubMillis(mRandom);
            final long requestTicks = SystemClock.elapsedRealtime();
            writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, randomizedRequestTimestamp);

            socket.send(request);

            // read the response
            DatagramPacket response = new DatagramPacket(buffer, buffer.length);
            socket.receive(response);
            final long responseTicks = SystemClock.elapsedRealtime();
            final Instant responseTime = requestTime.plusMillis(responseTicks - requestTicks);
            final Timestamp64 responseTimestamp = Timestamp64.fromInstant(responseTime);

            // extract the results
            final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
            final byte mode = (byte) (buffer[0] & 0x7);
            final int stratum = (int) (buffer[1] & 0xff);
            final Timestamp64 referenceTimestamp = readTimeStamp(buffer, REFERENCE_TIME_OFFSET);
            final Timestamp64 originateTimestamp = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
            final Timestamp64 receiveTimestamp = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
            final Timestamp64 transmitTimestamp = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);

            /* Do validation according to RFC */
            checkValidServerReply(leap, mode, stratum, transmitTimestamp, referenceTimestamp,
                    randomizedRequestTimestamp, originateTimestamp);

            long totalTransactionDurationMillis = responseTicks - requestTicks;
            long serverDurationMillis =
                    Duration64.between(receiveTimestamp, transmitTimestamp).toDuration().toMillis();
            long roundTripTimeMillis = totalTransactionDurationMillis - serverDurationMillis;

            Duration clockOffsetDuration = calculateClockOffset(requestTimestamp,
                    receiveTimestamp, transmitTimestamp, responseTimestamp);
            long clockOffsetMillis = clockOffsetDuration.toMillis();

            EventLogTags.writeNtpSuccess(
                    address.toString(), roundTripTimeMillis, clockOffsetMillis);
            if (DBG) {
                Log.d(TAG, "round trip: " + roundTripTimeMillis + "ms, "
                        + "clock offset: " + clockOffsetMillis + "ms");
            }

            // save our results - use the times on this side of the network latency
            // (response rather than request time)
            mClockOffset = clockOffsetMillis;
            mNtpTime = responseTime.plus(clockOffsetDuration).toEpochMilli();
            mNtpTimeReference = responseTicks;
            mRoundTripTime = roundTripTimeMillis;
        } catch (Exception e) {
            EventLogTags.writeNtpFailure(address.toString(), e.toString());
            if (DBG) Log.d(TAG, "request time failed: " + e);
            return false;
        } finally {
            if (socket != null) {
                socket.close();
            }
            TrafficStats.setThreadStatsTag(oldTag);
        }

        return true;
    }

    /** Performs the NTP clock offset calculation. */
    @VisibleForTesting
    public static Duration calculateClockOffset(Timestamp64 clientRequestTimestamp,
            Timestamp64 serverReceiveTimestamp, Timestamp64 serverTransmitTimestamp,
            Timestamp64 clientResponseTimestamp) {
        // According to RFC4330:
        // t is the system clock offset (the adjustment we are trying to find)
        // t = ((T2 - T1) + (T3 - T4)) / 2
        //
        // Which is:
        // t = (([server]receiveTimestamp - [client]requestTimestamp)
        //       + ([server]transmitTimestamp - [client]responseTimestamp)) / 2
        //
        // See the NTP spec and tests: the numeric types used are deliberate:
        // + Duration64.between() uses 64-bit arithmetic (32-bit for the seconds).
        // + plus() / dividedBy() use Duration, which isn't the double precision floating point
        //   used in NTPv4, but is good enough.
        return Duration64.between(clientRequestTimestamp, serverReceiveTimestamp)
                .plus(Duration64.between(clientResponseTimestamp, serverTransmitTimestamp))
                .dividedBy(2);
    }

    @Deprecated
    @UnsupportedAppUsage
    public boolean requestTime(String host, int timeout) {
        Log.w(TAG, "Shame on you for calling the hidden API requestTime()!");
        return false;
    }

    /**
     * Returns the offset calculated to apply to the client clock to arrive at {@link #getNtpTime()}
     */
    @VisibleForTesting
    public long getClockOffset() {
        return mClockOffset;
    }

    /**
     * Returns the time computed from the NTP transaction.
     *
     * @return time value computed from NTP server response.
     */
    @UnsupportedAppUsage
    public long getNtpTime() {
        return mNtpTime;
    }

    /**
     * Returns the reference clock value (value of SystemClock.elapsedRealtime())
     * corresponding to the NTP time.
     *
     * @return reference clock corresponding to the NTP time.
     */
    @UnsupportedAppUsage
    public long getNtpTimeReference() {
        return mNtpTimeReference;
    }

    /**
     * Returns the round trip time of the NTP transaction
     *
     * @return round trip time in milliseconds.
     */
    @UnsupportedAppUsage
    public long getRoundTripTime() {
        return mRoundTripTime;
    }

    private static void checkValidServerReply(
            byte leap, byte mode, int stratum, Timestamp64 transmitTimestamp,
            Timestamp64 referenceTimestamp, Timestamp64 randomizedRequestTimestamp,
            Timestamp64 originateTimestamp) throws InvalidServerReplyException {
        if (leap == NTP_LEAP_NOSYNC) {
            throw new InvalidServerReplyException("unsynchronized server");
        }
        if ((mode != NTP_MODE_SERVER) && (mode != NTP_MODE_BROADCAST)) {
            throw new InvalidServerReplyException("untrusted mode: " + mode);
        }
        if ((stratum == NTP_STRATUM_DEATH) || (stratum > NTP_STRATUM_MAX)) {
            throw new InvalidServerReplyException("untrusted stratum: " + stratum);
        }
        if (!randomizedRequestTimestamp.equals(originateTimestamp)) {
            throw new InvalidServerReplyException(
                    "originateTimestamp != randomizedRequestTimestamp");
        }
        if (transmitTimestamp.equals(Timestamp64.ZERO)) {
            throw new InvalidServerReplyException("zero transmitTimestamp");
        }
        if (referenceTimestamp.equals(Timestamp64.ZERO)) {
            throw new InvalidServerReplyException("zero referenceTimestamp");
        }
    }

    /**
     * Reads an unsigned 32 bit big endian number from the given offset in the buffer.
     */
    private long readUnsigned32(byte[] buffer, int offset) {
        int i0 = buffer[offset++] & 0xFF;
        int i1 = buffer[offset++] & 0xFF;
        int i2 = buffer[offset++] & 0xFF;
        int i3 = buffer[offset] & 0xFF;

        int bits = (i0 << 24) | (i1 << 16) | (i2 << 8) | i3;
        return bits & 0xFFFF_FFFFL;
    }

    /**
     * Reads the NTP time stamp from the given offset in the buffer.
     */
    private Timestamp64 readTimeStamp(byte[] buffer, int offset) {
        long seconds = readUnsigned32(buffer, offset);
        int fractionBits = (int) readUnsigned32(buffer, offset + 4);
        return Timestamp64.fromComponents(seconds, fractionBits);
    }

    /**
     * Writes the NTP time stamp at the given offset in the buffer.
     */
    private void writeTimeStamp(byte[] buffer, int offset, Timestamp64 timestamp) {
        long seconds = timestamp.getEraSeconds();
        // write seconds in big endian format
        buffer[offset++] = (byte) (seconds >>> 24);
        buffer[offset++] = (byte) (seconds >>> 16);
        buffer[offset++] = (byte) (seconds >>> 8);
        buffer[offset++] = (byte) (seconds);

        int fractionBits = timestamp.getFractionBits();
        // write fraction in big endian format
        buffer[offset++] = (byte) (fractionBits >>> 24);
        buffer[offset++] = (byte) (fractionBits >>> 16);
        buffer[offset++] = (byte) (fractionBits >>> 8);
        buffer[offset] = (byte) (fractionBits);
    }

    private static Random defaultRandom() {
        Random random;
        try {
            random = SecureRandom.getInstanceStrong();
        } catch (NoSuchAlgorithmException e) {
            // This should never happen.
            Slog.wtf(TAG, "Unable to access SecureRandom", e);
            random = new Random(System.currentTimeMillis());
        }
        return random;
    }
}