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 * https://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} 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 * Gets the 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 the 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 * Gets the NTP message packet.
264 *
265 * @return NTP message packet.
266 */
267 public NtpV3Packet getMessage() {
268 return message;
269 }
270
271 /**
272 * Gets the 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 * Gets the 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 }