001/*
002 * Copyright 2007-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2022 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-2022 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.ldap.sdk;
037
038
039
040import java.io.Serializable;
041import java.lang.reflect.Constructor;
042import java.util.ArrayList;
043import java.util.concurrent.ConcurrentHashMap;
044
045import com.unboundid.asn1.ASN1Boolean;
046import com.unboundid.asn1.ASN1Buffer;
047import com.unboundid.asn1.ASN1BufferSequence;
048import com.unboundid.asn1.ASN1Constants;
049import com.unboundid.asn1.ASN1Element;
050import com.unboundid.asn1.ASN1Exception;
051import com.unboundid.asn1.ASN1OctetString;
052import com.unboundid.asn1.ASN1Sequence;
053import com.unboundid.asn1.ASN1StreamReader;
054import com.unboundid.asn1.ASN1StreamReaderSequence;
055import com.unboundid.util.Debug;
056import com.unboundid.util.Extensible;
057import com.unboundid.util.NotMutable;
058import com.unboundid.util.NotNull;
059import com.unboundid.util.Nullable;
060import com.unboundid.util.StaticUtils;
061import com.unboundid.util.ThreadSafety;
062import com.unboundid.util.ThreadSafetyLevel;
063import com.unboundid.util.Validator;
064
065import static com.unboundid.ldap.sdk.LDAPMessages.*;
066
067
068
069/**
070 * This class provides a data structure that represents an LDAP control.  A
071 * control is an element that may be attached to an LDAP request or response
072 * to provide additional information about the processing that should be (or has
073 * been) performed.  This class may be overridden to provide additional
074 * processing for specific types of controls.
075 * <BR><BR>
076 * A control includes the following elements:
077 * <UL>
078 *   <LI>An object identifier (OID), which identifies the type of control.</LI>
079 *   <LI>A criticality flag, which indicates whether the control should be
080 *       considered critical to the processing of the operation.  If a control
081 *       is marked critical but the server either does not support that control
082 *       or it is not appropriate for the associated request, then the server
083 *       will reject the request.  If a control is not marked critical and the
084 *       server either does not support it or it is not appropriate for the
085 *       associated request, then the server will simply ignore that
086 *       control and process the request as if it were not present.</LI>
087 *   <LI>An optional value, which provides additional information for the
088 *       control.  Some controls do not take values, and the value encoding for
089 *       controls which do take values varies based on the type of control.</LI>
090 * </UL>
091 * Controls may be included in a request from the client to the server, as well
092 * as responses from the server to the client (including intermediate response,
093 * search result entry, and search result references, in addition to the final
094 * response message for an operation).  When using request controls, they may be
095 * included in the request object at the time it is created, or may be added
096 * after the fact for {@link UpdatableLDAPRequest} objects.  When using
097 * response controls, each response control class includes a {@code get} method
098 * that can be used to extract the appropriate control from an appropriate
099 * result (e.g.,  {@link LDAPResult}, {@link SearchResultEntry}, or
100 * {@link SearchResultReference}).
101 */
102@Extensible()
103@NotMutable()
104@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
105public class Control
106       implements Serializable
107{
108  /**
109   * The BER type to use for the encoded set of controls in an LDAP message.
110   */
111  private static final byte CONTROLS_TYPE = (byte) 0xA0;
112
113
114
115  /**
116   * A map of the decodable control classes that have been registered with the
117   * LDAP SDK, mapped from OID to fully-qualified class name.
118   */
119  @NotNull static final ConcurrentHashMap<String,String>
120       DECODEABLE_CONTROL_CLASS_NAMES = new ConcurrentHashMap<>();
121
122
123
124  /**
125   * A map of the instantiated decodeable control classes registered with the
126   * LDAP SDK, mapped from OID to class instance.
127   */
128  @NotNull private static final ConcurrentHashMap<String,DecodeableControl>
129       DECODEABLE_CONTROL_INSTANCES = new ConcurrentHashMap<>();
130
131
132
133  /**
134   * The serial version UID for this serializable class.
135   */
136  private static final long serialVersionUID = 4440956109070220054L;
137
138
139
140  // The encoded value for this control, if there is one.
141  @Nullable private final ASN1OctetString value;
142
143  // Indicates whether this control should be considered critical.
144  private final boolean isCritical;
145
146  // The OID for this control
147  @NotNull private final String oid;
148
149
150
151  static
152  {
153    com.unboundid.ldap.sdk.controls.ControlHelper.
154         registerDefaultResponseControls();
155    com.unboundid.ldap.sdk.experimental.ControlHelper.
156         registerDefaultResponseControls();
157    com.unboundid.ldap.sdk.unboundidds.controls.ControlHelper.
158         registerDefaultResponseControls();
159  }
160
161
162
163  /**
164   * Creates a new empty control instance that is intended to be used only for
165   * decoding controls via the {@code DecodeableControl} interface.  All
166   * {@code DecodeableControl} objects must provide a default constructor that
167   * can be used to create an instance suitable for invoking the
168   * {@code decodeControl} method.
169   */
170  protected Control()
171  {
172    oid        = null;
173    isCritical = true;
174    value      = null;
175  }
176
177
178
179  /**
180   * Creates a new control whose fields are initialized from the contents of the
181   * provided control.
182   *
183   * @param  control  The control whose information should be used to create
184   *                  this new control.
185   */
186  protected Control(@NotNull final Control control)
187  {
188    oid        = control.oid;
189    isCritical = control.isCritical;
190    value      = control.value;
191  }
192
193
194
195  /**
196   * Creates a new control with the provided OID.  It will not be critical, and
197   * it will not have a value.
198   *
199   * @param  oid  The OID for this control.  It must not be {@code null}.
200   */
201  public Control(@NotNull final String oid)
202  {
203    Validator.ensureNotNull(oid);
204
205    this.oid   = oid;
206    isCritical = false;
207    value      = null;
208  }
209
210
211
212  /**
213   * Creates a new control with the provided OID and criticality.  It will not
214   * have a value.
215   *
216   * @param  oid         The OID for this control.  It must not be {@code null}.
217   * @param  isCritical  Indicates whether this control should be considered
218   *                     critical.
219   */
220  public Control(@NotNull final String oid, final boolean isCritical)
221  {
222    Validator.ensureNotNull(oid);
223
224    this.oid        = oid;
225    this.isCritical = isCritical;
226    value           = null;
227  }
228
229
230
231  /**
232   * Creates a new control with the provided information.
233   *
234   * @param  oid         The OID for this control.  It must not be {@code null}.
235   * @param  isCritical  Indicates whether this control should be considered
236   *                     critical.
237   * @param  value       The value for this control.  It may be {@code null} if
238   *                     there is no value.
239   */
240  public Control(@NotNull final String oid, final boolean isCritical,
241                 @Nullable final ASN1OctetString value)
242  {
243    Validator.ensureNotNull(oid);
244
245    this.oid        = oid;
246    this.isCritical = isCritical;
247    this.value      = value;
248  }
249
250
251
252  /**
253   * Retrieves the OID for this control.
254   *
255   * @return  The OID for this control.
256   */
257  @NotNull()
258  public final String getOID()
259  {
260    return oid;
261  }
262
263
264
265  /**
266   * Indicates whether this control should be considered critical.
267   *
268   * @return  {@code true} if this control should be considered critical, or
269   *          {@code false} if not.
270   */
271  public final boolean isCritical()
272  {
273    return isCritical;
274  }
275
276
277
278  /**
279   * Indicates whether this control has a value.
280   *
281   * @return  {@code true} if this control has a value, or {@code false} if not.
282   */
283  public final boolean hasValue()
284  {
285    return (value != null);
286  }
287
288
289
290  /**
291   * Retrieves the encoded value for this control.
292   *
293   * @return  The encoded value for this control, or {@code null} if there is no
294   *          value.
295   */
296  @Nullable()
297  public final ASN1OctetString getValue()
298  {
299    return value;
300  }
301
302
303
304  /**
305   * Writes an ASN.1-encoded representation of this control to the provided
306   * ASN.1 stream writer.
307   *
308   * @param  writer  The ASN.1 stream writer to which the encoded representation
309   *                 should be written.
310   */
311  public final void writeTo(@NotNull final ASN1Buffer writer)
312  {
313    final ASN1BufferSequence controlSequence = writer.beginSequence();
314    writer.addOctetString(oid);
315
316    if (isCritical)
317    {
318      writer.addBoolean(true);
319    }
320
321    if (value != null)
322    {
323      writer.addOctetString(value.getValue());
324    }
325
326    controlSequence.end();
327  }
328
329
330
331  /**
332   * Encodes this control to an ASN.1 sequence suitable for use in an LDAP
333   * message.
334   *
335   * @return  The encoded representation of this control.
336   */
337  @NotNull()
338  public final ASN1Sequence encode()
339  {
340    final ArrayList<ASN1Element> elementList = new ArrayList<>(3);
341    elementList.add(new ASN1OctetString(oid));
342
343    if (isCritical)
344    {
345      elementList.add(new ASN1Boolean(isCritical));
346    }
347
348    if (value != null)
349    {
350      elementList.add(new ASN1OctetString(value.getValue()));
351    }
352
353    return new ASN1Sequence(elementList);
354  }
355
356
357
358  /**
359   * Reads an LDAP control from the provided ASN.1 stream reader.
360   *
361   * @param  reader  The ASN.1 stream reader from which to read the control.
362   *
363   * @return  The decoded control.
364   *
365   * @throws  LDAPException  If a problem occurs while attempting to read or
366   *                         parse the control.
367   */
368  @NotNull()
369  public static Control readFrom(@NotNull final ASN1StreamReader reader)
370         throws LDAPException
371  {
372    try
373    {
374      final ASN1StreamReaderSequence controlSequence = reader.beginSequence();
375      final String oid = reader.readString();
376
377      boolean isCritical = false;
378      ASN1OctetString value = null;
379      while (controlSequence.hasMoreElements())
380      {
381        final byte type = (byte) reader.peek();
382        switch (type)
383        {
384          case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE:
385            isCritical = reader.readBoolean();
386            break;
387          case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE:
388            value = new ASN1OctetString(reader.readBytes());
389            break;
390          default:
391            throw new LDAPException(ResultCode.DECODING_ERROR,
392                 ERR_CONTROL_INVALID_TYPE.get(StaticUtils.toHex(type)));
393        }
394      }
395
396      return decode(oid, isCritical, value);
397    }
398    catch (final LDAPException le)
399    {
400      Debug.debugException(le);
401      throw le;
402    }
403    catch (final Exception e)
404    {
405      Debug.debugException(e);
406      throw new LDAPException(ResultCode.DECODING_ERROR,
407           ERR_CONTROL_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)),
408           e);
409    }
410  }
411
412
413
414  /**
415   * Decodes the provided ASN.1 sequence as an LDAP control.
416   *
417   * @param  controlSequence  The ASN.1 sequence to be decoded.
418   *
419   * @return  The decoded control.
420   *
421   * @throws  LDAPException  If a problem occurs while attempting to decode the
422   *                         provided ASN.1 sequence as an LDAP control.
423   */
424  @NotNull()
425  public static Control decode(@NotNull final ASN1Sequence controlSequence)
426         throws LDAPException
427  {
428    final ASN1Element[] elements = controlSequence.elements();
429
430    if ((elements.length < 1) || (elements.length > 3))
431    {
432      throw new LDAPException(ResultCode.DECODING_ERROR,
433                              ERR_CONTROL_DECODE_INVALID_ELEMENT_COUNT.get(
434                                   elements.length));
435    }
436
437    final String oid =
438         ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
439
440    boolean isCritical = false;
441    ASN1OctetString value = null;
442    if (elements.length == 2)
443    {
444      switch (elements[1].getType())
445      {
446        case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE:
447          try
448          {
449            isCritical =
450                 ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
451          }
452          catch (final ASN1Exception ae)
453          {
454            Debug.debugException(ae);
455            throw new LDAPException(ResultCode.DECODING_ERROR,
456                 ERR_CONTROL_DECODE_CRITICALITY.get(
457                      StaticUtils.getExceptionMessage(ae)),
458                 ae);
459          }
460          break;
461
462        case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE:
463          value = ASN1OctetString.decodeAsOctetString(elements[1]);
464          break;
465
466        default:
467          throw new LDAPException(ResultCode.DECODING_ERROR,
468               ERR_CONTROL_INVALID_TYPE.get(
469                    StaticUtils.toHex(elements[1].getType())));
470      }
471    }
472    else if (elements.length == 3)
473    {
474      try
475      {
476        isCritical = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
477      }
478      catch (final ASN1Exception ae)
479      {
480        Debug.debugException(ae);
481        throw new LDAPException(ResultCode.DECODING_ERROR,
482             ERR_CONTROL_DECODE_CRITICALITY.get(
483                  StaticUtils.getExceptionMessage(ae)),
484             ae);
485      }
486
487      value = ASN1OctetString.decodeAsOctetString(elements[2]);
488    }
489
490    return decode(oid, isCritical, value);
491  }
492
493
494
495  /**
496   * Attempts to create the most appropriate control instance from the provided
497   * information.  If a {@link DecodeableControl} instance has been registered
498   * for the specified OID, then this method will attempt to use that instance
499   * to construct a control.  If that fails, or if no appropriate
500   * {@code DecodeableControl} is registered, then a generic control will be
501   * returned.
502   *
503   * @param  oid         The OID for the control.  It must not be {@code null}.
504   * @param  isCritical  Indicates whether the control should be considered
505   *                     critical.
506   * @param  value       The value for the control.  It may be {@code null} if
507   *                     there is no value.
508   *
509   * @return  The decoded control.
510   *
511   * @throws  LDAPException  If a problem occurs while attempting to decode the
512   *                         provided ASN.1 sequence as an LDAP control.
513   */
514  @NotNull()
515  public static Control decode(@NotNull final String oid,
516                               final boolean isCritical,
517                               @Nullable final ASN1OctetString value)
518         throws LDAPException
519  {
520    DecodeableControl decodeableControl = DECODEABLE_CONTROL_INSTANCES.get(oid);
521    if (decodeableControl == null)
522    {
523      final String controlClassName = DECODEABLE_CONTROL_CLASS_NAMES.get(oid);
524      if (controlClassName == null)
525      {
526        return new Control(oid, isCritical, value);
527      }
528
529      try
530      {
531        final Class<?> controlClass = Class.forName(controlClassName);
532        final Constructor<?> noArgumentConstructor =
533             controlClass.getDeclaredConstructor();
534        noArgumentConstructor.setAccessible(true);
535        decodeableControl =
536             (DecodeableControl) noArgumentConstructor.newInstance();
537      }
538      catch (final Exception e)
539      {
540        Debug.debugException(e);
541        return new Control(oid, isCritical, value);
542      }
543    }
544
545    try
546    {
547      return decodeableControl.decodeControl(oid, isCritical, value);
548    }
549    catch (final Exception e)
550    {
551      Debug.debugException(e);
552      return new Control(oid, isCritical, value);
553    }
554  }
555
556
557
558  /**
559   * Encodes the provided set of controls to an ASN.1 sequence suitable for
560   * inclusion in an LDAP message.
561   *
562   * @param  controls  The set of controls to be encoded.
563   *
564   * @return  An ASN.1 sequence containing the encoded set of controls.
565   */
566  @NotNull()
567  public static ASN1Sequence encodeControls(@NotNull final Control[] controls)
568  {
569    final ASN1Sequence[] controlElements = new ASN1Sequence[controls.length];
570    for (int i=0; i < controls.length; i++)
571    {
572      controlElements[i] = controls[i].encode();
573    }
574
575    return new ASN1Sequence(CONTROLS_TYPE, controlElements);
576  }
577
578
579
580  /**
581   * Decodes the contents of the provided sequence as a set of controls.
582   *
583   * @param  controlSequence  The ASN.1 sequence containing the encoded set of
584   *                          controls.
585   *
586   * @return  The decoded set of controls.
587   *
588   * @throws  LDAPException  If a problem occurs while attempting to decode any
589   *                         of the controls.
590   */
591  @NotNull()
592  public static Control[] decodeControls(
593                               @NotNull final ASN1Sequence controlSequence)
594         throws LDAPException
595  {
596    final ASN1Element[] controlElements = controlSequence.elements();
597    final Control[] controls = new Control[controlElements.length];
598
599    for (int i=0; i < controlElements.length; i++)
600    {
601      try
602      {
603        controls[i] = decode(ASN1Sequence.decodeAsSequence(controlElements[i]));
604      }
605      catch (final ASN1Exception ae)
606      {
607        Debug.debugException(ae);
608        throw new LDAPException(ResultCode.DECODING_ERROR,
609             ERR_CONTROLS_DECODE_ELEMENT_NOT_SEQUENCE.get(
610                  StaticUtils.getExceptionMessage(ae)),
611             ae);
612      }
613    }
614
615    return controls;
616  }
617
618
619
620  /**
621   * Registers the specified class to be used in an attempt to decode controls
622   * with the specified OID.
623   *
624   * @param  oid        The response control OID for which the provided class
625   *                    will be registered.
626   * @param  className  The fully-qualified name for the Java class that
627   *                    provides the decodeable control implementation to use
628   *                    for the provided OID.
629   */
630  public static void registerDecodeableControl(@NotNull final String oid,
631                                               @NotNull final String className)
632  {
633    DECODEABLE_CONTROL_CLASS_NAMES.put(oid, className);
634    DECODEABLE_CONTROL_INSTANCES.remove(oid);
635  }
636
637
638
639  /**
640   * Registers the provided class to be used in an attempt to decode controls
641   * with the specified OID.
642   *
643   * @param  oid              The response control OID for which the provided
644   *                          class will be registered.
645   * @param  controlInstance  The control instance that should be used to decode
646   *                          controls with the provided OID.
647   */
648  public static void registerDecodeableControl(@NotNull final String oid,
649                          @NotNull final DecodeableControl controlInstance)
650  {
651    DECODEABLE_CONTROL_CLASS_NAMES.put(oid,
652         controlInstance.getClass().getName());
653    DECODEABLE_CONTROL_INSTANCES.put(oid, controlInstance);
654  }
655
656
657
658  /**
659   * Deregisters the decodeable control class associated with the provided OID.
660   *
661   * @param  oid  The response control OID for which to deregister the
662   *              decodeable control class.
663   */
664  public static void deregisterDecodeableControl(@NotNull final String oid)
665  {
666    DECODEABLE_CONTROL_CLASS_NAMES.remove(oid);
667    DECODEABLE_CONTROL_INSTANCES.remove(oid);
668  }
669
670
671
672  /**
673   * Retrieves a hash code for this control.
674   *
675   * @return  A hash code for this control.
676   */
677  @Override()
678  public final int hashCode()
679  {
680    int hashCode = oid.hashCode();
681
682    if (isCritical)
683    {
684      hashCode++;
685    }
686
687    if (value != null)
688    {
689      hashCode += value.hashCode();
690    }
691
692    return hashCode;
693  }
694
695
696
697  /**
698   * Indicates whether the provided object may be considered equal to this
699   * control.
700   *
701   * @param  o  The object for which to make the determination.
702   *
703   * @return  {@code true} if the provided object may be considered equal to
704   *          this control, or {@code false} if not.
705   */
706  @Override()
707  public final boolean equals(@Nullable final Object o)
708  {
709    if (o == null)
710    {
711      return false;
712    }
713
714    if (o == this)
715    {
716      return true;
717    }
718
719    if (! (o instanceof Control))
720    {
721      return false;
722    }
723
724    final Control c = (Control) o;
725    if (! oid.equals(c.oid))
726    {
727      return false;
728    }
729
730    if (isCritical != c.isCritical)
731    {
732      return false;
733    }
734
735    if (value == null)
736    {
737      if (c.value != null)
738      {
739        return false;
740      }
741    }
742    else
743    {
744      if (c.value == null)
745      {
746        return false;
747      }
748
749      if (! value.equals(c.value))
750      {
751        return false;
752      }
753    }
754
755
756    return true;
757  }
758
759
760
761  /**
762   * Retrieves the user-friendly name for this control, if available.  If no
763   * user-friendly name has been defined, then the OID will be returned.
764   *
765   * @return  The user-friendly name for this control, or the OID if no
766   *          user-friendly name is available.
767   */
768  @NotNull()
769  public String getControlName()
770  {
771    // By default, we will return the OID.  Subclasses should override this to
772    // provide the user-friendly name.
773    return oid;
774  }
775
776
777
778  /**
779   * Retrieves a string representation of this LDAP control.
780   *
781   * @return  A string representation of this LDAP control.
782   */
783  @Override()
784  @NotNull()
785  public String toString()
786  {
787    final StringBuilder buffer = new StringBuilder();
788    toString(buffer);
789    return buffer.toString();
790  }
791
792
793
794  /**
795   * Appends a string representation of this LDAP control to the provided
796   * buffer.
797   *
798   * @param  buffer  The buffer to which to append the string representation of
799   *                 this buffer.
800   */
801  public void toString(@NotNull final StringBuilder buffer)
802  {
803    buffer.append("Control(oid=");
804    buffer.append(oid);
805    buffer.append(", isCritical=");
806    buffer.append(isCritical);
807    buffer.append(", value=");
808
809    if (value == null)
810    {
811      buffer.append("{null}");
812    }
813    else
814    {
815      buffer.append("{byte[");
816      buffer.append(value.getValue().length);
817      buffer.append("]}");
818    }
819
820    buffer.append(')');
821  }
822}