001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-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) 2007-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.Collection;
042
043import com.unboundid.util.ByteStringBuffer;
044import com.unboundid.util.Debug;
045import com.unboundid.util.NotMutable;
046import com.unboundid.util.NotNull;
047import com.unboundid.util.Nullable;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050
051import static com.unboundid.asn1.ASN1Messages.*;
052
053
054
055/**
056 * This class provides an ASN.1 sequence element, which is used to hold an
057 * ordered set of zero or more other elements (potentially including additional
058 * "envelope" element types like other sequences and/or sets).
059 */
060@NotMutable()
061@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
062public final class ASN1Sequence
063       extends ASN1Element
064{
065  /**
066   * The serial version UID for this serializable class.
067   */
068  private static final long serialVersionUID = 7294248008273774906L;
069
070
071
072  /*
073   * NOTE:  This class uses lazy initialization for the encoded value.  The
074   * encoded value should only be needed by the getValue() method, which is used
075   * by ASN1Element.encode().  Even though this class is externally immutable,
076   * that does not by itself make it completely threadsafe, because weirdness in
077   * the Java memory model could allow the assignment to be performed out of
078   * order.  By passing the value through a volatile variable any time the value
079   * is set other than in the constructor (which will always be safe) we ensure
080   * that this reordering cannot happen.
081   *
082   * In the majority of cases, passing the value through assignments to
083   * valueBytes through a volatile variable is much faster than declaring
084   * valueBytes itself to be volatile because a volatile variable cannot be held
085   * in CPU caches or registers and must only be accessed from memory visible to
086   * all threads.  Since the value may be read much more often than it is
087   * written, passing it through a volatile variable rather than making it
088   * volatile directly can help avoid that penalty when possible.
089   */
090
091
092
093  // The set of ASN.1 elements contained in this sequence.
094  @NotNull private final ASN1Element[] elements;
095
096  // The encoded representation of the value, if available.
097  @Nullable private byte[] encodedValue;
098
099  // A volatile variable used to guard publishing the encodedValue array.  See
100  // the note above to explain why this is needed.
101  @Nullable
102  private volatile byte[] encodedValueGuard;
103
104
105
106  /**
107   * Creates a new ASN.1 sequence with the default BER type and no encapsulated
108   * elements.
109   */
110  public ASN1Sequence()
111  {
112    super(ASN1Constants.UNIVERSAL_SEQUENCE_TYPE);
113
114    elements     = ASN1Constants.NO_ELEMENTS;
115    encodedValue = ASN1Constants.NO_VALUE;
116  }
117
118
119
120  /**
121   * Creates a new ASN.1 sequence with the specified BER type and no
122   * encapsulated elements.
123   *
124   * @param  type  The BER type to use for this element.
125   */
126  public ASN1Sequence(final byte type)
127  {
128    super(type);
129
130    elements     = ASN1Constants.NO_ELEMENTS;
131    encodedValue = ASN1Constants.NO_VALUE;
132  }
133
134
135
136  /**
137   * Creates a new ASN.1 sequence with the default BER type and the provided set
138   * of elements.
139   *
140   * @param  elements  The set of elements to include in this sequence.
141   */
142  public ASN1Sequence(@Nullable final ASN1Element... elements)
143  {
144    super(ASN1Constants.UNIVERSAL_SEQUENCE_TYPE);
145
146    if (elements == null)
147    {
148      this.elements = ASN1Constants.NO_ELEMENTS;
149    }
150    else
151    {
152      this.elements = elements;
153    }
154
155    encodedValue = null;
156  }
157
158
159
160  /**
161   * Creates a new ASN.1 sequence with the default BER type and the provided set
162   * of elements.
163   *
164   * @param  elements  The set of elements to include in this sequence.
165   */
166  public ASN1Sequence(
167              @Nullable final Collection<? extends ASN1Element> elements)
168  {
169    super(ASN1Constants.UNIVERSAL_SEQUENCE_TYPE);
170
171    if ((elements == null) || elements.isEmpty())
172    {
173      this.elements = ASN1Constants.NO_ELEMENTS;
174    }
175    else
176    {
177      this.elements = new ASN1Element[elements.size()];
178      elements.toArray(this.elements);
179    }
180
181    encodedValue = null;
182  }
183
184
185
186  /**
187   * Creates a new ASN.1 sequence with the specified BER type and the provided
188   * set of elements.
189   *
190   * @param  type      The BER type to use for this element.
191   * @param  elements  The set of elements to include in this sequence.
192   */
193  public ASN1Sequence(final byte type,
194                      @Nullable final ASN1Element... elements)
195  {
196    super(type);
197
198    if (elements == null)
199    {
200      this.elements = ASN1Constants.NO_ELEMENTS;
201    }
202    else
203    {
204      this.elements = elements;
205    }
206
207    encodedValue = null;
208  }
209
210
211
212  /**
213   * Creates a new ASN.1 sequence with the specified BER type and the provided
214   * set of elements.
215   *
216   * @param  type      The BER type to use for this element.
217   * @param  elements  The set of elements to include in this sequence.
218   */
219  public ASN1Sequence(final byte type,
220              @Nullable final Collection<? extends ASN1Element> elements)
221  {
222    super(type);
223
224    if ((elements == null) || elements.isEmpty())
225    {
226      this.elements = ASN1Constants.NO_ELEMENTS;
227    }
228    else
229    {
230      this.elements = new ASN1Element[elements.size()];
231      elements.toArray(this.elements);
232    }
233
234    encodedValue = null;
235  }
236
237
238
239  /**
240   * Creates a new ASN.1 sequence with the specified type, set of elements, and
241   * encoded value.
242   *
243   * @param  type      The BER type to use for this element.
244   * @param  elements  The set of elements to include in this sequence.
245   * @param  value     The pre-encoded value for this element.
246   */
247  private ASN1Sequence(final byte type,
248                       @NotNull final ASN1Element[] elements,
249                       @NotNull final byte[] value)
250  {
251    super(type);
252
253    this.elements = elements;
254    encodedValue  = value;
255  }
256
257
258
259  /**
260   * {@inheritDoc}
261   */
262  @Override()
263  @NotNull()
264  byte[] getValueArray()
265  {
266    return getValue();
267  }
268
269
270
271  /**
272   * {@inheritDoc}
273   */
274  @Override()
275  int getValueOffset()
276  {
277    return 0;
278  }
279
280
281
282  /**
283   * {@inheritDoc}
284   */
285  @Override()
286  public int getValueLength()
287  {
288    return getValue().length;
289  }
290
291
292
293  /**
294   * {@inheritDoc}
295   */
296  @Override()
297  @NotNull()
298  public byte[] getValue()
299  {
300    if (encodedValue == null)
301    {
302      encodedValueGuard = encodeElements(elements);
303      encodedValue = encodedValueGuard;
304    }
305
306    return encodedValue;
307  }
308
309
310
311  /**
312   * {@inheritDoc}
313   */
314  @Override()
315  public void encodeTo(@NotNull final ByteStringBuffer buffer)
316  {
317    buffer.append(getType());
318
319    if (elements.length == 0)
320    {
321      buffer.append((byte) 0x00);
322      return;
323    }
324
325    // In this case, it will likely be faster to just go ahead and append
326    // encoded representations of all of the elements and insert the length
327    // later once we know it.
328    final int originalLength = buffer.length();
329    for (final ASN1Element e : elements)
330    {
331      e.encodeTo(buffer);
332    }
333
334    buffer.insert(originalLength,
335                  encodeLength(buffer.length() - originalLength));
336  }
337
338
339
340  /**
341   * Encodes the provided set of elements to a byte array suitable for use as
342   * the element value.
343   *
344   * @param  elements  The set of elements to be encoded.
345   *
346   * @return  A byte array containing the encoded elements.
347   */
348  @NotNull()
349  static byte[] encodeElements(@Nullable final ASN1Element[] elements)
350  {
351    if ((elements == null) || (elements.length == 0))
352    {
353      return ASN1Constants.NO_VALUE;
354    }
355
356    int totalLength = 0;
357    final int numElements = elements.length;
358    final byte[][] encodedElements = new byte[numElements][];
359    for (int i=0; i < numElements; i++)
360    {
361      encodedElements[i] = elements[i].encode();
362      totalLength += encodedElements[i].length;
363    }
364
365    int pos = 0;
366    final byte[] b = new byte[totalLength];
367    for (int i=0; i < numElements; i++)
368    {
369      System.arraycopy(encodedElements[i], 0, b, pos,
370                       encodedElements[i].length);
371      pos += encodedElements[i].length;
372    }
373
374    return b;
375  }
376
377
378
379  /**
380   * Retrieves the set of encapsulated elements held in this sequence.
381   *
382   * @return  The set of encapsulated elements held in this sequence.
383   */
384  @NotNull()
385  public ASN1Element[] elements()
386  {
387    return elements;
388  }
389
390
391
392  /**
393   * Decodes the contents of the provided byte array as a sequence element.
394   *
395   * @param  elementBytes  The byte array to decode as an ASN.1 sequence
396   *                       element.
397   *
398   * @return  The decoded ASN.1 sequence element.
399   *
400   * @throws  ASN1Exception  If the provided array cannot be decoded as a
401   *                         sequence element.
402   */
403  @NotNull()
404  public static ASN1Sequence decodeAsSequence(
405                                  @NotNull final byte[] elementBytes)
406         throws ASN1Exception
407  {
408    try
409    {
410      int valueStartPos = 2;
411      int length = (elementBytes[1] & 0x7F);
412      if (length != elementBytes[1])
413      {
414        final int numLengthBytes = length;
415
416        length = 0;
417        for (int i=0; i < numLengthBytes; i++)
418        {
419          length <<= 8;
420          length |= (elementBytes[valueStartPos++] & 0xFF);
421        }
422      }
423
424      if ((elementBytes.length - valueStartPos) != length)
425      {
426        throw new ASN1Exception(ERR_ELEMENT_LENGTH_MISMATCH.get(length,
427                                     (elementBytes.length - valueStartPos)));
428      }
429
430      final byte[] value = new byte[length];
431      System.arraycopy(elementBytes, valueStartPos, value, 0, length);
432
433      int numElements = 0;
434      final ArrayList<ASN1Element> elementList = new ArrayList<>(5);
435      try
436      {
437        int pos = 0;
438        while (pos < value.length)
439        {
440          final byte type = value[pos++];
441
442          final byte firstLengthByte = value[pos++];
443          int l = (firstLengthByte & 0x7F);
444          if (l != firstLengthByte)
445          {
446            final int numLengthBytes = l;
447            l = 0;
448            for (int i=0; i < numLengthBytes; i++)
449            {
450              l <<= 8;
451              l |= (value[pos++] & 0xFF);
452            }
453          }
454
455          final int posPlusLength = pos + l;
456          if ((l < 0) || (posPlusLength < 0) || (posPlusLength > value.length))
457          {
458            throw new ASN1Exception(
459                 ERR_SEQUENCE_BYTES_DECODE_LENGTH_EXCEEDS_AVAILABLE.get());
460          }
461
462          elementList.add(new ASN1Element(type, value, pos, l));
463          pos += l;
464          numElements++;
465        }
466      }
467      catch (final ASN1Exception ae)
468      {
469        throw ae;
470      }
471      catch (final Exception e)
472      {
473        Debug.debugException(e);
474        throw new ASN1Exception(ERR_SEQUENCE_BYTES_DECODE_EXCEPTION.get(e), e);
475      }
476
477      int i = 0;
478      final ASN1Element[] elements = new ASN1Element[numElements];
479      for (final ASN1Element e : elementList)
480      {
481        elements[i++] = e;
482      }
483
484      return new ASN1Sequence(elementBytes[0], elements, value);
485    }
486    catch (final ASN1Exception ae)
487    {
488      Debug.debugException(ae);
489      throw ae;
490    }
491    catch (final Exception e)
492    {
493      Debug.debugException(e);
494      throw new ASN1Exception(ERR_ELEMENT_DECODE_EXCEPTION.get(e), e);
495    }
496  }
497
498
499
500  /**
501   * Decodes the provided ASN.1 element as a sequence element.
502   *
503   * @param  element  The ASN.1 element to be decoded.
504   *
505   * @return  The decoded ASN.1 sequence element.
506   *
507   * @throws  ASN1Exception  If the provided element cannot be decoded as a
508   *                         sequence element.
509   */
510  @NotNull()
511  public static ASN1Sequence decodeAsSequence(
512                                  @NotNull final ASN1Element element)
513         throws ASN1Exception
514  {
515    int numElements = 0;
516    final ArrayList<ASN1Element> elementList = new ArrayList<>(5);
517    final byte[] value = element.getValue();
518
519    try
520    {
521      int pos = 0;
522      while (pos < value.length)
523      {
524        final byte type = value[pos++];
525
526        final byte firstLengthByte = value[pos++];
527        int length = (firstLengthByte & 0x7F);
528        if (length != firstLengthByte)
529        {
530          final int numLengthBytes = length;
531          length = 0;
532          for (int i=0; i < numLengthBytes; i++)
533          {
534            length <<= 8;
535            length |= (value[pos++] & 0xFF);
536          }
537        }
538
539        final int posPlusLength = pos + length;
540        if ((length < 0) || (posPlusLength < 0) ||
541            (posPlusLength > value.length))
542        {
543          throw new ASN1Exception(
544               ERR_SEQUENCE_DECODE_LENGTH_EXCEEDS_AVAILABLE.get(
545                    String.valueOf(element)));
546        }
547
548        elementList.add(new ASN1Element(type, value, pos, length));
549        pos += length;
550        numElements++;
551      }
552    }
553    catch (final ASN1Exception ae)
554    {
555      throw ae;
556    }
557    catch (final Exception e)
558    {
559      Debug.debugException(e);
560      throw new ASN1Exception(
561           ERR_SEQUENCE_DECODE_EXCEPTION.get(String.valueOf(element), e), e);
562    }
563
564    int i = 0;
565    final ASN1Element[] elements = new ASN1Element[numElements];
566    for (final ASN1Element e : elementList)
567    {
568      elements[i++] = e;
569    }
570
571    return new ASN1Sequence(element.getType(), elements, value);
572  }
573
574
575
576  /**
577   * {@inheritDoc}
578   */
579  @Override()
580  public void toString(@NotNull final StringBuilder buffer)
581  {
582    buffer.append('[');
583    for (int i=0; i < elements.length; i++)
584    {
585      if (i > 0)
586      {
587        buffer.append(',');
588      }
589      elements[i].toString(buffer);
590    }
591    buffer.append(']');
592  }
593}