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