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