forked from I2P_Developers/i2p.i2p
* Timestamper:
- Move from core to router, leave stub in core so it doesn't break compatibility. This removes a thread in app context and prevents any app context from running NTP; external clients must use the time received from the router. - Increase query interval
This commit is contained in:
@@ -1,209 +0,0 @@
|
||||
package net.i2p.time;
|
||||
/*
|
||||
* Copyright (c) 2004, Adam Buckley
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of Adam Buckley nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
|
||||
/**
|
||||
* NtpClient - an NTP client for Java. This program connects to an NTP server
|
||||
* and prints the response to the console.
|
||||
*
|
||||
* The local clock offset calculation is implemented according to the SNTP
|
||||
* algorithm specified in RFC 2030.
|
||||
*
|
||||
* Note that on windows platforms, the curent time-of-day timestamp is limited
|
||||
* to an resolution of 10ms and adversely affects the accuracy of the results.
|
||||
*
|
||||
* @author Adam Buckley
|
||||
* (minor refactoring by jrandom)
|
||||
*/
|
||||
public class NtpClient {
|
||||
/** difference between the unix epoch and jan 1 1900 (NTP uses that) */
|
||||
private final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
|
||||
private final static int NTP_PORT = 123;
|
||||
|
||||
/**
|
||||
* Query the ntp servers, returning the current time from first one we find
|
||||
*
|
||||
* @return milliseconds since january 1, 1970 (UTC)
|
||||
* @throws IllegalArgumentException if none of the servers are reachable
|
||||
*/
|
||||
public static long currentTime(String serverNames[]) {
|
||||
if (serverNames == null)
|
||||
throw new IllegalArgumentException("No NTP servers specified");
|
||||
ArrayList names = new ArrayList(serverNames.length);
|
||||
for (int i = 0; i < serverNames.length; i++)
|
||||
names.add(serverNames[i]);
|
||||
Collections.shuffle(names);
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
long now = currentTime((String)names.get(i));
|
||||
if (now > 0)
|
||||
return now;
|
||||
}
|
||||
throw new IllegalArgumentException("No reachable NTP servers specified");
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the ntp servers, returning the current time from first one we find
|
||||
* Hack to return time and stratum
|
||||
* @return time in rv[0] and stratum in rv[1]
|
||||
* @throws IllegalArgumentException if none of the servers are reachable
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public static long[] currentTimeAndStratum(String serverNames[]) {
|
||||
if (serverNames == null)
|
||||
throw new IllegalArgumentException("No NTP servers specified");
|
||||
ArrayList names = new ArrayList(serverNames.length);
|
||||
for (int i = 0; i < serverNames.length; i++)
|
||||
names.add(serverNames[i]);
|
||||
Collections.shuffle(names);
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
long[] rv = currentTimeAndStratum((String)names.get(i));
|
||||
if (rv != null && rv[0] > 0)
|
||||
return rv;
|
||||
}
|
||||
throw new IllegalArgumentException("No reachable NTP servers specified");
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the given NTP server, returning the current internet time
|
||||
*
|
||||
* @return milliseconds since january 1, 1970 (UTC), or -1 on error
|
||||
*/
|
||||
public static long currentTime(String serverName) {
|
||||
long[] la = currentTimeAndStratum(serverName);
|
||||
if (la != null)
|
||||
return la[0];
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack to return time and stratum
|
||||
* @return time in rv[0] and stratum in rv[1], or null for error
|
||||
* @since 0.7.12
|
||||
*/
|
||||
private static long[] currentTimeAndStratum(String serverName) {
|
||||
try {
|
||||
// Send request
|
||||
DatagramSocket socket = new DatagramSocket();
|
||||
InetAddress address = InetAddress.getByName(serverName);
|
||||
byte[] buf = new NtpMessage().toByteArray();
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT);
|
||||
|
||||
// Set the transmit timestamp *just* before sending the packet
|
||||
// ToDo: Does this actually improve performance or not?
|
||||
NtpMessage.encodeTimestamp(packet.getData(), 40,
|
||||
(System.currentTimeMillis()/1000.0)
|
||||
+ SECONDS_1900_TO_EPOCH);
|
||||
|
||||
socket.send(packet);
|
||||
|
||||
// Get response
|
||||
packet = new DatagramPacket(buf, buf.length);
|
||||
socket.setSoTimeout(10*1000);
|
||||
try {
|
||||
socket.receive(packet);
|
||||
} catch (InterruptedIOException iie) {
|
||||
socket.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Immediately record the incoming timestamp
|
||||
double destinationTimestamp = (System.currentTimeMillis()/1000.0) + SECONDS_1900_TO_EPOCH;
|
||||
|
||||
// Process response
|
||||
NtpMessage msg = new NtpMessage(packet.getData());
|
||||
|
||||
//double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
|
||||
// (msg.receiveTimestamp-msg.transmitTimestamp);
|
||||
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
|
||||
(msg.transmitTimestamp - destinationTimestamp)) / 2;
|
||||
socket.close();
|
||||
|
||||
// Stratum must be between 1 (atomic) and 15 (maximum defined value)
|
||||
// Anything else is right out, treat such responses like errors
|
||||
if ((msg.stratum < 1) || (msg.stratum > 15)) {
|
||||
//System.out.println("Response from NTP server of unacceptable stratum " + msg.stratum + ", failing.");
|
||||
return null;
|
||||
}
|
||||
|
||||
long[] rv = new long[2];
|
||||
rv[0] = (long)(System.currentTimeMillis() + localClockOffset*1000);
|
||||
rv[1] = msg.stratum;
|
||||
//System.out.println("host: " + address.getHostAddress() + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds");
|
||||
return rv;
|
||||
} catch (IOException ioe) {
|
||||
//ioe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
// Process command-line args
|
||||
if(args.length <= 0) {
|
||||
printUsage();
|
||||
return;
|
||||
// args = new String[] { "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
|
||||
}
|
||||
|
||||
long now = currentTime(args);
|
||||
System.out.println("Current time: " + new java.util.Date(now));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Prints usage
|
||||
*/
|
||||
static void printUsage() {
|
||||
System.out.println(
|
||||
"NtpClient - an NTP client for Java.\n" +
|
||||
"\n" +
|
||||
"This program connects to an NTP server and prints the current time to the console.\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"Usage: java NtpClient server[ server]*\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"This program is copyright (c) Adam Buckley 2004 and distributed under the terms\n" +
|
||||
"of the GNU General Public License. This program is distributed in the hope\n" +
|
||||
"that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n" +
|
||||
"warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" +
|
||||
"General Public License available at http://www.gnu.org/licenses/gpl.html for\n" +
|
||||
"more details.");
|
||||
|
||||
}
|
||||
}
|
@@ -1,467 +0,0 @@
|
||||
package net.i2p.time;
|
||||
/*
|
||||
* Copyright (c) 2004, Adam Buckley
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of Adam Buckley nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* This class represents a NTP message, as specified in RFC 2030. The message
|
||||
* format is compatible with all versions of NTP and SNTP.
|
||||
*
|
||||
* This class does not support the optional authentication protocol, and
|
||||
* ignores the key ID and message digest fields.
|
||||
*
|
||||
* For convenience, this class exposes message values as native Java types, not
|
||||
* the NTP-specified data formats. For example, timestamps are
|
||||
* stored as doubles (as opposed to the NTP unsigned 64-bit fixed point
|
||||
* format).
|
||||
*
|
||||
* However, the contructor NtpMessage(byte[]) and the method toByteArray()
|
||||
* allow the import and export of the raw NTP message format.
|
||||
*
|
||||
*
|
||||
* Usage example
|
||||
*
|
||||
* // Send message
|
||||
* DatagramSocket socket = new DatagramSocket();
|
||||
* InetAddress address = InetAddress.getByName("ntp.cais.rnp.br");
|
||||
* byte[] buf = new NtpMessage().toByteArray();
|
||||
* DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 123);
|
||||
* socket.send(packet);
|
||||
*
|
||||
* // Get response
|
||||
* socket.receive(packet);
|
||||
* System.out.println(msg.toString());
|
||||
*
|
||||
* Comments for member variables are taken from RFC2030 by David Mills,
|
||||
* University of Delaware.
|
||||
*
|
||||
* Number format conversion code in NtpMessage(byte[] array) and toByteArray()
|
||||
* inspired by http://www.pps.jussieu.fr/~jch/enseignement/reseaux/
|
||||
* NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
|
||||
*
|
||||
* @author Adam Buckley
|
||||
*/
|
||||
public class NtpMessage {
|
||||
/**
|
||||
* This is a two-bit code warning of an impending leap second to be
|
||||
* inserted/deleted in the last minute of the current day. It's values
|
||||
* may be as follows:
|
||||
*
|
||||
* Value Meaning
|
||||
* ----- -------
|
||||
* 0 no warning
|
||||
* 1 last minute has 61 seconds
|
||||
* 2 last minute has 59 seconds)
|
||||
* 3 alarm condition (clock not synchronized)
|
||||
*/
|
||||
public byte leapIndicator = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the NTP/SNTP version number. The version number
|
||||
* is 3 for Version 3 (IPv4 only) and 4 for Version 4 (IPv4, IPv6 and OSI).
|
||||
* If necessary to distinguish between IPv4, IPv6 and OSI, the
|
||||
* encapsulating context must be inspected.
|
||||
*/
|
||||
public byte version = 3;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the mode, with values defined as follows:
|
||||
*
|
||||
* Mode Meaning
|
||||
* ---- -------
|
||||
* 0 reserved
|
||||
* 1 symmetric active
|
||||
* 2 symmetric passive
|
||||
* 3 client
|
||||
* 4 server
|
||||
* 5 broadcast
|
||||
* 6 reserved for NTP control message
|
||||
* 7 reserved for private use
|
||||
*
|
||||
* In unicast and anycast modes, the client sets this field to 3 (client)
|
||||
* in the request and the server sets it to 4 (server) in the reply. In
|
||||
* multicast mode, the server sets this field to 5 (broadcast).
|
||||
*/
|
||||
public byte mode = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the stratum level of the local clock, with values
|
||||
* defined as follows:
|
||||
*
|
||||
* Stratum Meaning
|
||||
* ----------------------------------------------
|
||||
* 0 unspecified or unavailable
|
||||
* 1 primary reference (e.g., radio clock)
|
||||
* 2-15 secondary reference (via NTP or SNTP)
|
||||
* 16-255 reserved
|
||||
*/
|
||||
public short stratum = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the maximum interval between successive messages,
|
||||
* in seconds to the nearest power of two. The values that can appear in
|
||||
* this field presently range from 4 (16 s) to 14 (16284 s); however, most
|
||||
* applications use only the sub-range 6 (64 s) to 10 (1024 s).
|
||||
*/
|
||||
public byte pollInterval = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the precision of the local clock, in seconds to
|
||||
* the nearest power of two. The values that normally appear in this field
|
||||
* range from -6 for mains-frequency clocks to -20 for microsecond clocks
|
||||
* found in some workstations.
|
||||
*/
|
||||
public byte precision = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the total roundtrip delay to the primary reference
|
||||
* source, in seconds. Note that this variable can take on both positive
|
||||
* and negative values, depending on the relative time and frequency
|
||||
* offsets. The values that normally appear in this field range from
|
||||
* negative values of a few milliseconds to positive values of several
|
||||
* hundred milliseconds.
|
||||
*/
|
||||
public double rootDelay = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the nominal error relative to the primary reference
|
||||
* source, in seconds. The values that normally appear in this field
|
||||
* range from 0 to several hundred milliseconds.
|
||||
*/
|
||||
public double rootDispersion = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is a 4-byte array identifying the particular reference source.
|
||||
* In the case of NTP Version 3 or Version 4 stratum-0 (unspecified) or
|
||||
* stratum-1 (primary) servers, this is a four-character ASCII string, left
|
||||
* justified and zero padded to 32 bits. In NTP Version 3 secondary
|
||||
* servers, this is the 32-bit IPv4 address of the reference source. In NTP
|
||||
* Version 4 secondary servers, this is the low order 32 bits of the latest
|
||||
* transmit timestamp of the reference source. NTP primary (stratum 1)
|
||||
* servers should set this field to a code identifying the external
|
||||
* reference source according to the following list. If the external
|
||||
* reference is one of those listed, the associated code should be used.
|
||||
* Codes for sources not listed can be contrived as appropriate.
|
||||
*
|
||||
* Code External Reference Source
|
||||
* ---- -------------------------
|
||||
* LOCL uncalibrated local clock used as a primary reference for
|
||||
* a subnet without external means of synchronization
|
||||
* PPS atomic clock or other pulse-per-second source
|
||||
* individually calibrated to national standards
|
||||
* ACTS NIST dialup modem service
|
||||
* USNO USNO modem service
|
||||
* PTB PTB (Germany) modem service
|
||||
* TDF Allouis (France) Radio 164 kHz
|
||||
* DCF Mainflingen (Germany) Radio 77.5 kHz
|
||||
* MSF Rugby (UK) Radio 60 kHz
|
||||
* WWV Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz
|
||||
* WWVB Boulder (US) Radio 60 kHz
|
||||
* WWVH Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz
|
||||
* CHU Ottawa (Canada) Radio 3330, 7335, 14670 kHz
|
||||
* LORC LORAN-C radionavigation system
|
||||
* OMEG OMEGA radionavigation system
|
||||
* GPS Global Positioning Service
|
||||
* GOES Geostationary Orbit Environment Satellite
|
||||
*/
|
||||
public byte[] referenceIdentifier = {0, 0, 0, 0};
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the local clock was last set or corrected, in
|
||||
* seconds since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double referenceTimestamp = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the request departed the client for the
|
||||
* server, in seconds since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double originateTimestamp = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the request arrived at the server, in seconds
|
||||
* since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double receiveTimestamp = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the reply departed the server for the client,
|
||||
* in seconds since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double transmitTimestamp = 0;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new NtpMessage from an array of bytes.
|
||||
*/
|
||||
public NtpMessage(byte[] array) {
|
||||
// See the packet format diagram in RFC 2030 for details
|
||||
leapIndicator = (byte) ((array[0] >> 6) & 0x3);
|
||||
version = (byte) ((array[0] >> 3) & 0x7);
|
||||
mode = (byte) (array[0] & 0x7);
|
||||
stratum = unsignedByteToShort(array[1]);
|
||||
pollInterval = array[2];
|
||||
precision = array[3];
|
||||
|
||||
rootDelay = (array[4] * 256.0) +
|
||||
unsignedByteToShort(array[5]) +
|
||||
(unsignedByteToShort(array[6]) / 256.0) +
|
||||
(unsignedByteToShort(array[7]) / 65536.0);
|
||||
|
||||
rootDispersion = (unsignedByteToShort(array[8]) * 256.0) +
|
||||
unsignedByteToShort(array[9]) +
|
||||
(unsignedByteToShort(array[10]) / 256.0) +
|
||||
(unsignedByteToShort(array[11]) / 65536.0);
|
||||
|
||||
referenceIdentifier[0] = array[12];
|
||||
referenceIdentifier[1] = array[13];
|
||||
referenceIdentifier[2] = array[14];
|
||||
referenceIdentifier[3] = array[15];
|
||||
|
||||
referenceTimestamp = decodeTimestamp(array, 16);
|
||||
originateTimestamp = decodeTimestamp(array, 24);
|
||||
receiveTimestamp = decodeTimestamp(array, 32);
|
||||
transmitTimestamp = decodeTimestamp(array, 40);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new NtpMessage in client -> server mode, and sets the
|
||||
* transmit timestamp to the current time.
|
||||
*/
|
||||
public NtpMessage() {
|
||||
// Note that all the other member variables are already set with
|
||||
// appropriate default values.
|
||||
this.mode = 3;
|
||||
this.transmitTimestamp = (System.currentTimeMillis()/1000.0) + 2208988800.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This method constructs the data bytes of a raw NTP packet.
|
||||
*/
|
||||
public byte[] toByteArray() {
|
||||
// All bytes are automatically set to 0
|
||||
byte[] p = new byte[48];
|
||||
|
||||
p[0] = (byte) (leapIndicator << 6 | version << 3 | mode);
|
||||
p[1] = (byte) stratum;
|
||||
p[2] = pollInterval;
|
||||
p[3] = precision;
|
||||
|
||||
// root delay is a signed 16.16-bit FP, in Java an int is 32-bits
|
||||
int l = (int) (rootDelay * 65536.0);
|
||||
p[4] = (byte) ((l >> 24) & 0xFF);
|
||||
p[5] = (byte) ((l >> 16) & 0xFF);
|
||||
p[6] = (byte) ((l >> 8) & 0xFF);
|
||||
p[7] = (byte) (l & 0xFF);
|
||||
|
||||
// root dispersion is an unsigned 16.16-bit FP, in Java there are no
|
||||
// unsigned primitive types, so we use a long which is 64-bits
|
||||
long ul = (long) (rootDispersion * 65536.0);
|
||||
p[8] = (byte) ((ul >> 24) & 0xFF);
|
||||
p[9] = (byte) ((ul >> 16) & 0xFF);
|
||||
p[10] = (byte) ((ul >> 8) & 0xFF);
|
||||
p[11] = (byte) (ul & 0xFF);
|
||||
|
||||
p[12] = referenceIdentifier[0];
|
||||
p[13] = referenceIdentifier[1];
|
||||
p[14] = referenceIdentifier[2];
|
||||
p[15] = referenceIdentifier[3];
|
||||
|
||||
encodeTimestamp(p, 16, referenceTimestamp);
|
||||
encodeTimestamp(p, 24, originateTimestamp);
|
||||
encodeTimestamp(p, 32, receiveTimestamp);
|
||||
encodeTimestamp(p, 40, transmitTimestamp);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of a NtpMessage
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
String precisionStr = new DecimalFormat("0.#E0").format(Math.pow(2, precision));
|
||||
|
||||
return "Leap indicator: " + leapIndicator + "\n" +
|
||||
"Version: " + version + "\n" +
|
||||
"Mode: " + mode + "\n" +
|
||||
"Stratum: " + stratum + "\n" +
|
||||
"Poll: " + pollInterval + "\n" +
|
||||
"Precision: " + precision + " (" + precisionStr + " seconds)\n" +
|
||||
"Root delay: " + new DecimalFormat("0.00").format(rootDelay*1000) + " ms\n" +
|
||||
"Root dispersion: " + new DecimalFormat("0.00").format(rootDispersion*1000) + " ms\n" +
|
||||
"Reference identifier: " + referenceIdentifierToString(referenceIdentifier, stratum, version) + "\n" +
|
||||
"Reference timestamp: " + timestampToString(referenceTimestamp) + "\n" +
|
||||
"Originate timestamp: " + timestampToString(originateTimestamp) + "\n" +
|
||||
"Receive timestamp: " + timestampToString(receiveTimestamp) + "\n" +
|
||||
"Transmit timestamp: " + timestampToString(transmitTimestamp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Converts an unsigned byte to a short. By default, Java assumes that
|
||||
* a byte is signed.
|
||||
*/
|
||||
public static short unsignedByteToShort(byte b) {
|
||||
if((b & 0x80)==0x80)
|
||||
return (short) (128 + (b & 0x7f));
|
||||
else
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Will read 8 bytes of a message beginning at <code>pointer</code>
|
||||
* and return it as a double, according to the NTP 64-bit timestamp
|
||||
* format.
|
||||
*/
|
||||
public static double decodeTimestamp(byte[] array, int pointer) {
|
||||
double r = 0.0;
|
||||
|
||||
for(int i=0; i<8; i++) {
|
||||
r += unsignedByteToShort(array[pointer+i]) * Math.pow(2, (3-i)*8);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Encodes a timestamp in the specified position in the message
|
||||
*/
|
||||
public static void encodeTimestamp(byte[] array, int pointer, double timestamp) {
|
||||
// Converts a double into a 64-bit fixed point
|
||||
for(int i=0; i<8; i++) {
|
||||
// 2^24, 2^16, 2^8, .. 2^-32
|
||||
double base = Math.pow(2, (3-i)*8);
|
||||
|
||||
// Capture byte value
|
||||
array[pointer+i] = (byte) (timestamp / base);
|
||||
|
||||
// Subtract captured value from remaining total
|
||||
timestamp = timestamp - (unsignedByteToShort(array[pointer+i]) * base);
|
||||
}
|
||||
|
||||
// From RFC 2030: It is advisable to fill the non-significant
|
||||
// low order bits of the timestamp with a random, unbiased
|
||||
// bitstring, both to avoid systematic roundoff errors and as
|
||||
// a means of loop detection and replay detection.
|
||||
array[7+pointer] = (byte) (RandomSource.getInstance().nextInt());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a timestamp (number of seconds since 00:00 1-Jan-1900) as a
|
||||
* formatted date/time string.
|
||||
*/
|
||||
public static String timestampToString(double timestamp) {
|
||||
if(timestamp==0) return "0";
|
||||
|
||||
// timestamp is relative to 1900, utc is used by Java and is relative
|
||||
// to 1970
|
||||
double utc = timestamp - (2208988800.0);
|
||||
|
||||
// milliseconds
|
||||
long ms = (long) (utc * 1000.0);
|
||||
|
||||
// date/time
|
||||
String date = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss").format(new Date(ms));
|
||||
|
||||
// fraction
|
||||
double fraction = timestamp - ((long) timestamp);
|
||||
String fractionSting = new DecimalFormat(".000000").format(fraction);
|
||||
|
||||
return date + fractionSting;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of a reference identifier according
|
||||
* to the rules set out in RFC 2030.
|
||||
*/
|
||||
public static String referenceIdentifierToString(byte[] ref, short stratum, byte version) {
|
||||
// From the RFC 2030:
|
||||
// In the case of NTP Version 3 or Version 4 stratum-0 (unspecified)
|
||||
// or stratum-1 (primary) servers, this is a four-character ASCII
|
||||
// string, left justified and zero padded to 32 bits.
|
||||
if(stratum==0 || stratum==1) {
|
||||
return new String(ref);
|
||||
}
|
||||
|
||||
// In NTP Version 3 secondary servers, this is the 32-bit IPv4
|
||||
// address of the reference source.
|
||||
else if(version==3) {
|
||||
return unsignedByteToShort(ref[0]) + "." +
|
||||
unsignedByteToShort(ref[1]) + "." +
|
||||
unsignedByteToShort(ref[2]) + "." +
|
||||
unsignedByteToShort(ref[3]);
|
||||
}
|
||||
|
||||
// In NTP Version 4 secondary servers, this is the low order 32 bits
|
||||
// of the latest transmit timestamp of the reference source.
|
||||
else if(version==4) {
|
||||
return "" + ((unsignedByteToShort(ref[0]) / 256.0) +
|
||||
(unsignedByteToShort(ref[1]) / 65536.0) +
|
||||
(unsignedByteToShort(ref[2]) / 16777216.0) +
|
||||
(unsignedByteToShort(ref[3]) / 4294967296.0));
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
@@ -1,336 +1,31 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Periodically query a series of NTP servers and update any associated
|
||||
* listeners. It tries the NTP servers in order, contacting them using
|
||||
* SNTP (UDP port 123). By default, it does this every 5 minutes,
|
||||
* forever.
|
||||
* Dummy. Real thing moved to net.i2p.router.time.RouterTimestamper.
|
||||
* What remains here is essentially an interface,
|
||||
* containing only what is needed to keep external apps
|
||||
* compiled with old libs from breaking, since
|
||||
* net.i2p.util.Clock returns a Timestamper in getTimestamper()
|
||||
*
|
||||
* Deprecated outside of the router.
|
||||
*/
|
||||
public class Timestamper implements Runnable {
|
||||
private final I2PAppContext _context;
|
||||
private Log _log;
|
||||
private final List<String> _servers;
|
||||
private List<String> _priorityServers;
|
||||
private final List<UpdateListener> _listeners;
|
||||
private int _queryFrequency;
|
||||
private int _concurringServers;
|
||||
private int _consecutiveFails;
|
||||
private volatile boolean _disabled;
|
||||
private final boolean _daemon;
|
||||
private boolean _initialized;
|
||||
private boolean _wellSynced;
|
||||
private volatile boolean _isRunning;
|
||||
private Thread _timestamperThread;
|
||||
|
||||
private static final int MIN_QUERY_FREQUENCY = 5*60*1000;
|
||||
private static final int DEFAULT_QUERY_FREQUENCY = 5*60*1000;
|
||||
private static final String DEFAULT_SERVER_LIST = "0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org";
|
||||
private static final String DEFAULT_DISABLED = "true";
|
||||
/** how many times do we have to query if we are changing the clock? */
|
||||
private static final int DEFAULT_CONCURRING_SERVERS = 3;
|
||||
private static final int MAX_CONSECUTIVE_FAILS = 10;
|
||||
/** dummy */
|
||||
public Timestamper() {}
|
||||
|
||||
public static final String PROP_QUERY_FREQUENCY = "time.queryFrequencyMs";
|
||||
public static final String PROP_SERVER_LIST = "time.sntpServerList";
|
||||
public static final String PROP_DISABLED = "time.disabled";
|
||||
public static final String PROP_CONCURRING_SERVERS = "time.concurringServers";
|
||||
public static final String PROP_IP_COUNTRY = "i2np.lastCountry";
|
||||
|
||||
/** if different SNTP servers differ by more than 10s, someone is b0rked */
|
||||
private static final int MAX_VARIANCE = 10*1000;
|
||||
|
||||
public Timestamper(I2PAppContext ctx) {
|
||||
this(ctx, null, true);
|
||||
}
|
||||
|
||||
public Timestamper(I2PAppContext ctx, UpdateListener lsnr) {
|
||||
this(ctx, lsnr, true);
|
||||
}
|
||||
public Timestamper(I2PAppContext ctx, UpdateListener lsnr, boolean daemon) {
|
||||
// moved here to prevent problems with synchronized statements.
|
||||
_servers = new ArrayList(3);
|
||||
_listeners = new CopyOnWriteArrayList();
|
||||
_context = ctx;
|
||||
_daemon = daemon;
|
||||
// DO NOT initialize _log here, stack overflow via LogManager init loop
|
||||
|
||||
// Don't bother starting a thread if we are disabled.
|
||||
// This means we no longer check every 5 minutes to see if we got enabled,
|
||||
// so the property must be set at startup.
|
||||
// We still need to be instantiated since the router calls clock().getTimestamper().waitForInitialization()
|
||||
String disabled = ctx.getProperty(PROP_DISABLED, DEFAULT_DISABLED);
|
||||
if (Boolean.valueOf(disabled).booleanValue()) {
|
||||
_initialized = true;
|
||||
return;
|
||||
}
|
||||
if (lsnr != null)
|
||||
_listeners.add(lsnr);
|
||||
updateConfig();
|
||||
startTimestamper();
|
||||
}
|
||||
|
||||
public int getServerCount() {
|
||||
synchronized (_servers) {
|
||||
return _servers.size();
|
||||
}
|
||||
}
|
||||
public String getServer(int index) {
|
||||
synchronized (_servers) {
|
||||
return _servers.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
public int getQueryFrequencyMs() { return _queryFrequency; }
|
||||
|
||||
public boolean getIsDisabled() { return _disabled; }
|
||||
|
||||
public void addListener(UpdateListener lsnr) {
|
||||
_listeners.add(lsnr);
|
||||
}
|
||||
public void removeListener(UpdateListener lsnr) {
|
||||
_listeners.remove(lsnr);
|
||||
}
|
||||
public int getListenerCount() {
|
||||
return _listeners.size();
|
||||
}
|
||||
public UpdateListener getListener(int index) {
|
||||
return _listeners.get(index);
|
||||
}
|
||||
|
||||
private void startTimestamper() {
|
||||
_timestamperThread = new I2PThread(this, "Timestamper", _daemon);
|
||||
_timestamperThread.setPriority(I2PThread.MIN_PRIORITY);
|
||||
_isRunning = true;
|
||||
_timestamperThread.start();
|
||||
_context.addShutdownTask(new Shutdown());
|
||||
}
|
||||
|
||||
public void waitForInitialization() {
|
||||
try {
|
||||
synchronized (this) {
|
||||
if (!_initialized)
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
/** dummy */
|
||||
public void waitForInitialization() {}
|
||||
|
||||
/**
|
||||
* Update the time immediately.
|
||||
* Dummy
|
||||
* @since 0.8.8
|
||||
*/
|
||||
public void timestampNow() {
|
||||
if (_initialized && _isRunning && (!_disabled) && _timestamperThread != null)
|
||||
_timestamperThread.interrupt();
|
||||
}
|
||||
public void timestampNow() {}
|
||||
|
||||
/** @since 0.8.8 */
|
||||
private class Shutdown implements Runnable {
|
||||
public void run() {
|
||||
_isRunning = false;
|
||||
if (_timestamperThread != null)
|
||||
_timestamperThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
_log = _context.logManager().getLog(Timestamper.class);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting timestamper");
|
||||
boolean lastFailed = false;
|
||||
try {
|
||||
while (_isRunning) {
|
||||
updateConfig();
|
||||
if (!_disabled) {
|
||||
// first the servers for our country, if we know what country we're in...
|
||||
if (_priorityServers != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Querying servers " + _priorityServers);
|
||||
try {
|
||||
lastFailed = !queryTime(_priorityServers.toArray(new String[_priorityServers.size()]));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if ( (!lastFailed) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Unable to reach country-specific NTP servers");
|
||||
lastFailed = true;
|
||||
}
|
||||
}
|
||||
// ... and then the global list, if that failed
|
||||
if (_priorityServers == null || lastFailed) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Querying servers " + _servers);
|
||||
try {
|
||||
lastFailed = !queryTime(_servers.toArray(new String[_servers.size()]));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if ( (!_initialized) && (_log.shouldLog(Log.ERROR)) ) {
|
||||
List<String> all = new ArrayList();
|
||||
if (_priorityServers != null)
|
||||
all.addAll(_priorityServers);
|
||||
all.addAll(_servers);
|
||||
_log.error("Unable to reach any of the NTP servers " + all + " - network disconnected? Or set time.sntpServerList=myserver1.com,myserver2.com in advanced configuration.");
|
||||
}
|
||||
lastFailed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
synchronized (this) { notifyAll(); }
|
||||
long sleepTime;
|
||||
if (lastFailed) {
|
||||
if (++_consecutiveFails >= MAX_CONSECUTIVE_FAILS)
|
||||
sleepTime = 30*60*1000;
|
||||
else
|
||||
sleepTime = 30*1000;
|
||||
} else {
|
||||
_consecutiveFails = 0;
|
||||
sleepTime = _context.random().nextInt(_queryFrequency) + _queryFrequency;
|
||||
if (_wellSynced)
|
||||
sleepTime *= 3;
|
||||
}
|
||||
try { Thread.sleep(sleepTime); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Timestamper died!", t);
|
||||
synchronized (this) { notifyAll(); }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the time was queried successfully, false if it couldn't be
|
||||
*/
|
||||
private boolean queryTime(String serverList[]) throws IllegalArgumentException {
|
||||
long found[] = new long[_concurringServers];
|
||||
long now = -1;
|
||||
int stratum = -1;
|
||||
long expectedDelta = 0;
|
||||
_wellSynced = false;
|
||||
for (int i = 0; i < _concurringServers; i++) {
|
||||
if (i > 0) {
|
||||
// this delays startup when net is disconnected or the timeserver list is bad, don't make it too long
|
||||
try { Thread.sleep(2*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList);
|
||||
now = timeAndStratum[0];
|
||||
stratum = (int) timeAndStratum[1];
|
||||
long delta = now - _context.clock().now();
|
||||
found[i] = delta;
|
||||
if (i == 0) {
|
||||
if (Math.abs(delta) < MAX_VARIANCE) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("a single SNTP query was within the tolerance (" + delta + "ms)");
|
||||
// If less than a half second on the first try, we're in good shape
|
||||
_wellSynced = Math.abs(delta) < 500;
|
||||
break;
|
||||
} else {
|
||||
// outside the tolerance, lets iterate across the concurring queries
|
||||
expectedDelta = delta;
|
||||
}
|
||||
} else {
|
||||
if (Math.abs(delta - expectedDelta) > MAX_VARIANCE) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
StringBuilder err = new StringBuilder(96);
|
||||
err.append("SNTP client variance exceeded at query ").append(i);
|
||||
err.append(". expected = ");
|
||||
err.append(expectedDelta);
|
||||
err.append(", found = ");
|
||||
err.append(delta);
|
||||
err.append(" all deltas: ");
|
||||
for (int j = 0; j < found.length; j++)
|
||||
err.append(found[j]).append(' ');
|
||||
_log.error(err.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
stampTime(now, stratum);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append("Deltas: ");
|
||||
for (int i = 0; i < found.length; i++)
|
||||
buf.append(found[i]).append(' ');
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the listeners
|
||||
*
|
||||
* @since stratum param added in 0.7.12
|
||||
*/
|
||||
private void stampTime(long now, int stratum) {
|
||||
long before = _context.clock().now();
|
||||
for (UpdateListener lsnr : _listeners) {
|
||||
lsnr.setNow(now, stratum);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Stamped the time as " + now + " (delta=" + (now-before) + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload all the config elements from the appContext
|
||||
*
|
||||
*/
|
||||
private void updateConfig() {
|
||||
String serverList = _context.getProperty(PROP_SERVER_LIST);
|
||||
if ( (serverList == null) || (serverList.trim().length() <= 0) ) {
|
||||
serverList = DEFAULT_SERVER_LIST;
|
||||
String country = _context.getProperty(PROP_IP_COUNTRY);
|
||||
if (country == null) {
|
||||
country = Locale.getDefault().getCountry();
|
||||
if (country != null)
|
||||
country = country.toLowerCase(Locale.US);
|
||||
}
|
||||
if (country != null && country.length() > 0) {
|
||||
_priorityServers = new ArrayList(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
_priorityServers.add(i + "." + country + ".pool.ntp.org");
|
||||
} else {
|
||||
_priorityServers = null;
|
||||
}
|
||||
} else {
|
||||
_priorityServers = null;
|
||||
}
|
||||
_servers.clear();
|
||||
StringTokenizer tok = new StringTokenizer(serverList, ", ");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String val = tok.nextToken();
|
||||
val = val.trim();
|
||||
if (val.length() > 0)
|
||||
_servers.add(val);
|
||||
}
|
||||
|
||||
_queryFrequency = Math.max(MIN_QUERY_FREQUENCY,
|
||||
_context.getProperty(PROP_QUERY_FREQUENCY, DEFAULT_QUERY_FREQUENCY));
|
||||
|
||||
String disabled = _context.getProperty(PROP_DISABLED, DEFAULT_DISABLED);
|
||||
_disabled = Boolean.valueOf(disabled).booleanValue();
|
||||
|
||||
_concurringServers = Math.min(4, Math.max(1,
|
||||
_context.getProperty(PROP_CONCURRING_SERVERS, DEFAULT_CONCURRING_SERVERS)));
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String args[]) {
|
||||
System.setProperty(PROP_DISABLED, "false");
|
||||
System.setProperty(PROP_QUERY_FREQUENCY, "30000");
|
||||
I2PAppContext.getGlobalContext();
|
||||
for (int i = 0; i < 5*60*1000; i += 61*1000) {
|
||||
try { Thread.sleep(61*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
****/
|
||||
/** dummy */
|
||||
public void run() {}
|
||||
|
||||
/**
|
||||
* Interface to receive update notifications for when we query the time
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Provides classes for time synchronisation using NTP.
|
||||
Provides a stub class for time synchronization.
|
||||
Full implementation is now in net.i2p.router.time.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -19,7 +19,6 @@ import net.i2p.time.Timestamper;
|
||||
*/
|
||||
public class Clock implements Timestamper.UpdateListener {
|
||||
protected final I2PAppContext _context;
|
||||
private final Timestamper _timestamper;
|
||||
protected long _startedOn;
|
||||
protected boolean _statCreated;
|
||||
protected volatile long _offset;
|
||||
@@ -29,7 +28,6 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
public Clock(I2PAppContext context) {
|
||||
_context = context;
|
||||
_listeners = new CopyOnWriteArraySet();
|
||||
_timestamper = new Timestamper(context, this);
|
||||
_startedOn = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@@ -37,7 +35,10 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
return I2PAppContext.getGlobalContext().clock();
|
||||
}
|
||||
|
||||
public Timestamper getTimestamper() { return _timestamper; }
|
||||
/**
|
||||
* This is a dummy, see RouterClock and RouterTimestamper for the real thing
|
||||
*/
|
||||
public Timestamper getTimestamper() { return new Timestamper(); }
|
||||
|
||||
/** we fetch it on demand to avoid circular dependencies (logging uses the clock) */
|
||||
protected Log getLog() { return _context.logManager().getLog(Clock.class); }
|
||||
|
Reference in New Issue
Block a user