View Javadoc
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 &gt; 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 }