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}