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