/*
* Copyright 2011 Future Systems
*
* 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 org.krakenapps.ntp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import org.krakenapps.ntp.impl.ServerTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author delmitz
*/
public class NtpClient {
private static final String DEFAULT_TIME_SERVER = "pool.ntp.org";
private static final int NTP_V3_PACKET = 3;
private static final int CLIENT_MODE = 3;
private static final int STRATUM_INDEX = 1;
private static final int POLL_INDEX = 2;
private static final int PRECISION_INDEX = 3;
private static final int ROOT_DELAY_INDEX = 4;
private static final int ROOT_DISPERSION_INDEX = 8;
private static final int REFERENCE_IDENTIFIER_INDEX = 12;
private static final int REFERENCE_TIMESTAMP_INDEX = 16;
private static final int ORIGINATE_TIMESTAMP_INDEX = 24;
private static final int RECEIVE_TIMESTAMP_INDEX = 32;
private static final int TRANSMIT_TIMESTAMP_INDEX = 40;
private Logger logger = LoggerFactory.getLogger(NtpClient.class);
private InetAddress timeServer;
private int timeout;
public NtpClient() throws UnknownHostException {
this(null);
}
public NtpClient(String timeServer) throws UnknownHostException {
this(timeServer, 5000);
}
public NtpClient(String timeServer, int timeout) throws UnknownHostException {
if (timeServer == null)
timeServer = DEFAULT_TIME_SERVER;
this.timeServer = InetAddress.getByName(timeServer);
this.timeout = timeout;
}
public InetAddress getTimeServer() {
return timeServer;
}
public void setTimeServer(InetAddress timeServer) {
this.timeServer = timeServer;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public void setSystemTime(Date time) throws IOException {
Date currentTime = new Date();
String os = System.getProperty("os.name");
if (os.contains("Windows")) {
String newDate = new SimpleDateFormat("MM-dd-yy").format(time);
Runtime.getRuntime().exec("cmd /c date " + newDate);
String newTime = new SimpleDateFormat("HH:mm:ss.SSS").format(time);
newTime = newTime.substring(0, newTime.length() - 1);
Runtime.getRuntime().exec("cmd /c time " + newTime);
} else if (os.contains("Linux")) {
String newTime = new SimpleDateFormat("MMddHHmmyyyy.ss").format(time);
Runtime.getRuntime().exec("date " + newTime);
} else {
// just pray
String newTime = new SimpleDateFormat("MMddHHmmyyyy.ss").format(time);
Runtime.getRuntime().exec("date " + newTime);
}
String newTime = new SimpleDateFormat("MMddHHmmyyyy.ss").format(time);
logger.debug("kraken ntp: OS [{}] set system time {}", os, newTime);
long diff = Math.abs((currentTime.getTime() - time.getTime()) / 60 * 1000);
if (diff >= 30) {
logger.info("kraken ntp: OS [{}] set recieve time [{}], current time [{}]", new Object[] { os, newTime,
new SimpleDateFormat("MMddHHmmyyyy.ss").format(currentTime) });
}
}
public Date sync() throws IOException {
ServerTime time = getTime();
setSystemTime(addOffset(time));
logger.trace("kraken ntp: The time has been successfully synchronized with {} on {}", timeServer, new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss.SSS 'UTC'").format(time.getTransmit()));
return time.getTransmit();
}
private ServerTime getTime() throws IOException {
DatagramSocket socket = new DatagramSocket();
try {
socket.setSoTimeout(timeout);
transmit(socket, timeServer, 123);
ServerTime time = receive(socket);
time.setDestination(getUtcTimeMillis());
return time;
} finally {
socket.close();
}
}
private void transmit(DatagramSocket socket, InetAddress addr, int port) throws IOException {
byte[] buf = new byte[48];
buf[0] |= NTP_V3_PACKET; // set NTP Packet version 3
buf[0] |= (CLIENT_MODE << 3); // set Client mode
long now = getUtcTimeMillis();
now += 2209021200000L; // baseline 1970 to 1900
long l = (now / 1000) << 32;
l |= ((now % 1000) << 32) / 1000;
for (int i = 7; i >= 0; i--) { // set Transmit Timestamp
buf[TRANSMIT_TIMESTAMP_INDEX + i] = (byte) (l & 0xff);
l >>>= 8;
}
DatagramPacket p = new DatagramPacket(buf, buf.length);
p.setAddress(addr);
p.setPort(port);
socket.send(p);
}
private long getUtcTimeMillis() {
return System.currentTimeMillis() - Calendar.getInstance().getTimeZone().getOffset(0);
}
private ServerTime receive(DatagramSocket socket) throws IOException, SocketTimeoutException {
byte[] buf = new byte[48];
DatagramPacket p = new DatagramPacket(buf, buf.length);
socket.receive(p);
ServerTime time = new ServerTime();
time.setStratum((int) buf[STRATUM_INDEX]);
time.setPoll(buf[POLL_INDEX]);
time.setPrecision(buf[PRECISION_INDEX]);
time.setRootDelay(Arrays.copyOfRange(buf, ROOT_DELAY_INDEX, ROOT_DELAY_INDEX + 4));
time.setRootDispersion(Arrays.copyOfRange(buf, ROOT_DISPERSION_INDEX, ROOT_DISPERSION_INDEX + 4));
time.setReferenceIdentifier(Arrays.copyOfRange(buf, REFERENCE_IDENTIFIER_INDEX, REFERENCE_IDENTIFIER_INDEX + 4));
time.setReference(ntpTimeToJavaDate(buf, REFERENCE_TIMESTAMP_INDEX));
time.setOriginate(ntpTimeToJavaDate(buf, ORIGINATE_TIMESTAMP_INDEX));
time.setReceive(ntpTimeToJavaDate(buf, RECEIVE_TIMESTAMP_INDEX));
time.setTransmit(ntpTimeToJavaDate(buf, TRANSMIT_TIMESTAMP_INDEX));
return time;
}
private long ntpTimeToJavaDate(byte[] buf, int offset) {
long seconds = 0;
for (int i = 0; i < 4; i++) {
seconds = seconds << 8;
seconds |= buf[offset + i] & 0xff;
}
seconds *= 1000L;
seconds -= 2209021200000L; // baseline 1900 to 1970
long fraction = 0;
for (int i = 4; i < 8; i++) {
fraction = fraction << 8;
fraction |= buf[offset + i] & 0xff;
}
fraction *= 1000;
fraction >>>= 32;
return (seconds + fraction);
}
private Date addOffset(ServerTime time) {
long millis = System.currentTimeMillis() + time.getClockOffset();
return new Date(millis);
}
}