View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.ntp;
19  
20  import java.net.DatagramPacket;
21  import java.net.InetAddress;
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  /**
26   * Wrapper class to network time packet messages (NTP, etc.) that computes related timing info and stats.
27   */
28  public class TimeInfo {
29  
30      private final NtpV3Packet message;
31      private List<String> comments;
32      private Long delayMillis;
33      private Long offsetMillis;
34  
35      /**
36       * time at which time message packet was received by local machine
37       */
38      private final long returnTimeMillis;
39  
40      /**
41       * flag indicating that the TimeInfo details was processed and delay/offset were computed
42       */
43      private boolean detailsComputed;
44  
45      /**
46       * Create TimeInfo object with raw packet message and destination time received.
47       *
48       * @param message          NTP message packet
49       * @param returnTimeMillis destination receive time
50       * @throws IllegalArgumentException if message is null
51       */
52      public TimeInfo(final NtpV3Packet message, final long returnTimeMillis) {
53          this(message, returnTimeMillis, null, true);
54      }
55  
56      /**
57       * Create TimeInfo object with raw packet message and destination time received. Auto-computes details if computeDetails flag set otherwise this is delayed
58       * until computeDetails() is called. Delayed computation is for fast initialization when sub-millisecond timing is needed.
59       *
60       * @param msgPacket        NTP message packet
61       * @param returnTimeMillis destination receive time
62       * @param doComputeDetails flag to pre-compute delay/offset values
63       * @throws IllegalArgumentException if message is null
64       */
65      public TimeInfo(final NtpV3Packet msgPacket, final long returnTimeMillis, final boolean doComputeDetails) {
66          this(msgPacket, returnTimeMillis, null, doComputeDetails);
67      }
68  
69      /**
70       * Create TimeInfo object with raw packet message and destination time received.
71       *
72       * @param message          NTP message packet
73       * @param returnTimeMillis destination receive time
74       * @param comments         List of errors/warnings identified during processing
75       * @throws IllegalArgumentException if message is null
76       */
77      public TimeInfo(final NtpV3Packet message, final long returnTimeMillis, final List<String> comments) {
78          this(message, returnTimeMillis, comments, true);
79      }
80  
81      /**
82       * Create TimeInfo object with raw packet message and destination time received. Auto-computes details if computeDetails flag set otherwise this is delayed
83       * until computeDetails() is called. Delayed computation is for fast initialization when sub-millisecond timing is needed.
84       *
85       * @param message          NTP message packet
86       * @param returnTimeMillis destination receive time
87       * @param comments         list of comments used to store errors/warnings with message
88       * @param doComputeDetails flag to pre-compute delay/offset values
89       * @throws IllegalArgumentException if message is null
90       */
91      public TimeInfo(final NtpV3Packet message, final long returnTimeMillis, final List<String> comments, final boolean doComputeDetails) {
92          if (message == null) {
93              throw new IllegalArgumentException("message cannot be null");
94          }
95          this.returnTimeMillis = returnTimeMillis;
96          this.message = message;
97          this.comments = comments;
98          if (doComputeDetails) {
99              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} if and only if the argument is not {@code null} 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} if the objects are the same; {@code false} 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      * Gets 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      * Gets 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      * Gets 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 hash code 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         final int result = (int) returnTimeMillis;
299         return prime * result + message.hashCode();
300     }
301 
302 }