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}