Created
April 26, 2013 11:32
-
-
Save tai2/5466578 to your computer and use it in GitHub Desktop.
SNTP client. RFC 2030.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package net.tai2.test; | |
| import java.io.IOException; | |
| import java.io.UnsupportedEncodingException; | |
| import java.net.DatagramPacket; | |
| import java.net.DatagramSocket; | |
| import java.net.InetAddress; | |
| import java.net.InetSocketAddress; | |
| import java.net.PortUnreachableException; | |
| import java.net.SocketTimeoutException; | |
| import java.net.UnknownHostException; | |
| import java.util.ArrayList; | |
| import java.util.Date; | |
| import java.util.List; | |
| /* | |
| * Timestampの1ビット目がゼロになった後(2036年頃)は、このモジュール使えない。 | |
| * (プロトコル上は利用可能だが、そのための処理をしていない) | |
| */ | |
| public class SntpClient { | |
| // ----------------------------- | |
| // パブリック定数 | |
| // ----------------------------- | |
| public static int SNTP_VERSION = 4; | |
| public static int SOCKET_TIMEOUT = 5000; | |
| public static int INITIAL_RETRY_SPAN = 2000; | |
| public static int RETRY_LIMIT = 3; | |
| // ----------------------------- | |
| // プライベート定数 | |
| // ----------------------------- | |
| private static String TAG = "SntpClient"; | |
| // ----------------------------- | |
| // フィールド | |
| // ----------------------------- | |
| private List<InetSocketAddress> server_list = new ArrayList<InetSocketAddress>(); | |
| private InetAddress local_address; | |
| private int local_port; | |
| private long source_time; | |
| private long sync_time; | |
| // ----------------------------- | |
| // パブリックメソッド | |
| // ----------------------------- | |
| public SntpClient(List<InetSocketAddress> list) { | |
| server_list = list; | |
| } | |
| public void setLocalAddress(InetAddress addr) { | |
| local_address = addr; | |
| } | |
| public void setLocalPort(int port) { | |
| local_port = port; | |
| } | |
| public boolean sync() { | |
| boolean succeeded = false; | |
| List<TryInfo> info_list = new ArrayList<TryInfo>(); | |
| for (InetSocketAddress address : server_list) { | |
| info_list.add(new TryInfo(address)); | |
| } | |
| for (;;) { | |
| for (TryInfo info : info_list) { | |
| if (!info.nomore) { | |
| try { | |
| sync(info.address, SOCKET_TIMEOUT); | |
| succeeded = true; | |
| } catch (PortUnreachableException e) { | |
| info.span = info.span == 0 ? INITIAL_RETRY_SPAN : info.span * 2; | |
| } catch (SocketTimeoutException e) { | |
| info.span = info.span == 0 ? INITIAL_RETRY_SPAN : info.span * 2; | |
| } catch (KissODethReceived e) { | |
| info.nomore = true; | |
| } catch (IOException e) { | |
| info.nomore = true; | |
| Log.e(TAG, "sync failed. cause=" + e.getMessage()); | |
| } | |
| info.count++; | |
| if (RETRY_LIMIT <= info.count) { | |
| info.nomore = true; | |
| } | |
| } | |
| } | |
| boolean more_chance = false; | |
| for (TryInfo info : info_list) { | |
| if (!info.nomore) { | |
| more_chance = true; | |
| break; | |
| } | |
| } | |
| if (succeeded || !more_chance) { | |
| break; | |
| } | |
| } | |
| return succeeded; | |
| } | |
| public Date getDate() { | |
| long now = System.nanoTime() / (1000 * 1000); | |
| return new Date(source_time + (now - sync_time)); | |
| } | |
| // ----------------------------- | |
| // ユーティリティーメソッド | |
| // ----------------------------- | |
| private void sync(InetSocketAddress address, int timeout) throws IOException, KissODethReceived { | |
| DatagramSocket sock = new DatagramSocket(local_port, local_address); | |
| sock.setSoTimeout(timeout); | |
| Date now = new Date(); | |
| Packet request = new Packet(); | |
| request.leap_indicator = 0; | |
| request.version_number = 4; | |
| request.mode = 3; | |
| request.stratum = 0; | |
| request.poll = 0; | |
| request.precision = 0; | |
| request.root_delay = 0; | |
| request.root_dispersion = 0; | |
| request.reference_identifier = 0; | |
| request.reference_timestamp = 0; | |
| request.originate_timestamp = 0; | |
| request.receive_timestamp = 0; | |
| request.transmit_timestamp = now.getTime(); | |
| byte[] data = request.pack(); | |
| DatagramPacket request_datagram = new DatagramPacket(data, data.length, address); | |
| sock.send(request_datagram); | |
| Log.d(TAG, "request src=" + sock.getLocalSocketAddress() | |
| + " dst=" + address | |
| + " packet=" + request.toString()); | |
| DatagramPacket response_datagram = new DatagramPacket(data, data.length); | |
| sock.receive(response_datagram); | |
| long rt = System.nanoTime(); | |
| if (Packet.PACKET_SIZE <= response_datagram.getLength()) { | |
| Packet response = new Packet(data); | |
| Log.d(TAG, "response src=" + response_datagram.getSocketAddress() | |
| + " dst=" + sock.getLocalSocketAddress() | |
| + " packet=" + response.toString()); | |
| if (response.stratum == 0) { | |
| throw new KissODethReceived(); | |
| } | |
| if (request_datagram.getSocketAddress().equals(response_datagram.getSocketAddress()) | |
| && request.transmit_timestamp == response.originate_timestamp | |
| && response.isValid()) { | |
| Date received = new Date(); | |
| long clock_offset = ((response.receive_timestamp - response.originate_timestamp) | |
| + (response.transmit_timestamp - received.getTime())) / 2; | |
| source_time = response.receive_timestamp + clock_offset; | |
| sync_time = rt / (1000 * 1000); | |
| } else { | |
| throw new IOException("invalid packet"); | |
| } | |
| } else { | |
| throw new IOException("packet too short"); | |
| } | |
| } | |
| // ----------------------------- | |
| // プライベート内部クラス | |
| // ----------------------------- | |
| /* | |
| * 1 2 3 | |
| * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * |LI | VN |Mode | Stratum | Poll | Precision | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | Root Delay | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | Root Dispersion | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | Reference Identifier | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | | | |
| * | Reference Timestamp (64) | | |
| * | | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | | | |
| * | Originate Timestamp (64) | | |
| * | | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | | | |
| * | Receive Timestamp (64) | | |
| * | | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | | | |
| * | Transmit Timestamp (64) | | |
| * | | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | Key Identifier (optional) (32) | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | | | |
| * | | | |
| * | Message Digest (optional) (128) | | |
| * | | | |
| * | | | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| * | |
| */ | |
| private static class Packet { | |
| public static int PACKET_SIZE = 48; | |
| private static long UNIX_TIME_OFFSET = 2208988800L; // from RFC 868 | |
| public int leap_indicator; | |
| public int version_number; | |
| public int mode; | |
| public int stratum; | |
| public int poll; | |
| public int precision; | |
| public int root_delay; // milliseconds | |
| public int root_dispersion; // milliseconds | |
| public int reference_identifier; | |
| public long reference_timestamp; // millisecond unix time | |
| public long originate_timestamp; // millisecond unix time | |
| public long receive_timestamp; // millisecond unix time | |
| public long transmit_timestamp; // millisecond unix time | |
| public Packet() { | |
| } | |
| public Packet(byte [] data) { | |
| assert PACKET_SIZE <= data.length; | |
| leap_indicator = (data[0]>>>6) & 0x03; // 8-bit code | |
| version_number = (data[0]>>>3) & 0x07; // 3-bit integer | |
| mode = (data[0]>>>0) & 0x07; // 3-bit integer | |
| stratum = (data[1]) & 0xFF; // 8-bit unsigned integer | |
| poll = (data[2]) & 0xFF; // 8-bit unsigned integer | |
| precision = data[3]; // 8-bit signed integer(assumes two's complement) | |
| root_delay = readSignedFixed32Sec(data, 4); | |
| root_dispersion = readSignedFixed32Sec(data, 8); | |
| reference_identifier = readInt(data, 12); | |
| reference_timestamp = readTimestamp(data, 16); | |
| originate_timestamp = readTimestamp(data, 24); | |
| receive_timestamp = readTimestamp(data, 32); | |
| transmit_timestamp = readTimestamp(data, 40); | |
| } | |
| public String toString() { | |
| StringBuilder result = new StringBuilder(); | |
| result.append("{"); | |
| result.append("LI=" + leap_indicator + ","); | |
| result.append("VN=" + version_number + ","); | |
| result.append("Mode=" + mode + ","); | |
| result.append("Stratum=" + stratum + ","); | |
| result.append("Poll=" + poll + ","); | |
| result.append("Precision=" + precision + ","); | |
| result.append("Root Delay=" + root_delay + ","); | |
| result.append("Root Dispersion=" + root_dispersion + ","); | |
| result.append("Reference Identifier=" + IdentifierString(stratum, reference_identifier) + ","); | |
| result.append("Reference Timestamp=" + TimestampString(reference_timestamp) + ","); | |
| result.append("Originate Timestamp=" + TimestampString(originate_timestamp) + ","); | |
| result.append("Receive Timestamp=" + TimestampString(receive_timestamp) + ","); | |
| result.append("Transmit Timestamp=" + TimestampString(transmit_timestamp)); | |
| result.append("}"); | |
| return result.toString(); | |
| } | |
| public byte[] pack() { | |
| assert 0 <= leap_indicator && leap_indicator <= 3; | |
| assert 0 <= version_number && version_number <= 7; | |
| assert 0 <= mode && mode <= 7; | |
| assert 0 <= stratum && stratum <= 255; | |
| assert 0 <= poll && poll <= 255; | |
| assert -128 <= precision && precision <= 127; | |
| byte[] result = new byte[48]; | |
| result[0] = (byte)((leap_indicator<<6) | (version_number<<3) | mode); | |
| result[1] = (byte)stratum; | |
| result[2] = (byte)poll; | |
| result[3] = (byte)precision; | |
| writeSignedFixed32Sec(result, 4, root_delay); | |
| writeSignedFixed32Sec(result, 8, root_dispersion); | |
| writeInt(result, 12, reference_identifier); | |
| writeTimestamp(result, 16, reference_timestamp); | |
| writeTimestamp(result, 24, originate_timestamp); | |
| writeTimestamp(result, 32, receive_timestamp); | |
| writeTimestamp(result, 40, transmit_timestamp); | |
| return result; | |
| } | |
| public boolean isValid() { | |
| if (leap_indicator < 0 || 3 < leap_indicator) { | |
| return false; | |
| } | |
| if (version_number != SNTP_VERSION) { | |
| return false; | |
| } | |
| if (mode != 4) { | |
| return false; | |
| } | |
| if (stratum < 0 || 15 < stratum) { | |
| return false; | |
| } | |
| if (transmit_timestamp == 0) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| private static int readInt(byte[] bytes, int i) { | |
| return ((bytes[i + 0]<<24) & 0xFF000000) | |
| | ((bytes[i + 1]<<16) & 0x00FF0000) | |
| | ((bytes[i + 2]<< 8) & 0x0000FF00) | |
| | ((bytes[i + 3]<< 0) & 0x000000FF); | |
| } | |
| private static void writeInt(byte[] bytes, int i, int val) { | |
| bytes[i + 0] = (byte)((val>>24) & 0xFF); | |
| bytes[i + 1] = (byte)((val>>16) & 0xFF); | |
| bytes[i + 2] = (byte)((val>> 8) & 0xFF); | |
| bytes[i + 3] = (byte)((val>> 0) & 0xFF); | |
| } | |
| private static int readSignedFixed32Sec(byte[] bytes, int i) { | |
| long sec = readInt(bytes, i); | |
| return (int)(1000 * sec / 65536); // to milliseconds | |
| } | |
| private static void writeSignedFixed32Sec(byte[] bytes, int i, int val) { | |
| long sec = 65536 * val / 1000; | |
| writeInt(bytes, i, (int)sec); | |
| } | |
| private static long readTimestamp(byte[] bytes, int i) { | |
| long sec = | |
| (((long)bytes[i + 0]<<56) & 0xFF00000000000000l) | |
| | (((long)bytes[i + 1]<<48) & 0x00FF000000000000l) | |
| | (((long)bytes[i + 2]<<40) & 0x0000FF0000000000l) | |
| | (((long)bytes[i + 3]<<32) & 0x000000FF00000000l) | |
| | (((long)bytes[i + 4]<<24) & 0x00000000FF000000l) | |
| | (((long)bytes[i + 5]<<16) & 0x0000000000FF0000l) | |
| | (((long)bytes[i + 6]<< 8) & 0x000000000000FF00l) | |
| | (((long)bytes[i + 7]<< 0) & 0x00000000000000FFl); | |
| return (sec>>>22) - (sec>>>28) - (sec>>>29) - UNIX_TIME_OFFSET * 1000; // to milliseconds | |
| } | |
| private static void writeTimestamp(byte[] bytes, int i, long val) { | |
| assert 0 <= val; | |
| long sec = (val / 1000) % (2l<<32) + UNIX_TIME_OFFSET; | |
| long msec = ((val % 1000)<<32) / 1000; | |
| bytes[i + 0] = (byte)(( sec>>24) & 0xFF); | |
| bytes[i + 1] = (byte)(( sec>>16) & 0xFF); | |
| bytes[i + 2] = (byte)(( sec>> 8) & 0xFF); | |
| bytes[i + 3] = (byte)(( sec>> 0) & 0xFF); | |
| bytes[i + 4] = (byte)((msec>>24) & 0xFF); | |
| bytes[i + 5] = (byte)((msec>>16) & 0xFF); | |
| bytes[i + 6] = (byte)((msec>> 8) & 0xFF); | |
| bytes[i + 7] = (byte)((msec>> 0) & 0xFF); | |
| } | |
| private static String IdentifierString(int stratum, int identifier) { | |
| String result = null; | |
| byte[] bytes = { | |
| (byte)((identifier>>24) & 0xFF), | |
| (byte)((identifier>>16) & 0xFF), | |
| (byte)((identifier>> 8) & 0xFF), | |
| (byte)((identifier>> 0) & 0xFF), | |
| }; | |
| if (stratum == 0 || stratum == 1) { | |
| try { | |
| result = new String(bytes, "US-ASCII"); | |
| } catch (UnsupportedEncodingException e) { | |
| Log.e(TAG, "UnsupportedEncodingException. cause=" + e.getMessage()); | |
| } | |
| } else { | |
| try { | |
| InetAddress addr = InetAddress.getByAddress(bytes); | |
| result = addr.getHostAddress(); | |
| } catch (UnknownHostException e) { | |
| Log.e(TAG, "UnknownHostException. cause=" + e.getMessage()); | |
| } | |
| } | |
| return result; | |
| } | |
| private static String TimestampString(long timestamp) { | |
| Date date = new Date(timestamp); | |
| return date.toString(); | |
| } | |
| } | |
| private static class KissODethReceived extends IOException { | |
| public static final long serialVersionUID = 1; | |
| } | |
| private static class TryInfo { | |
| InetSocketAddress address; | |
| int count; | |
| int span; | |
| boolean nomore; | |
| public TryInfo(InetSocketAddress addr) { | |
| address = addr; | |
| count = 0; | |
| span = 0; | |
| nomore = false; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment