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