TimeInfo.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.net.ntp;

  18. import java.net.DatagramPacket;
  19. import java.net.InetAddress;
  20. import java.util.ArrayList;
  21. import java.util.List;

  22. /**
  23.  * Wrapper class to network time packet messages (NTP, etc.) that computes related timing info and stats.
  24.  */
  25. public class TimeInfo {

  26.     private final NtpV3Packet message;
  27.     private List<String> comments;
  28.     private Long delayMillis;
  29.     private Long offsetMillis;

  30.     /**
  31.      * time at which time message packet was received by local machine
  32.      */
  33.     private final long returnTimeMillis;

  34.     /**
  35.      * flag indicating that the TimeInfo details was processed and delay/offset were computed
  36.      */
  37.     private boolean detailsComputed;

  38.     /**
  39.      * Create TimeInfo object with raw packet message and destination time received.
  40.      *
  41.      * @param message          NTP message packet
  42.      * @param returnTimeMillis destination receive time
  43.      * @throws IllegalArgumentException if message is null
  44.      */
  45.     public TimeInfo(final NtpV3Packet message, final long returnTimeMillis) {
  46.         this(message, returnTimeMillis, null, true);
  47.     }

  48.     /**
  49.      * Create TimeInfo object with raw packet message and destination time received. Auto-computes details if computeDetails flag set otherwise this is delayed
  50.      * until computeDetails() is called. Delayed computation is for fast initialization when sub-millisecond timing is needed.
  51.      *
  52.      * @param msgPacket        NTP message packet
  53.      * @param returnTimeMillis destination receive time
  54.      * @param doComputeDetails flag to pre-compute delay/offset values
  55.      * @throws IllegalArgumentException if message is null
  56.      */
  57.     public TimeInfo(final NtpV3Packet msgPacket, final long returnTimeMillis, final boolean doComputeDetails) {
  58.         this(msgPacket, returnTimeMillis, null, doComputeDetails);
  59.     }

  60.     /**
  61.      * Create TimeInfo object with raw packet message and destination time received.
  62.      *
  63.      * @param message          NTP message packet
  64.      * @param returnTimeMillis destination receive time
  65.      * @param comments         List of errors/warnings identified during processing
  66.      * @throws IllegalArgumentException if message is null
  67.      */
  68.     public TimeInfo(final NtpV3Packet message, final long returnTimeMillis, final List<String> comments) {
  69.         this(message, returnTimeMillis, comments, true);
  70.     }

  71.     /**
  72.      * Create TimeInfo object with raw packet message and destination time received. Auto-computes details if computeDetails flag set otherwise this is delayed
  73.      * until computeDetails() is called. Delayed computation is for fast initialization when sub-millisecond timing is needed.
  74.      *
  75.      * @param message          NTP message packet
  76.      * @param returnTimeMillis destination receive time
  77.      * @param comments         list of comments used to store errors/warnings with message
  78.      * @param doComputeDetails flag to pre-compute delay/offset values
  79.      * @throws IllegalArgumentException if message is null
  80.      */
  81.     public TimeInfo(final NtpV3Packet message, final long returnTimeMillis, final List<String> comments, final boolean doComputeDetails) {
  82.         if (message == null) {
  83.             throw new IllegalArgumentException("message cannot be null");
  84.         }
  85.         this.returnTimeMillis = returnTimeMillis;
  86.         this.message = message;
  87.         this.comments = comments;
  88.         if (doComputeDetails) {
  89.             computeDetails();
  90.         }
  91.     }

  92.     /**
  93.      * Add comment (error/warning) to list of comments associated with processing of NTP parameters. If comment list not create then one will be created.
  94.      *
  95.      * @param comment the comment
  96.      */
  97.     public void addComment(final String comment) {
  98.         if (comments == null) {
  99.             comments = new ArrayList<>();
  100.         }
  101.         comments.add(comment);
  102.     }

  103.     /**
  104.      * Compute and validate details of the NTP message packet. Computed fields include the offset and delay.
  105.      */
  106.     public void computeDetails() {
  107.         if (detailsComputed) {
  108.             return; // details already computed - do nothing
  109.         }
  110.         detailsComputed = true;
  111.         if (comments == null) {
  112.             comments = new ArrayList<>();
  113.         }

  114.         final TimeStamp origNtpTime = message.getOriginateTimeStamp();
  115.         final long origTimeMillis = origNtpTime.getTime();

  116.         // Receive Time is time request received by server (t2)
  117.         final TimeStamp rcvNtpTime = message.getReceiveTimeStamp();
  118.         final long rcvTimeMillis = rcvNtpTime.getTime();

  119.         // Transmit time is time reply sent by server (t3)
  120.         final TimeStamp xmitNtpTime = message.getTransmitTimeStamp();
  121.         final long xmitTimeMillis = xmitNtpTime.getTime();

  122.         /*
  123.          * Round-trip network delay and local clock offset (or time drift) is calculated according to this standard NTP equation:
  124.          *
  125.          * LocalClockOffset = ((ReceiveTimestamp - OriginateTimestamp) + (TransmitTimestamp - DestinationTimestamp)) / 2
  126.          *
  127.          * equations from RFC-1305 (NTPv3) roundtrip delay = (t4 - t1) - (t3 - t2) local clock offset = ((t2 - t1) + (t3 - t4)) / 2
  128.          *
  129.          * It takes into account network delays and assumes that they are symmetrical.
  130.          *
  131.          * Note the typo in SNTP RFCs 1769/2030 which state that the delay is (T4 - T1) - (T2 - T3) with the "T2" and "T3" switched.
  132.          */
  133.         if (origNtpTime.ntpValue() == 0) {
  134.             // without originate time cannot determine when packet went out
  135.             // might be via a broadcast NTP packet...
  136.             if (xmitNtpTime.ntpValue() != 0) {
  137.                 offsetMillis = Long.valueOf(xmitTimeMillis - returnTimeMillis);
  138.                 comments.add("Error: zero orig time -- cannot compute delay");
  139.             } else {
  140.                 comments.add("Error: zero orig time -- cannot compute delay/offset");
  141.             }
  142.         } else if (rcvNtpTime.ntpValue() == 0 || xmitNtpTime.ntpValue() == 0) {
  143.             comments.add("Warning: zero rcvNtpTime or xmitNtpTime");
  144.             // assert destTime >= origTime since network delay cannot be negative
  145.             if (origTimeMillis > returnTimeMillis) {
  146.                 comments.add("Error: OrigTime > DestRcvTime");
  147.             } else {
  148.                 // without receive or xmit time cannot figure out processing time
  149.                 // so delay is simply the network travel time
  150.                 delayMillis = Long.valueOf(returnTimeMillis - origTimeMillis);
  151.             }
  152.             // TODO: is offset still valid if rcvNtpTime=0 || xmitNtpTime=0 ???
  153.             // Could always hash origNtpTime (sendTime) but if host doesn't set it
  154.             // then it's an malformed ntp host anyway and we don't care?
  155.             // If server is in broadcast mode then we never send out a query in first place...
  156.             if (rcvNtpTime.ntpValue() != 0) {
  157.                 // xmitTime is 0 just use rcv time
  158.                 offsetMillis = Long.valueOf(rcvTimeMillis - origTimeMillis);
  159.             } else if (xmitNtpTime.ntpValue() != 0) {
  160.                 // rcvTime is 0 just use xmitTime time
  161.                 offsetMillis = Long.valueOf(xmitTimeMillis - returnTimeMillis);
  162.             }
  163.         } else {
  164.             long delayValueMillis = returnTimeMillis - origTimeMillis;
  165.             // assert xmitTime >= rcvTime: difference typically < 1ms
  166.             if (xmitTimeMillis < rcvTimeMillis) {
  167.                 // server cannot send out a packet before receiving it...
  168.                 comments.add("Error: xmitTime < rcvTime"); // time-travel not allowed
  169.             } else {
  170.                 // subtract processing time from round-trip network delay
  171.                 final long deltaMillis = xmitTimeMillis - rcvTimeMillis;
  172.                 // in normal cases the processing delta is less than
  173.                 // the total roundtrip network travel time.
  174.                 if (deltaMillis <= delayValueMillis) {
  175.                     delayValueMillis -= deltaMillis; // delay = (t4 - t1) - (t3 - t2)
  176.                 } else // if delta - delayValue == 1 ms then it's a round-off error
  177.                 // e.g. delay=3ms, processing=4ms
  178.                 if (deltaMillis - delayValueMillis == 1) {
  179.                     // delayValue == 0 -> local clock saw no tick change but destination clock did
  180.                     if (delayValueMillis != 0) {
  181.                         comments.add("Info: processing time > total network time by 1 ms -> assume zero delay");
  182.                         delayValueMillis = 0;
  183.                     }
  184.                 } else {
  185.                     comments.add("Warning: processing time > total network time");
  186.                 }
  187.             }
  188.             delayMillis = Long.valueOf(delayValueMillis);
  189.             if (origTimeMillis > returnTimeMillis) {
  190.                 comments.add("Error: OrigTime > DestRcvTime");
  191.             }

  192.             offsetMillis = Long.valueOf((rcvTimeMillis - origTimeMillis + xmitTimeMillis - returnTimeMillis) / 2);
  193.         }
  194.     }

  195.     /**
  196.      * Compares this object against the specified object. The result is {@code true} if and only if the argument is not {@code null} and is a
  197.      * <code>TimeStamp</code> object that contains the same values as this object.
  198.      *
  199.      * @param obj the object to compare with.
  200.      * @return {@code true} if the objects are the same; {@code false} otherwise.
  201.      * @since 3.4
  202.      */
  203.     @Override
  204.     public boolean equals(final Object obj) {
  205.         if (this == obj) {
  206.             return true;
  207.         }
  208.         if (obj == null || getClass() != obj.getClass()) {
  209.             return false;
  210.         }
  211.         final TimeInfo other = (TimeInfo) obj;
  212.         return returnTimeMillis == other.returnTimeMillis && message.equals(other.message);
  213.     }

  214.     /**
  215.      * Gets host address from message datagram if available
  216.      *
  217.      * @return host address of available otherwise null
  218.      * @since 3.4
  219.      */
  220.     public InetAddress getAddress() {
  221.         final DatagramPacket pkt = message.getDatagramPacket();
  222.         return pkt == null ? null : pkt.getAddress();
  223.     }

  224.     /**
  225.      * Return list of comments (if any) during processing of NTP packet.
  226.      *
  227.      * @return List or null if not yet computed
  228.      */
  229.     public List<String> getComments() {
  230.         return comments;
  231.     }

  232.     /**
  233.      * Gets round-trip network delay. If null then could not compute the delay.
  234.      *
  235.      * @return Long or null if delay not available.
  236.      */
  237.     public Long getDelay() {
  238.         return delayMillis;
  239.     }

  240.     /**
  241.      * Returns NTP message packet.
  242.      *
  243.      * @return NTP message packet.
  244.      */
  245.     public NtpV3Packet getMessage() {
  246.         return message;
  247.     }

  248.     /**
  249.      * Gets clock offset needed to adjust local clock to match remote clock. If null then could not compute the offset.
  250.      *
  251.      * @return Long or null if offset not available.
  252.      */
  253.     public Long getOffset() {
  254.         return offsetMillis;
  255.     }

  256.     /**
  257.      * Returns time at which time message packet was received by local machine.
  258.      *
  259.      * @return packet return time.
  260.      */
  261.     public long getReturnTime() {
  262.         return returnTimeMillis;
  263.     }

  264.     /**
  265.      * Computes a hash code for this object. The result is the exclusive OR of the return time and the message hash code.
  266.      *
  267.      * @return a hash code value for this object.
  268.      * @since 3.4
  269.      */
  270.     @Override
  271.     public int hashCode() {
  272.         final int prime = 31;
  273.         final int result = (int) returnTimeMillis;
  274.         return prime * result + message.hashCode();
  275.     }

  276. }