1 /*
   2  * Copyright (c) 2001, 2006, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.jmx.snmp;
  26 
  27 import java.net.InetAddress;
  28 import java.io.Serializable;
  29 import java.net.UnknownHostException;
  30 import java.util.StringTokenizer;
  31 import java.util.Arrays;
  32 import java.util.NoSuchElementException;
  33 
  34 import com.sun.jmx.snmp.internal.SnmpTools;
  35 
  36 /**
  37  * This class is handling an <CODE>SnmpEngineId</CODE> data. It copes with binary as well as <CODE>String</CODE> representation of an engine Id. A string format engine is an hex string starting with 0x.
  38  * <p><b>This API is a Sun Microsystems internal API  and is subject
  39  * to change without notice.</b></p>
  40  * @since 1.5
  41  */
  42 public class SnmpEngineId implements Serializable {
  43     private static final long serialVersionUID = 5434729655830763317L;
  44 
  45     byte[] engineId = null;
  46     String hexString = null;
  47     String humanString = null;
  48     /**
  49      * New <CODE>SnmpEngineId</CODE> with an hex string value. Can handle engine Id format &lt;host&gt:&lt;port&gt.
  50      * @param hexString Hexa string.
  51      */
  52     SnmpEngineId(String hexString) {
  53         engineId = SnmpTools.ascii2binary(hexString);
  54         this.hexString = hexString.toLowerCase();
  55     }
  56     /**
  57      * New <CODE>SnmpEngineId</CODE> with a binary value. You can use <CODE> SnmpTools </CODE> to convert from hex string to binary format.
  58      * @param bin Binary value
  59      */
  60     SnmpEngineId(byte[] bin) {
  61         engineId = bin;
  62         hexString = SnmpTools.binary2ascii(bin).toLowerCase();
  63     }
  64 
  65     /**
  66      * If a string of the format &lt;address&gt;:&lt;port&gt;:&lt;IANA number&gt; has been provided at creation time, this string is returned.
  67      * @return The Id as a readable string or null if not provided.
  68      */
  69     public String getReadableId() {
  70         return humanString;
  71     }
  72 
  73     /**
  74      * Returns a string format engine Id.
  75      * @return String format value.
  76      */
  77     public String toString() {
  78         return hexString;
  79     }
  80     /**
  81      * Returns a binary engine Id.
  82      * @return Binary value.
  83      */
  84     public byte[] getBytes() {
  85         return engineId;
  86     }
  87 
  88     /**
  89      * In order to store the string used to create the engineId.
  90      */
  91     void setStringValue(String val) {
  92         humanString = val;
  93     }
  94 
  95     static void validateId(String str) throws IllegalArgumentException {
  96         byte[] arr = SnmpTools.ascii2binary(str);
  97         validateId(arr);
  98     }
  99 
 100     static void validateId(byte[] arr) throws IllegalArgumentException {
 101 
 102         if(arr.length < 5) throw new IllegalArgumentException("Id size lower than 5 bytes.");
 103         if(arr.length > 32) throw new IllegalArgumentException("Id size greater than 32 bytes.");
 104 
 105         //octet strings with very first bit = 0 and length != 12 octets
 106         if( ((arr[0] & 0x80) == 0) && arr.length != 12)
 107             throw new IllegalArgumentException("Very first bit = 0 and length != 12 octets");
 108 
 109         byte[] zeroedArrays = new byte[arr.length];
 110         if(Arrays.equals(zeroedArrays, arr)) throw new IllegalArgumentException("Zeroed Id.");
 111         byte[] FFArrays = new byte[arr.length];
 112         Arrays.fill(FFArrays, (byte)0xFF);
 113         if(Arrays.equals(FFArrays, arr)) throw new IllegalArgumentException("0xFF Id.");
 114 
 115     }
 116 
 117     /**
 118      * Generates an engine Id based on the passed array.
 119      * @return The created engine Id or null if given arr is null or its length == 0;
 120      * @exception IllegalArgumentException when:
 121      * <ul>
 122      *  <li>octet string lower than 5 bytes.</li>
 123      *  <li>octet string greater than 32 bytes.</li>
 124      *  <li>octet string = all zeros.</li>
 125      *  <li>octet string = all 'ff'H.</li>
 126      *  <li>octet strings with very first bit = 0 and length != 12 octets</li>
 127      * </ul>
 128      */
 129     public static SnmpEngineId createEngineId(byte[] arr) throws IllegalArgumentException {
 130         if( (arr == null) || arr.length == 0) return null;
 131         validateId(arr);
 132         return new SnmpEngineId(arr);
 133     }
 134 
 135     /**
 136      * Generates an engine Id that is unique to the host the agent is running on. The engine Id unicity is system time based. The creation algorithm uses the SUN Microsystems IANA number (42).
 137      * @return The generated engine Id.
 138      */
 139     public static SnmpEngineId createEngineId() {
 140         byte[] address = null;
 141         byte[] engineid = new byte[13];
 142         int iana = 42;
 143         long mask = 0xFF;
 144         long time = System.currentTimeMillis();
 145 
 146         engineid[0] = (byte) ( (iana & 0xFF000000) >> 24 );
 147         engineid[0] |= 0x80;
 148         engineid[1] = (byte) ( (iana & 0x00FF0000) >> 16 );
 149         engineid[2] = (byte) ( (iana & 0x0000FF00) >> 8 );
 150         engineid[3] = (byte) (iana & 0x000000FF);
 151         engineid[4] = 0x05;
 152 
 153         engineid[5] =  (byte) ( (time & (mask << 56)) >>> 56 );
 154         engineid[6] =  (byte) ( (time & (mask << 48) ) >>> 48 );
 155         engineid[7] =  (byte) ( (time & (mask << 40) ) >>> 40 );
 156         engineid[8] =  (byte) ( (time & (mask << 32) ) >>> 32 );
 157         engineid[9] =  (byte) ( (time & (mask << 24) ) >>> 24 );
 158         engineid[10] = (byte) ( (time & (mask << 16) ) >>> 16 );
 159         engineid[11] = (byte) ( (time & (mask << 8) ) >>> 8 );
 160         engineid[12] = (byte) (time & mask);
 161 
 162         return new SnmpEngineId(engineid);
 163     }
 164 
 165     /**
 166      * Translates an engine Id in an SnmpOid format. This is useful when dealing with USM MIB indexes.
 167      * The oid format is : <engine Id length>.<engine Id binary octet1>....<engine Id binary octetn - 1>.<engine Id binary octetn>
 168      * Eg: "0x8000002a05819dcb6e00001f96" ==> 13.128.0.0.42.5.129.157.203.110.0.0.31.150
 169      *
 170      * @return SnmpOid The oid.
 171      */
 172     public SnmpOid toOid() {
 173         long[] oid = new long[engineId.length + 1];
 174         oid[0] = engineId.length;
 175         for(int i = 1; i <= engineId.length; i++)
 176             oid[i] = (long) (engineId[i-1] & 0xFF);
 177         return new SnmpOid(oid);
 178     }
 179 
 180    /**
 181     * <P>Generates a unique engine Id. Hexadecimal strings as well as a textual description are supported. The textual format is as follow:
 182     * <BR>  &lt;address&gt;:&lt;port&gt;:&lt;IANA number&gt;</P>
 183     * <P>The allowed formats :</P>
 184     * <ul>
 185     * <li> &lt;address&gt;:&lt;port&gt;:&lt;IANA number&gt
 186     * <BR>   All these parameters are used to generate the Id. WARNING, this method is not compliant with IPv6 address format. Use { @link com.sun.jmx.snmp.SnmpEngineId#createEngineId(java.lang.String,java.lang.String) } instead.</li>
 187     * <li> &lt;address&gt;:&lt;port&gt;
 188     * <BR>   The IANA number will be the SUN Microsystems one (42). </li>
 189     * <li> address
 190     * <BR>   The port 161 will be used to generate the Id. IANA number will be the SUN Microsystems one (42). </li>
 191     * <li> :port
 192     * <BR>   The host to use is localhost. IANA number will be the SUN Microsystems one (42). </li>
 193     * <li> ::&lt;IANA number&gt &nbsp;&nbsp;&nbsp;
 194     * <BR>   The port 161 and localhost will be used to generate the Id. </li>
 195     * <li> :&lt;port&gt;:&lt;IANA number&gt;
 196     * <BR>   The host to use is localhost. </li>
 197     * <li> &lt;address&gt;::&lt;IANA number&gt
 198     * <BR>   The port 161 will be used to generate the Id. </li>
 199     * <li> :: &nbsp;&nbsp;&nbsp;
 200     * <BR>   The port 161, localhost and the SUN Microsystems IANA number will be used to generate the Id. </li>
 201     * </ul>
 202     * @exception UnknownHostException if the host name contained in the textual format is unknown.
 203     * @exception IllegalArgumentException when :
 204     * <ul>
 205     *  <li>octet string lower than 5 bytes.</li>
 206     *  <li>octet string greater than 32 bytes.</li>
 207     *  <li>octet string = all zeros.</li>
 208     *  <li>octet string = all 'ff'H.</li>
 209     *  <li>octet strings with very first bit = 0 and length != 12 octets</li>
 210     *  <li>An IPv6 address format is used in conjonction with the ":" separator</li>
 211     * </ul>
 212     * @param str The string to parse.
 213     * @return The generated engine Id or null if the passed string is null.
 214     *
 215     */
 216     public static SnmpEngineId createEngineId(String str)
 217         throws IllegalArgumentException, UnknownHostException {
 218         return createEngineId(str, null);
 219     }
 220 
 221     /**
 222      * Idem { @link
 223      * com.sun.jmx.snmp.SnmpEngineId#createEngineId(java.lang.String) }
 224      * with the ability to provide your own separator. This allows IPv6
 225      * address format handling (eg: providing @ as separator).
 226      * @param str The string to parse.
 227      * @param separator the separator to use. If null is provided, the default
 228      * separator ":" is used.
 229      * @return The generated engine Id or null if the passed string is null.
 230      * @exception UnknownHostException if the host name contained in the
 231      * textual format is unknown.
 232      * @exception IllegalArgumentException when :
 233      * <ul>
 234      *  <li>octet string lower than 5 bytes.</li>
 235      *  <li>octet string greater than 32 bytes.</li>
 236      *  <li>octet string = all zeros.</li>
 237      *  <li>octet string = all 'ff'H.</li>
 238      *  <li>octet strings with very first bit = 0 and length != 12 octets</li>
 239      *  <li>An IPv6 address format is used in conjonction with the ":"
 240      *      separator</li>
 241      * </ul>
 242      * @since 1.5
 243      */
 244     public static SnmpEngineId createEngineId(String str, String separator)
 245         throws IllegalArgumentException, UnknownHostException {
 246         if(str == null) return null;
 247 
 248         if(str.startsWith("0x") || str.startsWith("0X")) {
 249             validateId(str);
 250             return new SnmpEngineId(str);
 251         }
 252         separator = separator == null ? ":" : separator;
 253         StringTokenizer token = new StringTokenizer(str,
 254                                                     separator,
 255                                                     true);
 256 
 257         String address = null;
 258         String port = null;
 259         String iana = null;
 260         int objPort = 161;
 261         int objIana = 42;
 262         InetAddress objAddress = null;
 263         SnmpEngineId eng = null;
 264         try {
 265             //Deal with address
 266             try {
 267                 address = token.nextToken();
 268             }catch(NoSuchElementException e) {
 269                 throw new IllegalArgumentException("Passed string is invalid : ["+str+"]");
 270             }
 271             if(!address.equals(separator)) {
 272                 objAddress = InetAddress.getByName(address);
 273                 try {
 274                     token.nextToken();
 275                 }catch(NoSuchElementException e) {
 276                     //No need to go further, no port.
 277                     eng = SnmpEngineId.createEngineId(objAddress,
 278                                                       objPort,
 279                                                       objIana);
 280                     eng.setStringValue(str);
 281                     return eng;
 282                 }
 283             }
 284             else
 285                 objAddress = InetAddress.getLocalHost();
 286 
 287             //Deal with port
 288             try {
 289                 port = token.nextToken();
 290             }catch(NoSuchElementException e) {
 291                 //No need to go further, no port.
 292                 eng = SnmpEngineId.createEngineId(objAddress,
 293                                                   objPort,
 294                                                   objIana);
 295                 eng.setStringValue(str);
 296                 return eng;
 297             }
 298 
 299             if(!port.equals(separator)) {
 300                 objPort = Integer.parseInt(port);
 301                 try {
 302                     token.nextToken();
 303                 }catch(NoSuchElementException e) {
 304                     //No need to go further, no iana.
 305                     eng = SnmpEngineId.createEngineId(objAddress,
 306                                                       objPort,
 307                                                       objIana);
 308                     eng.setStringValue(str);
 309                     return eng;
 310                 }
 311             }
 312 
 313             //Deal with iana
 314             try {
 315                 iana = token.nextToken();
 316             }catch(NoSuchElementException e) {
 317                 //No need to go further, no port.
 318                 eng = SnmpEngineId.createEngineId(objAddress,
 319                                                   objPort,
 320                                                   objIana);
 321                 eng.setStringValue(str);
 322                 return eng;
 323             }
 324 
 325             if(!iana.equals(separator))
 326                 objIana = Integer.parseInt(iana);
 327 
 328             eng = SnmpEngineId.createEngineId(objAddress,
 329                                               objPort,
 330                                               objIana);
 331             eng.setStringValue(str);
 332 
 333             return eng;
 334 
 335         } catch(Exception e) {
 336             throw new IllegalArgumentException("Passed string is invalid : ["+str+"]. Check that the used separator ["+ separator + "] is compatible with IPv6 address format.");
 337         }
 338 
 339     }
 340 
 341     /**
 342      * Generates a unique engine Id. The engine Id unicity is based on
 343      * the host IP address and port. The IP address used is the
 344      * localhost one. The creation algorithm uses the SUN Microsystems IANA
 345      * number (42).
 346      * @param port The TCP/IP port the SNMPv3 Adaptor Server is listening to.
 347      * @return The generated engine Id.
 348      * @exception UnknownHostException if the local host name
 349      *            used to calculate the id is unknown.
 350      */
 351     public static SnmpEngineId createEngineId(int port)
 352         throws UnknownHostException {
 353         int suniana = 42;
 354         InetAddress address = null;
 355         address = InetAddress.getLocalHost();
 356         return createEngineId(address, port, suniana);
 357     }
 358     /**
 359      * Generates a unique engine Id. The engine Id unicity is based on
 360      * the host IP address and port. The IP address used is the passed
 361      * one. The creation algorithm uses the SUN Microsystems IANA
 362      * number (42).
 363      * @param address The IP address the SNMPv3 Adaptor Server is listening to.
 364      * @param port The TCP/IP port the SNMPv3 Adaptor Server is listening to.
 365      * @return The generated engine Id.
 366      * @exception UnknownHostException. if the provided address is null.
 367      */
 368     public static SnmpEngineId createEngineId(InetAddress address, int port)
 369         throws IllegalArgumentException {
 370         int suniana = 42;
 371         if(address == null)
 372             throw new IllegalArgumentException("InetAddress is null.");
 373         return createEngineId(address, port, suniana);
 374     }
 375 
 376     /**
 377      * Generates a unique engine Id. The engine Id unicity is based on
 378      * the host IP address and port. The IP address is the localhost one.
 379      * The creation algorithm uses the passed IANA number.
 380      * @param port The TCP/IP port the SNMPv3 Adaptor Server is listening to.
 381      * @param iana Your enterprise IANA number.
 382      * @exception UnknownHostException if the local host name used to calculate the id is unknown.
 383      * @return The generated engine Id.
 384      */
 385     public static SnmpEngineId createEngineId(int port, int iana) throws UnknownHostException {
 386         InetAddress address = null;
 387         address = InetAddress.getLocalHost();
 388         return createEngineId(address, port, iana);
 389     }
 390 
 391     /**
 392      * Generates a unique engine Id. The engine Id unicity is based on the host IP address and port. The IP address is the passed one, it handles IPv4 and IPv6 hosts. The creation algorithm uses the passed IANA number.
 393      * @param addr The IP address the SNMPv3 Adaptor Server is listening to.
 394      * @param port The TCP/IP port the SNMPv3 Adaptor Server is listening to.
 395      * @param iana Your enterprise IANA number.
 396      * @return The generated engine Id.
 397      * @exception UnknownHostException if the provided <CODE>InetAddress </CODE> is null.
 398      */
 399     public static SnmpEngineId createEngineId(InetAddress addr,
 400                                               int port,
 401                                               int iana) {
 402         if(addr == null) throw new IllegalArgumentException("InetAddress is null.");
 403         byte[] address = addr.getAddress();
 404         byte[] engineid = new byte[9 + address.length];
 405         engineid[0] = (byte) ( (iana & 0xFF000000) >> 24 );
 406         engineid[0] |= 0x80;
 407         engineid[1] = (byte) ( (iana & 0x00FF0000) >> 16 );
 408         engineid[2] = (byte) ( (iana & 0x0000FF00) >> 8 );
 409 
 410 engineid[3] = (byte) (iana & 0x000000FF);
 411         engineid[4] = 0x05;
 412 
 413         if(address.length == 4)
 414             engineid[4] = 0x01;
 415 
 416         if(address.length == 16)
 417             engineid[4] = 0x02;
 418 
 419         for(int i = 0; i < address.length; i++) {
 420             engineid[i + 5] = address[i];
 421         }
 422 
 423         engineid[5 + address.length] = (byte)  ( (port & 0xFF000000) >> 24 );
 424         engineid[6 + address.length] = (byte) ( (port & 0x00FF0000) >> 16 );
 425         engineid[7 + address.length] = (byte) ( (port & 0x0000FF00) >> 8 );
 426         engineid[8 + address.length] = (byte) (  port & 0x000000FF );
 427 
 428         return new SnmpEngineId(engineid);
 429     }
 430 
 431      /**
 432      * Generates an engine Id based on an InetAddress. Handles IPv4 and IPv6 addresses. The creation algorithm uses the passed IANA number.
 433      * @param iana Your enterprise IANA number.
 434      * @param addr The IP address the SNMPv3 Adaptor Server is listening to.
 435      * @return The generated engine Id.
 436      * @since 1.5
 437      * @exception UnknownHostException if the provided <CODE>InetAddress </CODE> is null.
 438      */
 439     public static SnmpEngineId createEngineId(int iana, InetAddress addr)
 440     {
 441         if(addr == null) throw new IllegalArgumentException("InetAddress is null.");
 442         byte[] address = addr.getAddress();
 443         byte[] engineid = new byte[5 + address.length];
 444         engineid[0] = (byte) ( (iana & 0xFF000000) >> 24 );
 445         engineid[0] |= 0x80;
 446         engineid[1] = (byte) ( (iana & 0x00FF0000) >> 16 );
 447         engineid[2] = (byte) ( (iana & 0x0000FF00) >> 8 );
 448 
 449         engineid[3] = (byte) (iana & 0x000000FF);
 450         if(address.length == 4)
 451             engineid[4] = 0x01;
 452 
 453         if(address.length == 16)
 454             engineid[4] = 0x02;
 455 
 456         for(int i = 0; i < address.length; i++) {
 457             engineid[i + 5] = address[i];
 458         }
 459 
 460         return new SnmpEngineId(engineid);
 461     }
 462 
 463     /**
 464      * Generates an engine Id based on an InetAddress. Handles IPv4 and IPv6
 465      * addresses. The creation algorithm uses the sun IANA number (42).
 466      * @param addr The IP address the SNMPv3 Adaptor Server is listening to.
 467      * @return The generated engine Id.
 468      * @since 1.5
 469      * @exception UnknownHostException if the provided
 470      *            <CODE>InetAddress</CODE> is null.
 471      */
 472     public static SnmpEngineId createEngineId(InetAddress addr) {
 473         return createEngineId(42, addr);
 474     }
 475 
 476 
 477     /**
 478      * Tests <CODE>SnmpEngineId</CODE> instance equality. Two <CODE>SnmpEngineId</CODE> are equal if they have the same value.
 479      * @return <CODE>true</CODE> if the two <CODE>SnmpEngineId</CODE> are equals, <CODE>false</CODE> otherwise.
 480      */
 481     public boolean equals(Object a) {
 482         if(!(a instanceof SnmpEngineId) ) return false;
 483         return hexString.equals(((SnmpEngineId) a).toString());
 484     }
 485 
 486     public int hashCode() {
 487         return hexString.hashCode();
 488     }
 489 }