1 // NAME 2 // $RCSfile: TimeWindow.java,v $ 3 // DESCRIPTION 4 // [given below in javadoc format] 5 // DELTA 6 // $Revision: 3.15 $ 7 // CREATED 8 // $Date: 2007/04/12 12:55:28 $ 9 // COPYRIGHT 10 // Westhawk Ltd 11 // TO DO 12 // 13 14 /* 15 * Copyright (C) 2000 - 2006 by Westhawk Ltd 16 * <a href="www.westhawk.co.uk">www.westhawk.co.uk</a> 17 * 18 * Permission to use, copy, modify, and distribute this software 19 * for any purpose and without fee is hereby granted, provided 20 * that the above copyright notices appear in all copies and that 21 * both the copyright notice and this permission notice appear in 22 * supporting documentation. 23 * This software is provided "as is" without express or implied 24 * warranty. 25 */ 26 package uk.co.westhawk.snmp.stack; 27 28 /*- 29 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ 30 * SNMP Java Client 31 * ჻჻჻჻჻჻ 32 * Copyright 2023 MetricsHub, Westhawk 33 * ჻჻჻჻჻჻ 34 * This program is free software: you can redistribute it and/or modify 35 * it under the terms of the GNU Lesser General Public License as 36 * published by the Free Software Foundation, either version 3 of the 37 * License, or (at your option) any later version. 38 * 39 * This program is distributed in the hope that it will be useful, 40 * but WITHOUT ANY WARRANTY; without even the implied warranty of 41 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 42 * GNU General Lesser Public License for more details. 43 * 44 * You should have received a copy of the GNU General Lesser Public 45 * License along with this program. If not, see 46 * <http://www.gnu.org/licenses/lgpl-3.0.html>. 47 * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ 48 */ 49 import java.util.*; 50 51 /** 52 * TimeWindow contains the lookup tables for the engine Id information. 53 * TimeWindow should be created only once. Use the 54 * <code>getCurrent()</code> 55 * method to access any other method, i.e. 56 * <pre> 57 * if (TimeWindow.getCurrent() == null) 58 * { 59 * TimeWindow timew = new TimeWindow(); 60 * } 61 * boolean known = TimeWindow.getCurrent().isSnmpEngineIdKnown(hostaddr, port); 62 * </pre> 63 * 64 * <p> 65 * This class contains two lookup tables. One that maps the 66 * host address+port onto the SNMP engine ID and one that keeps the SNMP 67 * engine ID with the timeline details about this engine. 68 * </p> 69 * 70 * @see #getCurrent() 71 * @author <a href="mailto:snmp@westhawk.co.uk">Birgit Arkesteijn</a> 72 * @version $Revision: 3.15 $ $Date: 2007/04/12 12:55:28 $ 73 */ 74 public class TimeWindow { 75 private static final String version_id = "@(#)$Id: TimeWindow.java,v 3.15 2007/04/12 12:55:28 birgita Exp $ Copyright Westhawk Ltd"; 76 77 /** 78 * The maximum number of seconds the engine time in the PDU is allowed 79 * to differ from my estimated engine time. Values <code>150</code>. 80 */ 81 public final static int MaxTimeDifference = 150; 82 83 private static TimeWindow current = null; 84 85 // lookup table hostaddr:port -> engine id 86 private Hashtable hostLookup; 87 88 // lookup table engine id -> TimeWindowNode 89 private Hashtable engineLookup; 90 91 private long startTime; 92 93 /** 94 * Constructor. 95 */ 96 public TimeWindow() { 97 if (current == null) { 98 current = this; 99 100 hostLookup = new Hashtable(5); 101 engineLookup = new Hashtable(5); 102 } 103 } 104 105 /** 106 * Returns the current reference to this class. 107 * TimeWindow should be created only once. Use this method 108 * to access any other method, i.e. 109 * 110 * <pre> 111 * if (TimeWindow.getCurrent() == null) { 112 * TimeWindow timew = new TimeWindow(); 113 * } 114 * boolean known = TimeWindow.getCurrent().isSnmpEngineIdKnown(hostaddr, port); 115 * </pre> 116 * 117 * @return the current time window 118 */ 119 public static TimeWindow getCurrent() { 120 return current; 121 } 122 123 /** 124 * Returns the snmp engine ID. This method will lookup the engine ID 125 * based on the host address and port. If it the engine ID is not known, null 126 * will be returned. 127 * 128 * @param hostaddr The host address of the engine ID 129 * @param port The port number of the engine ID 130 * @return the snmp engine ID 131 * @see #isSnmpEngineIdKnown(String, int) 132 */ 133 public String getSnmpEngineId(String hostaddr, int port) { 134 String key = getKey(hostaddr, port); 135 String snmpEngineId = (String) hostLookup.get(key); 136 return snmpEngineId; 137 } 138 139 /** 140 * Returns if the snmp engine ID is known. This method will lookup if the 141 * engine ID belonging to this hostaddr and port is known. 142 * <p> 143 * When the SNMP engine ID is known, this doesn't necessarily mean that 144 * the timeline details of this engine ID are known, since it takes a 145 * second discovery step to find out. 146 * </p> 147 * 148 * @param hostaddr The host address of the engine ID 149 * @param port The port number of the engine ID 150 * @return whether the snmp engine ID is known 151 */ 152 public boolean isSnmpEngineIdKnown(String hostaddr, int port) { 153 String key = getKey(hostaddr, port); 154 return hostLookup.containsKey(key); 155 } 156 157 /** 158 * Sets the SNMP engine ID that belongs to the specified hostaddr and port. 159 * The old SNMP engine ID (if any) will be overwritten. 160 * 161 * @param hostaddr The host address of the engine ID 162 * @param port The port number of the engine ID 163 * @param snmpEngineId The engine ID 164 */ 165 public void setSnmpEngineId(String hostaddr, int port, String snmpEngineId) { 166 String key = getKey(hostaddr, port); 167 if (AsnObject.debug > 4) { 168 System.out.println(); 169 System.out.println(getClass().getName() + ".setSnmpEngineId(): hostaddr '" 170 + hostaddr + "', port '" + port 171 + "', snmpEngineId '" + snmpEngineId 172 + "', key '" + key + "'"); 173 } 174 hostLookup.put(key, snmpEngineId); 175 } 176 177 /** 178 * Checks if the engine ID is OK. If there is no engine ID known for 179 * this hostaddr and port, the specified engine ID is added to the table. 180 * <p> 181 * If there is 182 * already an engine ID for this hostaddr and port, the method returns true 183 * if the specified engine ID is the same as the existing one, and false if 184 * they differ. In the latter case the engine ID in the table is not updated. 185 * </p> 186 * 187 * @param hostaddr The host address of the engine ID 188 * @param port The port number of the engine ID 189 * @param snmpEngineId The engine ID 190 * @return whether the engine ID matches the stored engine ID 191 * 192 * @see #setSnmpEngineId(String, int, String) 193 */ 194 public boolean isEngineIdOK(String hostaddr, int port, String snmpEngineId) { 195 boolean ok = true; 196 String key = getKey(hostaddr, port); 197 if (hostLookup.containsKey(key) == false) { 198 setSnmpEngineId(hostaddr, port, snmpEngineId); 199 } else { 200 String myEngineId = getSnmpEngineId(hostaddr, port); 201 if (myEngineId.equalsIgnoreCase(snmpEngineId) == false) { 202 ok = false; 203 } 204 } 205 206 if (AsnObject.debug > 4) { 207 System.out.println(); 208 System.out.println(getClass().getName() + ".isEngineIdOK(): hostaddr '" 209 + hostaddr + "', port '" + port 210 + "', snmpEngineId '" + snmpEngineId 211 + "', ok " + ok); 212 } 213 return ok; 214 } 215 216 /** 217 * Returns if the timeline details of this snmp engine ID are known. 218 * 219 * @param snmpEngineId The engine ID 220 * @return whether the timeline details are known 221 */ 222 public boolean isTimeLineKnown(String snmpEngineId) { 223 return engineLookup.containsKey(snmpEngineId); 224 } 225 226 /** 227 * Returns if the time details are outside the time window. 228 * When a response or report is received, the stack first checks the time 229 * window before updating it. It always does an update afterwards, even if 230 * the message was outside the time window! 231 * 232 * @param snmpEngineId The SNMP engine ID 233 * @param bootsA The SNMP engine boots 234 * @param timeA The SNMP engine time 235 * @return true if outside or when no details can be found, false if 236 * inside time window 237 * @see #updateTimeWindow(String, int, int, boolean) 238 */ 239 public boolean isOutsideTimeWindow(String snmpEngineId, int bootsA, 240 int timeA) { 241 boolean isOut = false; 242 243 TimeWindowNode node = getTimeLine(snmpEngineId); 244 if (node != null) { 245 int bootsL = node.getSnmpEngineBoots(); 246 int timeL = node.getSnmpEngineTime(); 247 if (bootsA == TimeWindowNode.maxTime 248 || 249 bootsA < bootsL 250 || 251 (bootsA == bootsL && timeA < (timeL - MaxTimeDifference))) { 252 isOut = true; 253 } 254 } else { 255 // We don't have any info, so by definition it is not out. 256 isOut = false; 257 } 258 return isOut; 259 } 260 261 /** 262 * Tries to update the time window and returns if succeeded. 263 * When a response or report is received, first check the time window 264 * before updating it. 265 * 266 * <p> 267 * An update will only occur if the message was authentic 268 * and the bootsA and timeA meet the requirements. 269 * New data will be inserted if the (bootsA > 0), irrespectively 270 * whether the message was authentic or not. 271 * </p> 272 * 273 * @param snmpEngineId The SNMP engine ID 274 * @param bootsA The SNMP engine boots 275 * @param timeA The SNMP engine time 276 * @return true if update succeeded, or false when not succeeded or when 277 * no details could be found. 278 * @see #isOutsideTimeWindow(String, int, int) 279 */ 280 public boolean updateTimeWindow(String snmpEngineId, int bootsA, int timeA, boolean isAuthentic) { 281 boolean updated = false; 282 283 TimeWindowNode node = getTimeLine(snmpEngineId); 284 if (node != null) { 285 if (isAuthentic) { 286 int bootsL = node.getSnmpEngineBoots(); 287 int latestL = node.getLatestReceivedEngineTime(); 288 289 if (bootsA > bootsL 290 || 291 (bootsA == bootsL && timeA > latestL)) { 292 synchronized (this) { 293 node.setSnmpEngineBoots(bootsA); 294 node.setSnmpEngineTime(timeA); 295 updated = true; 296 } 297 } 298 } 299 } else if (bootsA > 0 || timeA > 0) { 300 // @since 5_2, initially it didn't save when bootsA equals zero 301 node = new TimeWindowNode(snmpEngineId, bootsA, timeA); 302 setTimeLine(snmpEngineId, node); 303 } 304 305 if (AsnObject.debug > 4) { 306 System.out.println(); 307 System.out.println(getClass().getName() + ".updateTimeWindow(): snmpEngineId '" 308 + snmpEngineId 309 + "', bootsA " + bootsA 310 + ", timeA " + timeA 311 + ", isAuthentic " + isAuthentic 312 + ", updated " + updated); 313 } 314 return updated; 315 } 316 317 /** 318 * Clear all timing information for the given engine ID. 319 * 320 * This stinks, but occasionally the router's time window will 321 * slip outside of the acceptable window or will reboot without 322 * updating its "reboots" parameter. If you care more about 323 * security than functionality then never ever use this. 324 * Added on request of Steve A Cochran (steve@more.net). 325 * 326 * @param snmpEngineId The engine to clear 327 * 328 * @since 5_2 329 */ 330 public void clearTimeWindow(String snmpEngineId) { 331 if (engineLookup.containsKey(snmpEngineId)) { 332 // Remove from engine lookup table 333 engineLookup.remove(snmpEngineId); 334 335 // Remove any entries in the hostLookup table that point to 336 // this snmpEngineId 337 Vector v = new Vector(); 338 Iterator i = hostLookup.keySet().iterator(); 339 while (i.hasNext()) { 340 String key = (String) (i.next()); 341 if ((hostLookup.get(key)).equals(snmpEngineId)) { 342 v.add(key); 343 } 344 } 345 i = v.iterator(); 346 while (i.hasNext()) { 347 hostLookup.remove(i.next()); 348 } 349 } 350 } 351 352 /** 353 * Updates the estimated engine time of all gathered time details. 354 * It calculates the seconds that passed since the last call and 355 * updates all time window nodes accordingly. 356 * 357 * @see #setTimeLine(String, TimeWindowNode) 358 * @see #getTimeLine(String) 359 */ 360 protected void updateTimeWindows() { 361 if (engineLookup.size() > 0) { 362 long now = System.currentTimeMillis(); 363 long milli = now - startTime; 364 int sec = (int) (milli / 1000L); 365 366 long lostMillis = milli - (sec * 1000L); 367 if (lostMillis < 0L) { 368 lostMillis = 0L; 369 } 370 startTime = now - lostMillis; 371 372 Enumeration nodes = engineLookup.elements(); 373 while (nodes.hasMoreElements()) { 374 TimeWindowNode node = (TimeWindowNode) nodes.nextElement(); 375 node.incrementSnmpEngineTime(sec); 376 } 377 } else { 378 startTime = System.currentTimeMillis(); 379 } 380 } 381 382 /** 383 * Returns the key to the engine ID lookup table, based on the specified 384 * host address and port. 385 * 386 * @param hostaddr The host address 387 * @param port The port 388 * @return the key 389 */ 390 protected String getKey(String hostaddr, int port) { 391 return hostaddr + ":" + port; 392 } 393 394 /** 395 * Returns the timeline details of the snmp engine ID. 396 * If there are no matching timeline details for this engine ID, null 397 * will be returned. 398 * The timeline details will be updated before the node is retrieved 399 * from the table. 400 * 401 * @param snmpEngineId The engine ID 402 * @return The timeline details 403 * @see #updateTimeWindows() 404 */ 405 protected TimeWindowNode getTimeLine(String snmpEngineId) { 406 updateTimeWindows(); 407 TimeWindowNode node = (TimeWindowNode) engineLookup.get(snmpEngineId); 408 return node; 409 } 410 411 /** 412 * Sets the timeline details of the snmp engine ID. 413 * The timeline details will be updated before the node is put 414 * in the table. 415 * 416 * @param snmpEngineId The engine ID 417 * @param newNode The added time window node node 418 * @return The timeline details 419 * @see #updateTimeWindows() 420 */ 421 protected TimeWindowNode setTimeLine(String snmpEngineId, 422 TimeWindowNode newNode) { 423 updateTimeWindows(); 424 engineLookup.put(snmpEngineId, newNode); 425 if (AsnObject.debug > 4) { 426 System.out.println(); 427 System.out.println(getClass().getName() + ".setTimeLine(): snmpEngineId " 428 + snmpEngineId 429 + ", node " + newNode); 430 } 431 return newNode; 432 } 433 434 /** 435 * Returns the string representation. 436 * 437 * @since 4_14 438 */ 439 public String toString() { 440 StringBuffer buffer = new StringBuffer(this.getClass().getName()); 441 buffer.append("["); 442 443 Enumeration enum1 = hostLookup.keys(); 444 while (enum1.hasMoreElements()) { 445 String key = (String) enum1.nextElement(); 446 String snmpEngineId = (String) hostLookup.get(key); 447 TimeWindowNode node = (TimeWindowNode) engineLookup.get(snmpEngineId); 448 buffer.append("\n\t("); 449 if (node == null) { 450 buffer.append("key=").append(key); 451 buffer.append(", engineId=").append(snmpEngineId); 452 } else { 453 buffer.append("key=").append(key).append(", "); 454 buffer.append(node.toString()); 455 } 456 buffer.append(") "); 457 } 458 459 buffer.append("]"); 460 return buffer.toString(); 461 } 462 463 }