001/*
002 * Copyright 2008-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-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) 2008-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.io.IOException;
041import java.io.OutputStream;
042import java.nio.BufferOverflowException;
043import java.nio.ByteBuffer;
044
045import com.unboundid.util.ByteStringBuffer;
046import com.unboundid.util.Debug;
047import com.unboundid.util.NotNull;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050
051
052
053/**
054 * This class provides an efficient mechanism for writing ASN.1 elements to
055 * output streams.
056 */
057@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
058public final class ASN1Writer
059{
060  /**
061   * The thread-local buffers that will be used for encoding the elements.
062   */
063  @NotNull private static final ThreadLocal<ByteStringBuffer> BUFFERS =
064       new ThreadLocal<>();
065
066
067
068  /**
069   * The maximum amount of memory that will be used for a thread-local buffer.
070   */
071  private static final int MAX_BUFFER_LENGTH = 524_288;
072
073
074
075  /**
076   * Prevent this class from being instantiated.
077   */
078  private ASN1Writer()
079  {
080    // No implementation is required.
081  }
082
083
084
085  /**
086   * Writes an encoded representation of the provided ASN.1 element to the
087   * given output stream.
088   *
089   * @param  element       The ASN.1 element to be written.
090   * @param  outputStream  The output stream to which the encoded representation
091   *                       of the element should be written.
092   *
093   * @throws  IOException  If a problem occurs while writing the element.
094   */
095  public static void writeElement(@NotNull final ASN1Element element,
096                                  @NotNull final OutputStream outputStream)
097         throws IOException
098  {
099    Debug.debugASN1Write(element);
100
101    ByteStringBuffer buffer = BUFFERS.get();
102    if (buffer == null)
103    {
104      buffer = new ByteStringBuffer();
105      BUFFERS.set(buffer);
106    }
107
108    element.encodeTo(buffer);
109
110    try
111    {
112      buffer.write(outputStream);
113    }
114    finally
115    {
116      if (buffer.capacity() > MAX_BUFFER_LENGTH)
117      {
118        buffer.setCapacity(MAX_BUFFER_LENGTH);
119      }
120      buffer.clear();
121    }
122  }
123
124
125
126  /**
127   * Appends an encoded representation of the provided ASN.1 element to the
128   * given byte buffer.  When this method completes, the position will be at the
129   * beginning of the written element, and the limit will be at the end.
130   *
131   * @param  element  The ASN.1 element to be written.
132   * @param  buffer   The buffer to which the element should be added.
133   *
134   * @throws  BufferOverflowException  If the provided buffer does not have
135   *                                   enough space between the position and
136   *                                   the limit to hold the encoded element.
137   */
138  public static void writeElement(@NotNull final ASN1Element element,
139                                  @NotNull final ByteBuffer buffer)
140         throws BufferOverflowException
141  {
142    Debug.debugASN1Write(element);
143
144    ByteStringBuffer b = BUFFERS.get();
145    if (b == null)
146    {
147      b = new ByteStringBuffer();
148      BUFFERS.set(b);
149    }
150
151    element.encodeTo(b);
152
153    try
154    {
155      if (buffer.remaining() < b.length())
156      {
157        throw new BufferOverflowException();
158      }
159
160      final int pos = buffer.position();
161      buffer.put(b.getBackingArray(), 0, b.length());
162      buffer.limit(buffer.position());
163      buffer.position(pos);
164    }
165    finally
166    {
167      if (b.capacity() > MAX_BUFFER_LENGTH)
168      {
169        b.setCapacity(MAX_BUFFER_LENGTH);
170      }
171      b.clear();
172    }
173  }
174}