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 }