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