diff --git a/apps/time/java/build.xml b/apps/time/java/build.xml
new file mode 100644
index 000000000..636443437
--- /dev/null
+++ b/apps/time/java/build.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/time/java/src/net/i2p/time/NtpClient.java b/apps/time/java/src/net/i2p/time/NtpClient.java
new file mode 100644
index 000000000..1cef7d721
--- /dev/null
+++ b/apps/time/java/src/net/i2p/time/NtpClient.java
@@ -0,0 +1,138 @@
+package net.i2p.time;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.text.DecimalFormat;
+
+
+/**
+ * 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.
+ *
+ *
+ * This code is copyright (c) Adam Buckley 2004
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version. A HTML version of the GNU General Public License can be
+ * seen at http://www.gnu.org/licenses/gpl.html
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * @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");
+ for (int i = 0; i < serverNames.length; i++) {
+ long now = currentTime(serverNames[i]);
+ if (now > 0)
+ return now;
+ }
+ 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) {
+ 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.receive(packet);
+
+ // 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();
+
+ //System.out.println("host: " + serverName + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds");
+ return (long)(System.currentTimeMillis() + localClockOffset*1000);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ return -1;
+ }
+ }
+
+ 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.");
+
+ }
+}
diff --git a/apps/time/java/src/net/i2p/time/NtpMessage.java b/apps/time/java/src/net/i2p/time/NtpMessage.java
new file mode 100644
index 000000000..3e693449a
--- /dev/null
+++ b/apps/time/java/src/net/i2p/time/NtpMessage.java
@@ -0,0 +1,451 @@
+package net.i2p.time;
+
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+
+/**
+ * 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());
+ *
+ *
+ * This code is copyright (c) Adam Buckley 2004
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version. A HTML version of the GNU General Public License can be
+ * seen at http://www.gnu.org/licenses/gpl.html
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ *
+ * 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] = (byte) pollInterval;
+ p[3] = (byte) 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
+ */
+ 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 (short) b;
+ }
+
+
+
+ /**
+ * Will read 8 bytes of a message beginning at pointer
+ * 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 - (double) (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] = (byte) (Math.random()*255.0);
+ }
+
+
+
+ /**
+ * 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 "";
+ }
+}
diff --git a/apps/time/java/src/net/i2p/time/Timestamper.java b/apps/time/java/src/net/i2p/time/Timestamper.java
new file mode 100644
index 000000000..d83262242
--- /dev/null
+++ b/apps/time/java/src/net/i2p/time/Timestamper.java
@@ -0,0 +1,103 @@
+package net.i2p.time;
+
+import net.i2p.util.Log;
+import net.i2p.util.I2PThread;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+import java.io.IOException;
+
+/**
+ * Periodically query a series of NTP servers and post the offset
+ * to a given URL. It tries the NTP servers in order, contacting them
+ * using UDP port 123, and sends the current date to the URL specified
+ * (specifically, URL+"&now=" + yyyyMMdd_HH:mm:ss.SSS in the UK locale).
+ * It does this every 5 minutes, forever.
+ *
+ * Usage:
+ * Timestamper URL ntpServer1[ ntpServer2]*
+ *
+ */
+public class Timestamper implements Runnable {
+ private static Log _log = new Log(Timestamper.class);
+ private String _targetURL;
+ private String _serverList[];
+
+ private int DELAY_MS = 5*60*1000;
+
+ public Timestamper(String url, String serverNames[]) {
+ if (_log.shouldLog(Log.INFO))
+ _log.info("Creating new timestamper pointing at " + url);
+ _targetURL = url;
+ _serverList = serverNames;
+ }
+
+ public void startTimestamper() {
+ if (_log.shouldLog(Log.INFO))
+ _log.info("Starting timestamper pointing at " + _targetURL);
+ I2PThread t = new I2PThread(this, "Timestamper");
+ t.setPriority(I2PThread.MIN_PRIORITY);
+ t.start();
+ }
+
+ public void run() {
+ if (_log.shouldLog(Log.INFO))
+ _log.info("Starting up timestamper");
+ while (true) {
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug("Querying servers " + _serverList);
+ long now = NtpClient.currentTime(_serverList);
+ if (now < 0) {
+ _log.error("Unable to contact any of the NTP servers - network disconnect?");
+ } else {
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug("Stamp time");
+ stampTime(now);
+ }
+ try { Thread.sleep(DELAY_MS); } catch (InterruptedException ie) {}
+ }
+ }
+
+ /**
+ * Send an HTTP request to a given URL specifying the current time
+ */
+ private void stampTime(long now) {
+ try {
+ URL url = new URL(_targetURL + "&now=" + getNow(now));
+ Object o = url.getContent();
+ // ignore the content
+ } catch (MalformedURLException mue) {
+ _log.error("Invalid URL", mue);
+ } catch (IOException ioe) {
+ _log.error("Error stamping the time", ioe);
+ }
+ }
+
+ private SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd_HH:mm:ss.SSS", Locale.UK);
+ private String getNow(long now) {
+ synchronized (_fmt) {
+ return _fmt.format(new Date(now));
+ }
+ }
+
+ public static void main(String args[]) {
+ if ( (args == null) || (args.length < 2) ) {
+ usage();
+ return;
+ //args = new String[] { "http://dev.i2p.net:80/somePath?pass=password", "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
+ }
+ String servers[] = new String[args.length-1];
+ System.arraycopy(args, 1, servers, 0, servers.length);
+ Timestamper ts = new Timestamper(args[0], servers);
+ ts.startTimestamper();
+ }
+
+ private static void usage() {
+ System.err.println("Usage: Timestamper URL ntpServer[ ntpServer]*");
+ _log.error("Usage: Timestamper URL ntpServer[ ntpServer]*");
+ }
+}
\ No newline at end of file