001/*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 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 */
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.NotMutable;
030import com.unboundid.util.ThreadSafety;
031import com.unboundid.util.ThreadSafetyLevel;
032
033import static com.unboundid.asn1.ASN1Constants.*;
034import static com.unboundid.asn1.ASN1Messages.*;
035import 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)
047public 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}