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