    001    package 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
    020    import java.util.ArrayList;
    021    import java.util.List;
    022
    023    /**
    024     * Wrapper class to network time packet messages (NTP, etc) that computes
    025     * related timing info and stats.
    026     *
    027     * @author Jason Mathews, MITRE Corp
    028     *
    029     * @version $Revision: 929649 $ $Date: 2010-03-31 19:12:07 +0100 (Wed, 31 Mar 2010) $
    030     */
    031    public class TimeInfo {
    032
    033        private final NtpV3Packet _message;
    034        private List<String> _comments;
    035        private Long _delay;
    036        private Long _offset;
    037
    038        /**
    039         * time at which time message packet was received by local machine
    040         */
    041        private final long _returnTime;
    042
    043        /**
    044         * flag indicating that the TimeInfo details was processed and delay/offset were computed
    045         */
    046        private boolean _detailsComputed;
    047
    048        /**
    049         * Create TimeInfo object with raw packet message and destination time received.
    050         *
    051         * @param message NTP message packet
    052         * @param returnTime  destination receive time
    053         * @throws IllegalArgumentException if message is null
    054         */
    055        public TimeInfo(NtpV3Packet message, long returnTime) {
    056            this(message, returnTime, null, true);
    057        }
    058
    059        /**
    060         * Create TimeInfo object with raw packet message and destination time received.
    061         *
    062         * @param message NTP message packet
    063         * @param returnTime  destination receive time
    064         * @param comments List of errors/warnings identified during processing
    065         * @throws IllegalArgumentException if message is null
    066         */
    067        public TimeInfo(NtpV3Packet message, long returnTime, List<String> comments)
    068        {
    069                this(message, returnTime, comments, true);
    070        }
    071
    072        /**
    073         * Create TimeInfo object with raw packet message and destination time received.
    074         * Auto-computes details if computeDetails flag set otherwise this is delayed
    075         * until computeDetails() is called. Delayed computation is for fast
    076         * intialization when sub-millisecond timing is needed.
    077         *
    078         * @param msgPacket NTP message packet
    079         * @param returnTime  destination receive time
    080         * @param doComputeDetails  flag to pre-compute delay/offset values
    081         * @throws IllegalArgumentException if message is null
    082         */
    083        public TimeInfo(NtpV3Packet msgPacket, long returnTime, boolean doComputeDetails)
    084        {
    085                this(msgPacket, returnTime, null, doComputeDetails);
    086        }
    087
    088        /**
    089         * Create TimeInfo object with raw packet message and destination time received.
    090         * Auto-computes details if computeDetails flag set otherwise this is delayed
    091         * until computeDetails() is called. Delayed computation is for fast
    092         * intialization when sub-millisecond timing is needed.
    093         *
    094         * @param message NTP message packet
    095         * @param returnTime  destination receive time
    096         * @param comments  list of comments used to store errors/warnings with message
    097         * @param doComputeDetails  flag to pre-compute delay/offset values
    098         * @throws IllegalArgumentException if message is null
    099         */
    100        public TimeInfo(NtpV3Packet message, long returnTime, List<String> comments,
    101                       boolean doComputeDetails)
    102        {
    103            if (message == null)
    104                throw new IllegalArgumentException("message cannot be null");
    105            this._returnTime = returnTime;
    106            this._message = message;
    107            this._comments = comments;
    108            if (doComputeDetails)
    109                computeDetails();
    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
    118         */
    119        public void addComment(String comment)
    120        {
    121            if (_comments == null) {
    122                _comments = new ArrayList<String>();
    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<String>();
    139            }
    140
    141            TimeStamp origNtpTime = _message.getOriginateTimeStamp();
    142            long origTime = origNtpTime.getTime();
    143
    144            // Receive Time is time request received by server (t2)
    145            TimeStamp rcvNtpTime = _message.getReceiveTimeStamp();
    146            long rcvTime = rcvNtpTime.getTime();
    147
    148            // Transmit time is time reply sent by server (t3)
    149            TimeStamp xmitNtpTime = _message.getTransmitTimeStamp();
    150            long xmitTime = 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                    _offset = Long.valueOf(xmitTime - _returnTime);
    175                    _comments.add("Error: zero orig time -- cannot compute delay");
    176                } else
    177                    _comments.add("Error: zero orig time -- cannot compute delay/offset");
    178            } else if (rcvNtpTime.ntpValue() == 0 || xmitNtpTime.ntpValue() == 0)
    179            {
    180                _comments.add("Warning: zero rcvNtpTime or xmitNtpTime");
    181                // assert destTime >= origTime since network delay cannot be negative
    182                if (origTime > _returnTime)
    183                    _comments.add("Error: OrigTime > DestRcvTime");
    184                else
    185                {
    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                     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                 _delay = Long.valueOf(delayValue);
    237                if (origTime > _returnTime) // assert destTime >= origTime
    238                    _comments.add("Error: OrigTime > DestRcvTime");
    239
    240                _offset = Long.valueOf(((rcvTime - origTime) + (xmitTime - _returnTime)) / 2);
    241            }
    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        {
    251            return _comments;
    252        }
    253
    254        /**
    255         * Get round-trip network delay. If null then could not compute the delay.
    256         *
    257         * @return Long or null if delay not available.
    258         */
    259        public Long getDelay()
    260        {
    261            return _delay;
    262        }
    263
    264        /**
    265         * Get clock offset needed to adjust local clock to match remote clock. If null then could not
    266         * compute the offset.
    267         *
    268         * @return Long or null if offset not available.
    269         */
    270        public Long getOffset()
    271        {
    272            return _offset;
    273        }
    274
    275        /**
    276         * Returns NTP message packet.
    277         *
    278         * @return NTP message packet.
    279         */
    280        public NtpV3Packet getMessage()
    281        {
    282            return _message;
    283        }
    284
    285        /**
    286         * Returns time at which time message packet was received by local machine.
    287         *
    288         * @return packet return time.
    289         */
    290        public long getReturnTime()
    291        {
    292            return _returnTime;
    293        }
    294
    295    }