View Javadoc
1   // NAME
2   //      $RCSfile: AsnObjectId.java,v $
3   // DESCRIPTION
4   //      [given below in javadoc format]
5   // DELTA
6   //      $Revision: 3.25 $
7   // CREATED
8   //      $Date: 2008/08/19 13:34:13 $
9   // COPYRIGHT
10  //      Westhawk Ltd
11  // TO DO
12  //
13  
14  /*
15   * Copyright (C) 1995, 1996 by West Consulting BV
16   *
17   * Permission to use, copy, modify, and distribute this software
18   * for any purpose and without fee is hereby granted, provided
19   * that the above copyright notices appear in all copies and that
20   * both the copyright notice and this permission notice appear in
21   * supporting documentation.
22   * This software is provided "as is" without express or implied
23   * warranty.
24   * author <a href="mailto:snmp@westhawk.co.uk">Tim Panton</a>
25   * original version by hargrave@dellgate.us.dell.com (Jordan Hargrave)
26   */
27  
28  /*
29   * Copyright (C) 1996 - 2006 by Westhawk Ltd
30   * <a href="www.westhawk.co.uk">www.westhawk.co.uk</a>
31   *
32   * Permission to use, copy, modify, and distribute this software
33   * for any purpose and without fee is hereby granted, provided
34   * that the above copyright notices appear in all copies and that
35   * both the copyright notice and this permission notice appear in
36   * supporting documentation.
37   * This software is provided "as is" without express or implied
38   * warranty.
39   * author <a href="mailto:snmp@westhawk.co.uk">Tim Panton</a>
40   */
41  
42  package uk.co.westhawk.snmp.stack;
43  
44  /*-
45   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
46   * SNMP Java Client
47   * ჻჻჻჻჻჻
48   * Copyright 2023 MetricsHub, Westhawk
49   * ჻჻჻჻჻჻
50   * This program is free software: you can redistribute it and/or modify
51   * it under the terms of the GNU Lesser General Public License as
52   * published by the Free Software Foundation, either version 3 of the
53   * License, or (at your option) any later version.
54   *
55   * This program is distributed in the hope that it will be useful,
56   * but WITHOUT ANY WARRANTY; without even the implied warranty of
57   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
58   * GNU General Lesser Public License for more details.
59   *
60   * You should have received a copy of the GNU General Lesser Public
61   * License along with this program.  If not, see
62   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
63   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
64   */
65  
66  import java.io.*;
67  import java.util.*;
68  
69  // unsigned 32-bit integers encoded in the following format:
70  // MSB..LSB
71  // 76543210
72  // xyyyyyyy
73  // .\     /
74  // . \   /
75  // .  \ /
76  // .   +-------- OID bits
77  // +------------ 0 if this is the last byte, 1 if additional bytes follow
78  //
79  // Output sid in the form: abcdefgh.ijklmnop.qrstuvwx.yzABCDEF
80  //
81  // if (abcd != 0) {
82  //   output 1.000abcd 1.efghijk 1.lmnopqr 1.stuvwxy 0.zABCDEF
83  // } else if (efghijk != 0) {
84  //   output 1.efghijk 1.lmnopqr 1.stuvwxy 0.zABCDEF
85  // } else if (lmnopqr != 0) {
86  //   output 1.lmnopqr 1.stuvwxy 0.zABCDEF
87  // } else if (stuvwxy != 0) {
88  //   output 1.stuvwxy 0.zABCDEF
89  // } else {
90  //   output 0.zABCDEF
91  // }
92  
93  
94  /**
95   * This class represents the ASN.1 ObjectID class. An Object ID (OID) 
96   * identifies a variable in a MIB. 
97   *
98   * @author <a href="mailto:snmp@westhawk.co.uk">Tim Panton</a>
99   * @version $Revision: 3.25 $ $Date: 2008/08/19 13:34:13 $
100  */
101 public class AsnObjectId extends AsnObject implements Comparable {
102     private static final String version_id = "@(#)$Id: AsnObjectId.java,v 3.25 2008/08/19 13:34:13 birgita Exp $ Copyright Westhawk Ltd";
103 
104     // Thanks to Ken Swanson (gks@navcomm1.dnrc.bell-labs.com) for
105     // pointing out that this should be an array of longs.
106 
107     // default value for test purposes.
108     // private long value[] = {1,3,6,1,4,1,674,10889,2,1,0};
109     private long value[] = {};
110 
111     /**
112      * Thanks to Robert Kostes <rkostes@panix.com>.
113      * 
114      * @see #toString(long[])
115      */
116     final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
117 
118     /**
119      * The empty constructor.
120      */
121     protected AsnObjectId() {
122         type = ASN_OBJECT_ID;
123     }
124 
125     /**
126      * Constructor.
127      *
128      * @param in  The input stream from which the value should be read
129      * @param len The length of the AsnInteger
130      */
131     AsnObjectId(InputStream in, int len) throws IOException {
132         // get our data
133         byte data[] = new byte[len];
134         if (len != in.read(data, 0, len)) {
135             throw new IOException("AsnObjectId(): Not enough data");
136         }
137 
138         // now decide how many SID we will need
139         // count the bytes with 0 in the top bit - then add 1
140         int sids = 1; // first byte has 2 sids in it
141         for (int off = 0; off < len; off++) {
142             if (data[off] >= 0) {
143                 sids++;
144             }
145         }
146         // so allocate some space for the sids
147         value = new long[sids];
148 
149         // decode the first two
150         if (len > 0) {
151             value[0] = data[0] / 40;
152             if (value.length > 1) {
153                 value[1] = data[0] % 40;
154             }
155         }
156 
157         // now decode the rest
158         int off = 1;
159         for (int idx = 2; idx < value.length; idx++) {
160             long tval = 0;
161             do {
162                 tval = tval << 7;
163                 tval |= (data[off] & 0x7f);
164             } while (data[off++] < 0);
165 
166             value[idx] = tval;
167         }
168     }
169 
170     /**
171      * Constructs an AsnObjectId out of an OID string.
172      *
173      * @param s The OID, format a[.b]*
174      */
175     public AsnObjectId(String s)
176             throws IllegalArgumentException {
177         this();
178         value = toArrayOfLongs(s);
179     }
180 
181     /**
182      * Constructs an AsnObjectId out of an array of long.
183      *
184      * @param oida The OID, as an array of long
185      * @since 4_14
186      */
187     public AsnObjectId(long[] oida) {
188         this();
189         int len = oida.length;
190         value = new long[len];
191         System.arraycopy(oida, 0, value, 0, len);
192     }
193 
194     /**
195      * Converts the dotted string into an array of longs.
196      *
197      * @param s The OID, format a[.b]*
198      */
199     private long[] toArrayOfLongs(String s)
200             throws IllegalArgumentException {
201         long[] oidArray = new long[0];
202         if (s != null && s.length() > 0) {
203             StringTokenizer tok = new StringTokenizer(s, ".");
204             int count = tok.countTokens();
205             oidArray = new long[count];
206 
207             int n = 0;
208             while (tok.hasMoreTokens()) {
209                 try {
210                     String num = tok.nextToken();
211                     Long val = Long.valueOf(num);
212                     oidArray[n] = val.longValue();
213                     n++;
214                 } catch (NumberFormatException exc) {
215                     throw new IllegalArgumentException("AsnObjectId(): Bad OID '"
216                             + s + "' " + exc.getMessage());
217                 } catch (NoSuchElementException exc) {
218                 }
219             }
220         } else {
221             throw new IllegalArgumentException("AsnObjectId(): Bad OID '"
222                     + s + "' ");
223         }
224 
225         return oidArray;
226     }
227 
228     /**
229      * Checks if this OID starts with the specified prefix.
230      * 
231      * @return true if starts with the prefix, false otherwise
232      */
233     public boolean startsWith(AsnObjectId prefix) {
234         boolean sw = true;
235         if (prefix.value.length < this.value.length) {
236             int pos = 0;
237             while (pos < prefix.value.length && sw == true) {
238                 sw = (prefix.value[pos] == this.value[pos]);
239                 pos++;
240             }
241         } else {
242             sw = false;
243         }
244         return sw;
245     }
246 
247     /**
248      * Adds a single sub-identifier to the end of the OID.
249      *
250      * @param sub_oid The sub-identifier
251      */
252     public void add(long sub_oid) {
253         int size = value.length;
254 
255         long tmp_value[] = value;
256         value = new long[size + 1];
257         System.arraycopy(tmp_value, 0, value, 0, size);
258         value[size] = sub_oid;
259     }
260 
261     /**
262      * Adds a number of sub-identifiers to the end of the OID.
263      *
264      * @param sub_oid The sub-identifiers
265      * @see AsnOctets#toSubOid
266      */
267     public void add(long[] sub_oid) {
268         int size1 = value.length;
269         int size2 = sub_oid.length;
270 
271         long tmp_value[] = value;
272         value = new long[size1 + size2];
273         System.arraycopy(tmp_value, 0, value, 0, size1);
274         System.arraycopy(sub_oid, 0, value, size1, size2);
275     }
276 
277     /**
278      * Adds sub-identifiers to the end of the OID.
279      *
280      * @param s The sub-identifiers, format a[.b]*
281      */
282     public void add(String s) throws IllegalArgumentException {
283         long[] sub_oid = toArrayOfLongs(s);
284         add(sub_oid);
285     }
286 
287     /**
288      * Removes the last sub-identifier (if available) from this
289      * OID and returns it.
290      * 
291      * @return The sub-identifier or -1 if there is no sub-identifier
292      * @since 6.1
293      */
294     public long removeLast() {
295         long lastSubOid = -1;
296         int size = value.length;
297         if (size > 0) {
298             // get the last sub-oid
299             size -= 1;
300             lastSubOid = value[size];
301 
302             // remove the last sub-oid
303             long tmp_value[] = value;
304             value = new long[size];
305             System.arraycopy(tmp_value, 0, value, 0, size);
306         }
307         return lastSubOid;
308     }
309 
310     /**
311      * Returns the total size of the object ID.
312      */
313     int size() throws EncodingException {
314         int val, idx, len;
315 
316         if (value.length > 1) {
317             // First entry = OID[0]*40 + OID[1];
318             len = getSIDLen(value[0] * 40 + value[1]);
319             for (idx = 2; idx < value.length; idx++) {
320                 len += getSIDLen(value[idx]);
321             }
322         } else if (value.length == 1) {
323             len = getSIDLen(value[0] * 40);
324         } else if (value.length == 0) {
325             len = getSIDLen(0);
326         } else {
327             throw new EncodingException("Negative numbers cannot be encoded as OID sub-identifiers");
328         }
329         return len;
330     }
331 
332     /**
333      * Output data.
334      */
335     void write(OutputStream out, int pos)
336             throws IOException, EncodingException {
337         int idx;
338 
339         // Output header
340         AsnBuildHeader(out, ASN_OBJECT_ID, size());
341         if (debug > 10) {
342             System.out.println("\tAsnObjectId(): value = " + this.toString()
343                     + ", pos = " + pos);
344         }
345 
346         // Output data bytes
347         if (value.length > 1) {
348             // First entry = OID[0]*40 + OID[1];
349             EncodeSID(out, value[0] * 40 + value[1]);
350             for (idx = 2; idx < value.length; idx++) {
351                 EncodeSID(out, value[idx]);
352             }
353         } else if (value.length == 1) {
354             EncodeSID(out, value[0] * 40);
355         } else {
356             EncodeSID(out, 0);
357         }
358     }
359 
360     /**
361      * Returns the length of the OID subidentifier.
362      */
363     private int getSIDLen(long value) {
364         int count;
365 
366         for (count = 1; (value >>= 7) != 0; count++)
367             ;
368         return count;
369     }
370 
371     /**
372      * Encode OID subidentifier.
373      */
374     private void EncodeSID(OutputStream out, long value) throws IOException {
375         byte mask = (byte) 0x0F;
376         int count = 0;
377 
378         // Upper mask is 4 bits
379         mask = 0xF;
380 
381         // Loop while value and mask is zero
382         for (count = 28; count > 0; count -= 7) {
383             if (((value >> count) & mask) != 0)
384                 break;
385             mask = 0x7f;
386         }
387 
388         // While count, output value. If this isn't the last byte, output
389         // 0x80 | value.
390         for (; count >= 0; count -= 7) {
391             out.write((byte) (((value >> count) & mask) | (count > 0 ? 0x80 : 0x00)));
392             mask = 0x7f;
393         }
394     }
395 
396     /**
397      * Returns the value of the AsnObjectId.
398      *
399      * @return The value (the OID)
400      */
401     public String getValue() {
402         return toString();
403     }
404 
405     /**
406      * Returns the string representation of the AsnObjectId.
407      *
408      * @return The string of the AsnObjectId
409      */
410     public String toString() {
411         return toString(value);
412     }
413 
414     /**
415      * Returns the string representation of the AsnObjectId.
416      *
417      * @return The string of the AsnObjectId
418      */
419     public String toString(long v[]) {
420         StringBuffer buffer = new StringBuffer("");
421         if (v.length > 0) {
422             for (int n = 0; n < v.length - 1 && n < 100; n++) {
423                 // optimization, thanks to Robert Kostes <rkostes@panix.com> who
424                 // did some profiling.
425                 if (0 <= v[n] && v[n] <= 9) {
426                     // append a fixed char
427                     int i = (int) v[n];
428                     buffer.append(digits[i]);
429                 } else {
430                     // go through number conversion
431                     buffer.append(v[n]);
432                 }
433                 buffer.append(".");
434             }
435             if (v.length - 1 > 100) {
436                 buffer.append("[.. cut ..].");
437             }
438             buffer.append(v[v.length - 1]);
439         }
440         return buffer.toString();
441     }
442 
443     /**
444      * Returns the number of elements in the AsnObjectId.
445      * We cannot use size(), since that is already in use.
446      *
447      * @return the number of elements
448      */
449     public int getSize() {
450         return value.length;
451     }
452 
453     /**
454      * Returns the element in the AsnObjectId at the specified index.
455      *
456      * @param index The index
457      * @return the element at the specified index
458      * @exception ArrayIndexOutOfBoundsException if an invalid
459      *                                           index was given
460      */
461     public synchronized long getElementAt(int index)
462             throws ArrayIndexOutOfBoundsException {
463         if (index >= value.length) {
464             throw new ArrayIndexOutOfBoundsException(index
465                     + " >= " + value.length);
466         }
467         try {
468             return value[index];
469         } catch (ArrayIndexOutOfBoundsException exc) {
470             throw new ArrayIndexOutOfBoundsException(index + " < 0");
471         }
472     }
473 
474     /**
475      * Returns the last element in the AsnObjectId.
476      *
477      * @return the last element
478      * @exception ArrayIndexOutOfBoundsException if the
479      *                                           AsnObjectId is empty.
480      * @since 6.1
481      */
482     public long lastElement()
483             throws ArrayIndexOutOfBoundsException {
484         return getElementAt(value.length - 1);
485     }
486 
487     /**
488      * Returns the value of the AsnObjectId as an array of long. The value
489      * returned is a copy.
490      *
491      * @return The OID value
492      * @since 4_14
493      */
494     public long[] getOid() {
495         int len = value.length;
496         long[] oida = new long[len];
497         System.arraycopy(value, 0, oida, 0, len);
498         return oida;
499     }
500 
501     /**
502      * Returns a subset of the value of the AsnObjectId as an array of long.
503      * The value returned is a copy.
504      *
505      * @return The sub OID value
506      * @since 4_14
507      */
508     public long[] getSubOid(int beginIndex, int endIndex)
509             throws ArrayIndexOutOfBoundsException {
510         int len1 = value.length;
511         if (beginIndex < 0) {
512             throw new ArrayIndexOutOfBoundsException(beginIndex + " < 0");
513         }
514         if (endIndex > len1) {
515             throw new ArrayIndexOutOfBoundsException(endIndex + " > " + len1);
516         }
517         if (beginIndex > endIndex) {
518             throw new ArrayIndexOutOfBoundsException(beginIndex + " > " + endIndex);
519         }
520 
521         int len2 = endIndex - beginIndex;
522         long[] oida = new long[len2];
523         System.arraycopy(value, beginIndex, oida, 0, len2);
524         return oida;
525     }
526 
527     /**
528      * Compares this OID to the specified object.
529      * The result is <code>true</code> if and only if the argument is not
530      * <code>null</code> and is a <code>AsnObjectId</code> object that represents
531      * the same sequence of numbers as this object.
532      *
533      *
534      * <p>
535      * Thanks to Eli Bishop (eli@graphesthesia.com) for the suggestion of
536      * adding it.
537      * </p>
538      *
539      * @param anObject The object to compare this <code>AsnObjectId</code>
540      *                 against.
541      * @return <code>true</code> if the <code>AsnObjectId </code>are equal;
542      *         <code>false</code> otherwise.
543      */
544     public boolean equals(Object anObject) {
545         if (this == anObject) {
546             return true;
547         }
548         if (anObject instanceof AsnObjectId) {
549             AsnObjectId anotherOid = (AsnObjectId) anObject;
550             int n = value.length;
551             if (n == anotherOid.value.length) {
552                 long v1[] = value;
553                 long v2[] = anotherOid.value;
554                 int i = 0;
555                 int j = 0;
556                 while (n-- != 0) {
557                     if (v1[i++] != v2[j++]) {
558                         return false;
559                     }
560                 }
561                 return true;
562             }
563         }
564         return false;
565     }
566 
567     /**
568      * Returns a hash code for this OID.
569      * The hash value of the empty OID is zero.
570      *
571      * @return a hash code value for this object.
572      */
573     public int hashCode() {
574         int h = 0;
575         if (h == 0) {
576             int off = 0;
577             long val[] = value;
578             int len = value.length;
579 
580             for (int i = 0; i < len; i++) {
581                 long l = val[off++];
582                 // nicked from Long.hashCode()
583                 int hi = (int) (l ^ (l >>> 32));
584                 h = 31 * h + hi;
585             }
586         }
587         return h;
588     }
589 
590     /**
591      * @see Comparable#compareTo
592      * @see #compareTo(AsnObjectId)
593      * @since 6.1
594      */
595     public int compareTo(Object o) {
596         return compareTo((AsnObjectId) o);
597     }
598 
599     /**
600      * @see Comparable#compareTo
601      *      Thanks to Josh Bers to providing this code.
602      * @since 6.1
603      */
604     public int compareTo(AsnObjectId b) {
605         if (b == null) {
606             throw new NullPointerException("Trying to compare with null");
607         }
608 
609         int aElts = getSize();
610         int bElts = b.getSize();
611 
612         if ((aElts == 0) && (bElts > 0)) {
613             return -1;
614         }
615         if ((bElts == 0) && (aElts > 0)) {
616             return 1;
617         }
618 
619         for (int i = 0; (i < aElts) && (i < bElts); i++) {
620             if (getElementAt(i) != b.getElementAt(i)) {
621                 if (getElementAt(i) > b.getElementAt(i)) {
622                     return 1;
623                 } else {
624                     return -1;
625                 }
626             }
627         }
628 
629         // equal to the end of one object
630         if (aElts > bElts) {
631             return 1;
632         } else if (bElts > aElts) {
633             return -1;
634         }
635 
636         // both objects same
637         return 0;
638     }
639 
640     /**
641      * Compares the n leftmost sub-identifiers with the given
642      * AsnObject in left-to-right direction.
643      * 
644      * @param n The number of sub-identifiers to compare.
645      * @param b An AsnObject to compare with this object
646      * @return
647      *         <ul>
648      *         <li>
649      *         0, when the first 'n' sub-identifiers are the same.
650      *         </li>
651      *         <li>
652      *         -1 when the first 'n' sub-identifiers of this object are
653      *         lexicographic less than those of b. This includes the case where
654      *         this object is shorter than 'n' AND shorter than b.
655      *         </li>
656      *         <li>
657      *         1 when the first 'n' sub-identifiers of this object are
658      *         lexicographic greater than those of b.
659      *         </li>
660      *         </ul>
661      * @since 6.1
662      */
663     public int leftMostCompare(int n, AsnObjectId b) {
664         if (b == null) {
665             throw new NullPointerException("Trying to compare with null");
666         }
667 
668         int aElts = getSize();
669         int bElts = b.getSize();
670 
671         int min = Math.min(aElts, bElts);
672         if (min < n) {
673             if (aElts > bElts) {
674                 return 1;
675             } else if (bElts > aElts) {
676                 return -1;
677             } else {
678                 return this.compareTo(b);
679             }
680         } else {
681             // min >= n
682             long[] aOids = getSubOid(0, n);
683             long[] bOids = b.getSubOid(0, n);
684             AsnObjectId aCopy = new AsnObjectId(aOids);
685             AsnObjectId bCopy = new AsnObjectId(bOids);
686             return aCopy.compareTo(bCopy);
687         }
688     }
689 
690 }