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 }