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 * 029 */ 030public class TimeInfo { 031 032 private final NtpV3Packet _message; 033 private List<String> _comments; 034 private Long _delay; 035 private Long _offset; 036 037 /** 038 * time at which time message packet was received by local machine 039 */ 040 private final long _returnTime; 041 042 /** 043 * flag indicating that the TimeInfo details was processed and delay/offset were computed 044 */ 045 private boolean _detailsComputed; 046 047 /** 048 * Create TimeInfo object with raw packet message and destination time received. 049 * 050 * @param message NTP message packet 051 * @param returnTime destination receive time 052 * @throws IllegalArgumentException if message is null 053 */ 054 public TimeInfo(NtpV3Packet message, long returnTime) { 055 this(message, returnTime, null, true); 056 } 057 058 /** 059 * Create TimeInfo object with raw packet message and destination time received. 060 * 061 * @param message NTP message packet 062 * @param returnTime destination receive time 063 * @param comments List of errors/warnings identified during processing 064 * @throws IllegalArgumentException if message is null 065 */ 066 public TimeInfo(NtpV3Packet message, long returnTime, List<String> comments) 067 { 068 this(message, returnTime, comments, true); 069 } 070 071 /** 072 * Create TimeInfo object with raw packet message and destination time received. 073 * Auto-computes details if computeDetails flag set otherwise this is delayed 074 * until computeDetails() is called. Delayed computation is for fast 075 * intialization when sub-millisecond timing is needed. 076 * 077 * @param msgPacket NTP message packet 078 * @param returnTime destination receive time 079 * @param doComputeDetails flag to pre-compute delay/offset values 080 * @throws IllegalArgumentException if message is null 081 */ 082 public TimeInfo(NtpV3Packet msgPacket, long returnTime, boolean doComputeDetails) 083 { 084 this(msgPacket, returnTime, null, doComputeDetails); 085 } 086 087 /** 088 * Create TimeInfo object with raw packet message and destination time received. 089 * Auto-computes details if computeDetails flag set otherwise this is delayed 090 * until computeDetails() is called. Delayed computation is for fast 091 * intialization when sub-millisecond timing is needed. 092 * 093 * @param message NTP message packet 094 * @param returnTime destination receive time 095 * @param comments list of comments used to store errors/warnings with message 096 * @param doComputeDetails flag to pre-compute delay/offset values 097 * @throws IllegalArgumentException if message is null 098 */ 099 public TimeInfo(NtpV3Packet message, long returnTime, List<String> comments, 100 boolean doComputeDetails) 101 { 102 if (message == null) { 103 throw new IllegalArgumentException("message cannot be null"); 104 } 105 this._returnTime = returnTime; 106 this._message = message; 107 this._comments = comments; 108 if (doComputeDetails) { 109 computeDetails(); 110 } 111 } 112 113 /** 114 * Add comment (error/warning) to list of comments associated 115 * with processing of NTP parameters. If comment list not create 116 * then one will be created. 117 * 118 * @param comment the comment 119 */ 120 public void addComment(String comment) 121 { 122 if (_comments == null) { 123 _comments = new ArrayList<String>(); 124 } 125 _comments.add(comment); 126 } 127 128 /** 129 * Compute and validate details of the NTP message packet. Computed 130 * fields include the offset and delay. 131 */ 132 public void computeDetails() 133 { 134 if (_detailsComputed) { 135 return; // details already computed - do nothing 136 } 137 _detailsComputed = true; 138 if (_comments == null) { 139 _comments = new ArrayList<String>(); 140 } 141 142 TimeStamp origNtpTime = _message.getOriginateTimeStamp(); 143 long origTime = origNtpTime.getTime(); 144 145 // Receive Time is time request received by server (t2) 146 TimeStamp rcvNtpTime = _message.getReceiveTimeStamp(); 147 long rcvTime = rcvNtpTime.getTime(); 148 149 // Transmit time is time reply sent by server (t3) 150 TimeStamp xmitNtpTime = _message.getTransmitTimeStamp(); 151 long xmitTime = xmitNtpTime.getTime(); 152 153 /* 154 * Round-trip network delay and local clock offset (or time drift) is calculated 155 * according to this standard NTP equation: 156 * 157 * LocalClockOffset = ((ReceiveTimestamp - OriginateTimestamp) + 158 * (TransmitTimestamp - DestinationTimestamp)) / 2 159 * 160 * equations from RFC-1305 (NTPv3) 161 * roundtrip delay = (t4 - t1) - (t3 - t2) 162 * local clock offset = ((t2 - t1) + (t3 - t4)) / 2 163 * 164 * It takes into account network delays and assumes that they are symmetrical. 165 * 166 * Note the typo in SNTP RFCs 1769/2030 which state that the delay 167 * is (T4 - T1) - (T2 - T3) with the "T2" and "T3" switched. 168 */ 169 if (origNtpTime.ntpValue() == 0) 170 { 171 // without originate time cannot determine when packet went out 172 // might be via a broadcast NTP packet... 173 if (xmitNtpTime.ntpValue() != 0) 174 { 175 _offset = Long.valueOf(xmitTime - _returnTime); 176 _comments.add("Error: zero orig time -- cannot compute delay"); 177 } else { 178 _comments.add("Error: zero orig time -- cannot compute delay/offset"); 179 } 180 } else if (rcvNtpTime.ntpValue() == 0 || xmitNtpTime.ntpValue() == 0) { 181 _comments.add("Warning: zero rcvNtpTime or xmitNtpTime"); 182 // assert destTime >= origTime since network delay cannot be negative 183 if (origTime > _returnTime) { 184 _comments.add("Error: OrigTime > DestRcvTime"); 185 } else { 186 // without receive or xmit time cannot figure out processing time 187 // so delay is simply the network travel time 188 _delay = Long.valueOf(_returnTime - origTime); 189 } 190 // TODO: is offset still valid if rcvNtpTime=0 || xmitNtpTime=0 ??? 191 // Could always hash origNtpTime (sendTime) but if host doesn't set it 192 // then it's an malformed ntp host anyway and we don't care? 193 // If server is in broadcast mode then we never send out a query in first place... 194 if (rcvNtpTime.ntpValue() != 0) 195 { 196 // xmitTime is 0 just use rcv time 197 _offset = Long.valueOf(rcvTime - origTime); 198 } else if (xmitNtpTime.ntpValue() != 0) 199 { 200 // rcvTime is 0 just use xmitTime time 201 _offset = Long.valueOf(xmitTime - _returnTime); 202 } 203 } else 204 { 205 long delayValue = _returnTime - origTime; 206 // assert xmitTime >= rcvTime: difference typically < 1ms 207 if (xmitTime < rcvTime) 208 { 209 // server cannot send out a packet before receiving it... 210 _comments.add("Error: xmitTime < rcvTime"); // time-travel not allowed 211 } else 212 { 213 // subtract processing time from round-trip network delay 214 long delta = xmitTime - rcvTime; 215 // in normal cases the processing delta is less than 216 // the total roundtrip network travel time. 217 if (delta <= delayValue) 218 { 219 delayValue -= delta; // delay = (t4 - t1) - (t3 - t2) 220 } else 221 { 222 // if delta - delayValue == 1 ms then it's a round-off error 223 // e.g. delay=3ms, processing=4ms 224 if (delta - delayValue == 1) 225 { 226 // delayValue == 0 -> local clock saw no tick change but destination clock did 227 if (delayValue != 0) 228 { 229 _comments.add("Info: processing time > total network time by 1 ms -> assume zero delay"); 230 delayValue = 0; 231 } 232 } else { 233 _comments.add("Warning: processing time > total network time"); 234 } 235 } 236 } 237 _delay = Long.valueOf(delayValue); 238 if (origTime > _returnTime) { 239 _comments.add("Error: OrigTime > DestRcvTime"); 240 } 241 242 _offset = Long.valueOf(((rcvTime - origTime) + (xmitTime - _returnTime)) / 2); 243 } 244 } 245 246 /** 247 * Return list of comments (if any) during processing of NTP packet. 248 * 249 * @return List or null if not yet computed 250 */ 251 public List<String> getComments() 252 { 253 return _comments; 254 } 255 256 /** 257 * Get round-trip network delay. If null then could not compute the delay. 258 * 259 * @return Long or null if delay not available. 260 */ 261 public Long getDelay() 262 { 263 return _delay; 264 } 265 266 /** 267 * Get clock offset needed to adjust local clock to match remote clock. If null then could not 268 * compute the offset. 269 * 270 * @return Long or null if offset not available. 271 */ 272 public Long getOffset() 273 { 274 return _offset; 275 } 276 277 /** 278 * Returns NTP message packet. 279 * 280 * @return NTP message packet. 281 */ 282 public NtpV3Packet getMessage() 283 { 284 return _message; 285 } 286 287 /** 288 * Get host address from message datagram if available 289 * @return host address of available otherwise null 290 * @since 3.4 291 */ 292 public InetAddress getAddress() { 293 DatagramPacket pkt = _message.getDatagramPacket(); 294 return pkt == null ? null : pkt.getAddress(); 295 } 296 297 /** 298 * Returns time at which time message packet was received by local machine. 299 * 300 * @return packet return time. 301 */ 302 public long getReturnTime() 303 { 304 return _returnTime; 305 } 306 307 /** 308 * Compares this object against the specified object. 309 * The result is <code>true</code> if and only if the argument is 310 * not <code>null</code> and is a <code>TimeStamp</code> object that 311 * contains the same values as this object. 312 * 313 * @param obj the object to compare with. 314 * @return <code>true</code> if the objects are the same; 315 * <code>false</code> otherwise. 316 * @since 3.4 317 */ 318 @Override 319 public boolean equals(Object obj) 320 { 321 if (this == obj) { 322 return true; 323 } 324 if (obj == null || getClass() != obj.getClass()) { 325 return false; 326 } 327 TimeInfo other = (TimeInfo) obj; 328 return _returnTime == other._returnTime && _message.equals(other._message); 329 } 330 331 /** 332 * Computes a hashcode for this object. The result is the exclusive 333 * OR of the return time and the message hash code. 334 * 335 * @return a hash code value for this object. 336 * @since 3.4 337 */ 338 @Override 339 public int hashCode() 340 { 341 final int prime = 31; 342 int result = (int)_returnTime; 343 result = prime * result + _message.hashCode(); 344 return result; 345 } 346 347}