001/*
002 * Copyright 2017-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2017-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.asn1;
037
038
039
040import java.util.ArrayList;
041import java.util.Iterator;
042import java.util.List;
043
044import com.unboundid.util.ByteStringBuffer;
045import com.unboundid.util.Debug;
046import com.unboundid.util.NotMutable;
047import com.unboundid.util.NotNull;
048import com.unboundid.util.OID;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051
052import static com.unboundid.asn1.ASN1Messages.*;
053
054
055
056/**
057 * This class provides an ASN.1 object identifier element, whose value
058 * represents a numeric OID.  Note that ASN.1 object identifier elements must
059 * strictly conform to the numeric OID specification, which has the following
060 * requirements:
061 * <UL>
062 *   <LI>All valid OIDs must contain at least two components.</LI>
063 *   <LI>The value of the first component must be 0, 1, or 2.</LI>
064 *   <LI>If the value of the first component is 0 or 1, then the value of the
065 *       second component must not be greater than 39.</LI>
066 * </UL>
067 */
068@NotMutable()
069@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
070public final class ASN1ObjectIdentifier
071       extends ASN1Element
072{
073  /**
074   * The serial version UID for this serializable class.
075   */
076  private static final long serialVersionUID = -777778295086222273L;
077
078
079
080  // The OID represented by this object identifier element.
081  @NotNull private final OID oid;
082
083
084
085  /**
086   * Creates a new ASN.1 object identifier element with the default BER type and
087   * the provided OID.
088   *
089   * @param  oid  The OID to represent with this element.  It must not be
090   *              {@code null}, and it must represent a valid OID.
091   *
092   * @throws  ASN1Exception  If the provided OID does not strictly adhere to the
093   *                         numeric OID format.
094   */
095  public ASN1ObjectIdentifier(@NotNull final OID oid)
096         throws ASN1Exception
097  {
098    this(ASN1Constants.UNIVERSAL_OBJECT_IDENTIFIER_TYPE, oid);
099  }
100
101
102
103  /**
104   * Creates a new ASN.1 object identifier element with the specified BER type
105   * and the provided OID.
106   *
107   * @param  type  The BER type for this element.
108   * @param  oid   The OID to represent with this element.  It must not be
109   *               {@code null}, and it must represent a valid OID.
110   *
111   * @throws  ASN1Exception  If the provided OID does not strictly adhere to the
112   *                         numeric OID format.
113   */
114  public ASN1ObjectIdentifier(final byte type, @NotNull final OID oid)
115         throws ASN1Exception
116  {
117    this(type, oid, encodeValue(oid));
118  }
119
120
121
122  /**
123   * Creates a new ASN.1 object identifier element with the default BER type and
124   * the provided OID.
125   *
126   * @param  oidString  The string representation of the OID to represent with
127   *                    this element.  It must not be {@code null}, and it must
128   *                    represent a valid OID.
129   *
130   * @throws  ASN1Exception  If the provided OID does not strictly adhere to the
131   *                         numeric OID format.
132   */
133  public ASN1ObjectIdentifier(@NotNull final String oidString)
134         throws ASN1Exception
135  {
136    this(ASN1Constants.UNIVERSAL_OBJECT_IDENTIFIER_TYPE, oidString);
137  }
138
139
140
141  /**
142   * Creates a new ASN.1 object identifier element with the specified BER type
143   * and the provided OID.
144   *
145   * @param  type       The BER type for this element.
146   * @param  oidString  The string representation of the OID to represent with
147   *                    this element.  It must not be {@code null}, and it must
148   *                    represent a valid OID.
149   *
150   * @throws  ASN1Exception  If the provided OID does not strictly adhere to the
151   *                         numeric OID format.
152   */
153  public ASN1ObjectIdentifier(final byte type,
154                              @NotNull final String oidString)
155         throws ASN1Exception
156  {
157    this(type, new OID(oidString));
158  }
159
160
161
162  /**
163   * Creates a new ASN.1 object identifier element with the provided
164   * information.
165   *
166   * @param  type          The BER type to use for this element.
167   * @param  oid           The OID to represent with this element.
168   * @param  encodedValue  The encoded value for this element.
169   */
170  private ASN1ObjectIdentifier(final byte type, @NotNull final OID oid,
171                               @NotNull final byte[] encodedValue)
172  {
173    super(type, encodedValue);
174
175    this.oid = oid;
176  }
177
178
179
180  /**
181   * Generates an encoded value for an object identifier element with the
182   * provided OID.
183   *
184   * @param  oid  The OID to represent with this element.  It must not be
185   *              {@code null}, and it must represent a valid OID.
186   *
187   * @return  The encoded value.
188   *
189   * @throws  ASN1Exception  If the provided OID does not strictly conform to
190   *                         the requirements for ASN.1 OIDs.
191   */
192  @NotNull()
193  private static byte[] encodeValue(@NotNull final OID oid)
194          throws ASN1Exception
195  {
196    // Make sure that the provided UID conforms to the necessary constraints.
197    if (! oid.isValidNumericOID())
198    {
199      throw new ASN1Exception(ERR_OID_ENCODE_NOT_NUMERIC.get());
200    }
201
202    final List<Integer> components = oid.getComponents();
203    if (components.size() < 2)
204    {
205      throw new ASN1Exception(ERR_OID_ENCODE_NOT_ENOUGH_COMPONENTS.get(
206           oid.toString()));
207    }
208
209    final Iterator<Integer> componentIterator = components.iterator();
210
211    final int firstComponent = componentIterator.next();
212    if ((firstComponent < 0) || (firstComponent > 2))
213    {
214      throw new ASN1Exception(ERR_OID_ENCODE_INVALID_FIRST_COMPONENT.get(
215           oid.toString(), firstComponent));
216    }
217
218    final int secondComponent = componentIterator.next();
219    if ((secondComponent < 0) ||
220        ((firstComponent != 2) && (secondComponent > 39)))
221    {
222      throw new ASN1Exception(ERR_OID_ENCODE_INVALID_SECOND_COMPONENT.get(
223           oid.toString(), firstComponent, secondComponent));
224    }
225
226
227    // Construct the encoded representation of the OID.  Compute it as follows:
228    // - The first and second components are merged together by multiplying the
229    //   value of the first component by 40 and adding the value of the second
230    //   component.  Every other component is handled individually.
231    // - For components (including the merged first and second components) whose
232    //   value is less than or equal to 127, the encoded representation of that
233    //   component is simply the single-byte encoded representation of that
234    //   number.
235    // - For components (including the merged first and second components) whose
236    //   value is greater than 127, that component must be encoded in multiple
237    //   bytes.  In the encoded representation, only the lower seven bits of
238    //   each byte will be used to convey the value.  The most significant bit
239    //   of each byte will be used to indicate whether there are more bytes in
240    //   the component.
241    final ByteStringBuffer buffer = new ByteStringBuffer();
242    final int mergedFirstComponents = (40 * firstComponent) + secondComponent;
243    encodeComponent(mergedFirstComponents, buffer);
244    while (componentIterator.hasNext())
245    {
246      encodeComponent(componentIterator.next(), buffer);
247    }
248
249    return buffer.toByteArray();
250  }
251
252
253
254  /**
255   * Appends an encoded representation of the provided component value to the
256   * given buffer.
257   *
258   * @param  c  The value of the component to encode.
259   * @param  b  The buffer to which the encoded representation should be
260   *            appended.
261   */
262  private static void encodeComponent(final int c,
263                                      @NotNull final ByteStringBuffer b)
264  {
265    final int finalByte = c & 0b1111111;
266    if (finalByte == c)
267    {
268      b.append((byte) finalByte);
269    }
270    else if ((c & 0b1111111_1111111) == c)
271    {
272      b.append((byte) (0b10000000 | ((c >> 7) & 0b1111111)));
273      b.append((byte) finalByte);
274    }
275    else if ((c & 0b1111111_1111111_1111111) == c)
276    {
277      b.append((byte) (0b10000000 | ((c >> 14) & 0b1111111)));
278      b.append((byte) (0b10000000 | ((c >> 7) & 0b1111111)));
279      b.append((byte) finalByte);
280    }
281    else if ((c & 0b1111111_1111111_1111111_1111111) == c)
282    {
283      b.append((byte) (0b10000000 | ((c >> 21) & 0b1111111)));
284      b.append((byte) (0b10000000 | ((c >> 14) & 0b1111111)));
285      b.append((byte) (0b10000000 | ((c >> 7) & 0b1111111)));
286      b.append((byte) finalByte);
287    }
288    else
289    {
290      b.append((byte) (0b10000000 | ((c >> 28) & 0b1111111)));
291      b.append((byte) (0b10000000 | ((c >> 21) & 0b1111111)));
292      b.append((byte) (0b10000000 | ((c >> 14) & 0b1111111)));
293      b.append((byte) (0b10000000 | ((c >> 7) & 0b1111111)));
294      b.append((byte) finalByte);
295    }
296  }
297
298
299
300  /**
301   * Retrieves the OID represented by this object identifier element.
302   *
303   * @return  The OID represented by this object identifier element.
304   */
305  @NotNull()
306  public OID getOID()
307  {
308    return oid;
309  }
310
311
312
313  /**
314   * Decodes the contents of the provided byte array as an object identifier
315   * element.
316   *
317   * @param  elementBytes  The byte array to decode as an ASN.1 object
318   *                       identifier element.
319   *
320   * @return  The decoded ASN.1 object identifier element.
321   *
322   * @throws  ASN1Exception  If the provided array cannot be decoded as an
323   *                         object identifier element.
324   */
325  @NotNull()
326  public static ASN1ObjectIdentifier decodeAsObjectIdentifier(
327                                          @NotNull final byte[] elementBytes)
328         throws ASN1Exception
329  {
330    try
331    {
332      int valueStartPos = 2;
333      int length = (elementBytes[1] & 0x7F);
334      if (length != elementBytes[1])
335      {
336        final int numLengthBytes = length;
337
338        length = 0;
339        for (int i=0; i < numLengthBytes; i++)
340        {
341          length <<= 8;
342          length |= (elementBytes[valueStartPos++] & 0xFF);
343        }
344      }
345
346      if ((elementBytes.length - valueStartPos) != length)
347      {
348        throw new ASN1Exception(ERR_ELEMENT_LENGTH_MISMATCH.get(length,
349                                     (elementBytes.length - valueStartPos)));
350      }
351
352      final byte[] elementValue = new byte[length];
353      System.arraycopy(elementBytes, valueStartPos, elementValue, 0, length);
354      final OID oid = decodeValue(elementValue);
355      return new ASN1ObjectIdentifier(elementBytes[0], oid, elementValue);
356    }
357    catch (final ASN1Exception ae)
358    {
359      Debug.debugException(ae);
360      throw ae;
361    }
362    catch (final Exception e)
363    {
364      Debug.debugException(e);
365      throw new ASN1Exception(ERR_ELEMENT_DECODE_EXCEPTION.get(e), e);
366    }
367  }
368
369
370
371  /**
372   * Decodes the provided ASN.1 element as an object identifier element.
373   *
374   * @param  element  The ASN.1 element to be decoded.
375   *
376   * @return  The decoded ASN.1 object identifier element.
377   *
378   * @throws  ASN1Exception  If the provided element cannot be decoded as an
379   *                         object identifier element.
380   */
381  @NotNull()
382  public static ASN1ObjectIdentifier decodeAsObjectIdentifier(
383                                          @NotNull final ASN1Element element)
384         throws ASN1Exception
385  {
386    final OID oid = decodeValue(element.getValue());
387    return new ASN1ObjectIdentifier(element.getType(), oid, element.getValue());
388  }
389
390
391
392  /**
393   * Decodes the provided value as an OID.
394   *
395   * @param  elementValue  The bytes that comprise the encoded value for an
396   *                       object identifier element.
397   *
398   * @return  The decoded OID.
399   *
400   * @throws  ASN1Exception  If the provided value cannot be decoded as a valid
401   *                         OID.
402   */
403  @NotNull()
404  private static OID decodeValue(@NotNull final byte[] elementValue)
405          throws ASN1Exception
406  {
407    if (elementValue.length == 0)
408    {
409      throw new ASN1Exception(ERR_OID_DECODE_EMPTY_VALUE.get());
410    }
411
412    final byte lastByte = elementValue[elementValue.length - 1];
413    if ((lastByte & 0x80) == 0x80)
414    {
415      throw new ASN1Exception(ERR_OID_DECODE_INCOMPLETE_VALUE.get());
416    }
417
418    int currentComponent = 0x00;
419    final ArrayList<Integer> components = new ArrayList<>(elementValue.length);
420    for (final byte b : elementValue)
421    {
422      currentComponent <<= 7;
423      currentComponent |= (b & 0x7F);
424      if ((b & 0x80) == 0x00)
425      {
426        if (components.isEmpty())
427        {
428          if (currentComponent < 40)
429          {
430            components.add(0);
431            components.add(currentComponent);
432          }
433          else if (currentComponent < 80)
434          {
435            components.add(1);
436            components.add(currentComponent - 40);
437          }
438          else
439          {
440            components.add(2);
441            components.add(currentComponent - 80);
442          }
443        }
444        else
445        {
446          components.add(currentComponent);
447        }
448
449        currentComponent = 0x00;
450      }
451    }
452
453    return new OID(components);
454  }
455
456
457
458  /**
459   * {@inheritDoc}
460   */
461  @Override()
462  public void toString(@NotNull final StringBuilder buffer)
463  {
464    buffer.append(oid.toString());
465  }
466}