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