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}