    001package org.apache.commons.net.ntp;
    002/*
    003 * Licensed to the Apache Software Foundation (ASF) under one or more
    004 * contributor license agreements.  See the NOTICE file distributed with
    005 * this work for additional information regarding copyright ownership.
    006 * The ASF licenses this file to You under the Apache License, Version 2.0
    007 * (the "License"); you may not use this file except in compliance with
    008 * the License.  You may obtain a copy of the License at
    009 *
    010 *      http://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing, software
    013 * distributed under the License is distributed on an "AS IS" BASIS,
    014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    015 * See the License for the specific language governing permissions and
    016 * limitations under the License.
    017 */
    018
    019
    020import java.net.DatagramPacket;
    021import java.net.InetAddress;
    022import java.util.ArrayList;
    023import java.util.List;
    024
    025/**
    026 * Wrapper class to network time packet messages (NTP, etc) that computes
    027 * related timing info and stats.
    028 *
    029 */
    030public class TimeInfo {
    031
    032    private final NtpV3Packet message;
    033    private List<String> comments;
    034    private Long delay;
    035    private Long offset;
    036
    037    /**
    038     * time at which time message packet was received by local machine
    039     */
    040    private final long returnTime;
    041
    042    /**
    043     * flag indicating that the TimeInfo details was processed and delay/offset were computed
    044     */
    045    private boolean detailsComputed;
    046
    047    /**
    048     * Create TimeInfo object with raw packet message and destination time received.
    049     *
    050     * @param message NTP message packet
    051     * @param returnTime  destination receive time
    052     * @throws IllegalArgumentException if message is null
    053     */
    054    public TimeInfo(final NtpV3Packet message, final long returnTime) {
    055        this(message, returnTime, null, true);
    056    }
    057
    058    /**
    059     * Create TimeInfo object with raw packet message and destination time received.
    060     *
    061     * @param message NTP message packet
    062     * @param returnTime  destination receive time
    063     * @param comments List of errors/warnings identified during processing
    064     * @throws IllegalArgumentException if message is null
    065     */
    066    public TimeInfo(final NtpV3Packet message, final long returnTime, final List<String> comments)
    067    {
    068            this(message, returnTime, comments, true);
    069    }
    070
    071    /**
    072     * Create TimeInfo object with raw packet message and destination time received.
    073     * Auto-computes details if computeDetails flag set otherwise this is delayed
    074     * until computeDetails() is called. Delayed computation is for fast
    075     * intialization when sub-millisecond timing is needed.
    076     *
    077     * @param msgPacket NTP message packet
    078     * @param returnTime  destination receive time
    079     * @param doComputeDetails  flag to pre-compute delay/offset values
    080     * @throws IllegalArgumentException if message is null
    081     */
    082    public TimeInfo(final NtpV3Packet msgPacket, final long returnTime, final boolean doComputeDetails)
    083    {
    084            this(msgPacket, returnTime, null, doComputeDetails);
    085    }
    086
    087    /**
    088     * Create TimeInfo object with raw packet message and destination time received.
    089     * Auto-computes details if computeDetails flag set otherwise this is delayed
    090     * until computeDetails() is called. Delayed computation is for fast
    091     * intialization when sub-millisecond timing is needed.
    092     *
    093     * @param message NTP message packet
    094     * @param returnTime  destination receive time
    095     * @param comments  list of comments used to store errors/warnings with message
    096     * @param doComputeDetails  flag to pre-compute delay/offset values
    097     * @throws IllegalArgumentException if message is null
    098     */
    099    public TimeInfo(final NtpV3Packet message, final long returnTime, final List<String> comments,
    100                   final boolean doComputeDetails)
    101    {
    102        if (message == null) {
    103            throw new IllegalArgumentException("message cannot be null");
    104        }
    105        this.returnTime = returnTime;
    106        this.message = message;
    107        this.comments = comments;
    108        if (doComputeDetails) {
    109            computeDetails();
    110        }
    111    }
    112
    113    /**
    114     * Add comment (error/warning) to list of comments associated
    115     * with processing of NTP parameters. If comment list not create
    116     * then one will be created.
    117     *
    118     * @param comment the comment
    119     */
    120    public void addComment(final String comment)
    121    {
    122        if (comments == null) {
    123            comments = new ArrayList<>();
    124        }
    125        comments.add(comment);
    126    }
    127
    128    /**
    129     * Compute and validate details of the NTP message packet. Computed
    130     * fields include the offset and delay.
    131     */
    132    public void computeDetails()
    133    {
    134        if (detailsComputed) {
    135            return; // details already computed - do nothing
    136        }
    137        detailsComputed = true;
    138        if (comments == null) {
    139            comments = new ArrayList<>();
    140        }
    141
    142        final TimeStamp origNtpTime = message.getOriginateTimeStamp();
    143        final long origTime = origNtpTime.getTime();
    144
    145        // Receive Time is time request received by server (t2)
    146        final TimeStamp rcvNtpTime = message.getReceiveTimeStamp();
    147        final long rcvTime = rcvNtpTime.getTime();
    148
    149        // Transmit time is time reply sent by server (t3)
    150        final TimeStamp xmitNtpTime = message.getTransmitTimeStamp();
    151        final long xmitTime = xmitNtpTime.getTime();
    152
    153        /*
    154         * Round-trip network delay and local clock offset (or time drift) is calculated
    155         * according to this standard NTP equation:
    156         *
    157         * LocalClockOffset = ((ReceiveTimestamp - OriginateTimestamp) +
    158         *                     (TransmitTimestamp - DestinationTimestamp)) / 2
    159         *
    160         * equations from RFC-1305 (NTPv3)
    161         *      roundtrip delay = (t4 - t1) - (t3 - t2)
    162         *      local clock offset = ((t2 - t1) + (t3 - t4)) / 2
    163         *
    164         * It takes into account network delays and assumes that they are symmetrical.
    165         *
    166         * Note the typo in SNTP RFCs 1769/2030 which state that the delay
    167         * is (T4 - T1) - (T2 - T3) with the "T2" and "T3" switched.
    168         */
    169        if (origNtpTime.ntpValue() == 0)
    170        {
    171            // without originate time cannot determine when packet went out
    172            // might be via a broadcast NTP packet...
    173            if (xmitNtpTime.ntpValue() != 0)
    174            {
    175                offset = Long.valueOf(xmitTime - returnTime);
    176                comments.add("Error: zero orig time -- cannot compute delay");
    177            } else {
    178                comments.add("Error: zero orig time -- cannot compute delay/offset");
    179            }
    180        } else if (rcvNtpTime.ntpValue() == 0 || xmitNtpTime.ntpValue() == 0) {
    181            comments.add("Warning: zero rcvNtpTime or xmitNtpTime");
    182            // assert destTime >= origTime since network delay cannot be negative
    183            if (origTime > returnTime) {
    184                comments.add("Error: OrigTime > DestRcvTime");
    185            } else {
    186                // without receive or xmit time cannot figure out processing time
    187                // so delay is simply the network travel time
    188                delay = Long.valueOf(returnTime - origTime);
    189            }
    190            // TODO: is offset still valid if rcvNtpTime=0 || xmitNtpTime=0 ???
    191            // Could always hash origNtpTime (sendTime) but if host doesn't set it
    192            // then it's an malformed ntp host anyway and we don't care?
    193            // If server is in broadcast mode then we never send out a query in first place...
    194            if (rcvNtpTime.ntpValue() != 0)
    195            {
    196                // xmitTime is 0 just use rcv time
    197                offset = Long.valueOf(rcvTime - origTime);
    198            } else if (xmitNtpTime.ntpValue() != 0)
    199            {
    200                // rcvTime is 0 just use xmitTime time
    201                offset = Long.valueOf(xmitTime - returnTime);
    202            }
    203        } else
    204        {
    205             long delayValue = returnTime - origTime;
    206             // assert xmitTime >= rcvTime: difference typically < 1ms
    207             if (xmitTime < rcvTime)
    208             {
    209                 // server cannot send out a packet before receiving it...
    210                 comments.add("Error: xmitTime < rcvTime"); // time-travel not allowed
    211             } else
    212             {
    213                 // subtract processing time from round-trip network delay
    214                 final long delta = xmitTime - rcvTime;
    215                 // in normal cases the processing delta is less than
    216                 // the total roundtrip network travel time.
    217                 if (delta <= delayValue)
    218                 {
    219                     delayValue -= delta; // delay = (t4 - t1) - (t3 - t2)
    220                 } else
    221                 {
    222                     // if delta - delayValue == 1 ms then it's a round-off error
    223                     // e.g. delay=3ms, processing=4ms
    224                     if (delta - delayValue == 1)
    225                     {
    226                         // delayValue == 0 -> local clock saw no tick change but destination clock did
    227                         if (delayValue != 0)
    228                         {
    229                             comments.add("Info: processing time > total network time by 1 ms -> assume zero delay");
    230                             delayValue = 0;
    231                         }
    232                     } else {
    233                        comments.add("Warning: processing time > total network time");
    234                    }
    235                 }
    236             }
    237             delay = Long.valueOf(delayValue);
    238            if (origTime > returnTime) {
    239                comments.add("Error: OrigTime > DestRcvTime");
    240            }
    241
    242            offset = Long.valueOf(((rcvTime - origTime) + (xmitTime - returnTime)) / 2);
    243        }
    244    }
    245
    246    /**
    247     * Return list of comments (if any) during processing of NTP packet.
    248     *
    249     * @return List or null if not yet computed
    250     */
    251    public List<String> getComments()
    252    {
    253        return comments;
    254    }
    255
    256    /**
    257     * Get round-trip network delay. If null then could not compute the delay.
    258     *
    259     * @return Long or null if delay not available.
    260     */
    261    public Long getDelay()
    262    {
    263        return delay;
    264    }
    265
    266    /**
    267     * Get clock offset needed to adjust local clock to match remote clock. If null then could not
    268     * compute the offset.
    269     *
    270     * @return Long or null if offset not available.
    271     */
    272    public Long getOffset()
    273    {
    274        return offset;
    275    }
    276
    277    /**
    278     * Returns NTP message packet.
    279     *
    280     * @return NTP message packet.
    281     */
    282    public NtpV3Packet getMessage()
    283    {
    284        return message;
    285    }
    286
    287    /**
    288     * Get host address from message datagram if available
    289     * @return host address of available otherwise null
    290     * @since 3.4
    291     */
    292    public InetAddress getAddress() {
    293        final DatagramPacket pkt = message.getDatagramPacket();
    294        return pkt == null ? null : pkt.getAddress();
    295    }
    296
    297    /**
    298     * Returns time at which time message packet was received by local machine.
    299     *
    300     * @return packet return time.
    301     */
    302    public long getReturnTime()
    303    {
    304        return returnTime;
    305    }
    306
    307    /**
    308     * Compares this object against the specified object.
    309     * The result is <code>true</code> if and only if the argument is
    310     * not <code>null</code> and is a <code>TimeStamp</code> object that
    311     * contains the same values as this object.
    312     *
    313     * @param   obj   the object to compare with.
    314     * @return  <code>true</code> if the objects are the same;
    315     *          <code>false</code> otherwise.
    316     * @since 3.4
    317     */
    318    @Override
    319    public boolean equals(final Object obj)
    320    {
    321        if (this == obj) {
    322            return true;
    323        }
    324        if (obj == null || getClass() != obj.getClass()) {
    325            return false;
    326        }
    327        final TimeInfo other = (TimeInfo) obj;
    328        return returnTime == other.returnTime && message.equals(other.message);
    329    }
    330
    331    /**
    332     * Computes a hashcode for this object. The result is the exclusive
    333     * OR of the return time and the message hash code.
    334     *
    335     * @return  a hash code value for this object.
    336     * @since 3.4
    337     */
    338    @Override
    339    public int hashCode()
    340    {
    341        final int prime = 31;
    342        int result = (int)returnTime;
    343        result = prime * result + message.hashCode();
    344        return result;
    345    }
    346
    347}