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