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