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 }