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}