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