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.ASN1Element;
033import com.unboundid.asn1.ASN1Exception;
034import com.unboundid.asn1.ASN1OctetString;
035import com.unboundid.asn1.ASN1Sequence;
036import com.unboundid.asn1.ASN1StreamReader;
037import com.unboundid.asn1.ASN1StreamReaderSequence;
038import com.unboundid.util.Extensible;
039import com.unboundid.util.NotMutable;
040import com.unboundid.util.ThreadSafety;
041import com.unboundid.util.ThreadSafetyLevel;
042
043import static com.unboundid.asn1.ASN1Constants.*;
044import static com.unboundid.ldap.sdk.LDAPMessages.*;
045import static com.unboundid.util.Debug.*;
046import static com.unboundid.util.StaticUtils.*;
047import static com.unboundid.util.Validator.*;
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<String,DecodeableControl>();
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    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    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    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<ASN1Element>(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 UNIVERSAL_BOOLEAN_TYPE:
353            isCritical = reader.readBoolean();
354            break;
355          case 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(toHex(type)));
361        }
362      }
363
364      return decode(oid, isCritical, value);
365    }
366    catch (final LDAPException le)
367    {
368      debugException(le);
369      throw le;
370    }
371    catch (final Exception e)
372    {
373      debugException(e);
374      throw new LDAPException(ResultCode.DECODING_ERROR,
375           ERR_CONTROL_CANNOT_DECODE.get(getExceptionMessage(e)), e);
376    }
377  }
378
379
380
381  /**
382   * Decodes the provided ASN.1 sequence as an LDAP control.
383   *
384   * @param  controlSequence  The ASN.1 sequence to be decoded.
385   *
386   * @return  The decoded control.
387   *
388   * @throws  LDAPException  If a problem occurs while attempting to decode the
389   *                         provided ASN.1 sequence as an LDAP control.
390   */
391  public static Control decode(final ASN1Sequence controlSequence)
392         throws LDAPException
393  {
394    final ASN1Element[] elements = controlSequence.elements();
395
396    if ((elements.length < 1) || (elements.length > 3))
397    {
398      throw new LDAPException(ResultCode.DECODING_ERROR,
399                              ERR_CONTROL_DECODE_INVALID_ELEMENT_COUNT.get(
400                                   elements.length));
401    }
402
403    final String oid =
404         ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
405
406    boolean isCritical = false;
407    ASN1OctetString value = null;
408    if (elements.length == 2)
409    {
410      switch (elements[1].getType())
411      {
412        case UNIVERSAL_BOOLEAN_TYPE:
413          try
414          {
415            isCritical =
416                 ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
417          }
418          catch (final ASN1Exception ae)
419          {
420            debugException(ae);
421            throw new LDAPException(ResultCode.DECODING_ERROR,
422                 ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)),
423                 ae);
424          }
425          break;
426
427        case UNIVERSAL_OCTET_STRING_TYPE:
428          value = ASN1OctetString.decodeAsOctetString(elements[1]);
429          break;
430
431        default:
432          throw new LDAPException(ResultCode.DECODING_ERROR,
433                                  ERR_CONTROL_INVALID_TYPE.get(
434                                       toHex(elements[1].getType())));
435      }
436    }
437    else if (elements.length == 3)
438    {
439      try
440      {
441        isCritical = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
442      }
443      catch (final ASN1Exception ae)
444      {
445        debugException(ae);
446        throw new LDAPException(ResultCode.DECODING_ERROR,
447             ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)), ae);
448      }
449
450      value = ASN1OctetString.decodeAsOctetString(elements[2]);
451    }
452
453    return decode(oid, isCritical, value);
454  }
455
456
457
458  /**
459   * Attempts to create the most appropriate control instance from the provided
460   * information.  If a {@link DecodeableControl} instance has been registered
461   * for the specified OID, then this method will attempt to use that instance
462   * to construct a control.  If that fails, or if no appropriate
463   * {@code DecodeableControl} is registered, then a generic control will be
464   * returned.
465   *
466   * @param  oid         The OID for the control.  It must not be {@code null}.
467   * @param  isCritical  Indicates whether the control should be considered
468   *                     critical.
469   * @param  value       The value for the control.  It may be {@code null} if
470   *                     there is no value.
471   *
472   * @return  The decoded control.
473   *
474   * @throws  LDAPException  If a problem occurs while attempting to decode the
475   *                         provided ASN.1 sequence as an LDAP control.
476   */
477  public static Control decode(final String oid, final boolean isCritical,
478                               final ASN1OctetString value)
479         throws LDAPException
480  {
481     final DecodeableControl decodeableControl = decodeableControlMap.get(oid);
482     if (decodeableControl == null)
483     {
484       return new Control(oid, isCritical, value);
485     }
486     else
487     {
488       try
489       {
490         return decodeableControl.decodeControl(oid, isCritical, value);
491       }
492       catch (final Exception e)
493       {
494         debugException(e);
495         return new Control(oid, isCritical, value);
496       }
497     }
498  }
499
500
501
502  /**
503   * Encodes the provided set of controls to an ASN.1 sequence suitable for
504   * inclusion in an LDAP message.
505   *
506   * @param  controls  The set of controls to be encoded.
507   *
508   * @return  An ASN.1 sequence containing the encoded set of controls.
509   */
510  public static ASN1Sequence encodeControls(final Control[] controls)
511  {
512    final ASN1Sequence[] controlElements = new ASN1Sequence[controls.length];
513    for (int i=0; i < controls.length; i++)
514    {
515      controlElements[i] = controls[i].encode();
516    }
517
518    return new ASN1Sequence(CONTROLS_TYPE, controlElements);
519  }
520
521
522
523  /**
524   * Decodes the contents of the provided sequence as a set of controls.
525   *
526   * @param  controlSequence  The ASN.1 sequence containing the encoded set of
527   *                          controls.
528   *
529   * @return  The decoded set of controls.
530   *
531   * @throws  LDAPException  If a problem occurs while attempting to decode any
532   *                         of the controls.
533   */
534  public static Control[] decodeControls(final ASN1Sequence controlSequence)
535         throws LDAPException
536  {
537    final ASN1Element[] controlElements = controlSequence.elements();
538    final Control[] controls = new Control[controlElements.length];
539
540    for (int i=0; i < controlElements.length; i++)
541    {
542      try
543      {
544        controls[i] = decode(ASN1Sequence.decodeAsSequence(controlElements[i]));
545      }
546      catch (final ASN1Exception ae)
547      {
548        debugException(ae);
549        throw new LDAPException(ResultCode.DECODING_ERROR,
550                                ERR_CONTROLS_DECODE_ELEMENT_NOT_SEQUENCE.get(
551                                     getExceptionMessage(ae)),
552                                ae);
553      }
554    }
555
556    return controls;
557  }
558
559
560
561  /**
562   * Registers the provided class to be used in an attempt to decode controls
563   * with the specified OID.
564   *
565   * @param  oid              The response control OID for which the provided
566   *                          class will be registered.
567   * @param  controlInstance  The control instance that should be used to decode
568   *                          controls with the provided OID.
569   */
570  public static void registerDecodeableControl(final String oid,
571                          final DecodeableControl controlInstance)
572  {
573    decodeableControlMap.put(oid, controlInstance);
574  }
575
576
577
578  /**
579   * Deregisters the decodeable control class associated with the provided OID.
580   *
581   * @param  oid  The response control OID for which to deregister the
582   *              decodeable control class.
583   */
584  public static void deregisterDecodeableControl(final String oid)
585  {
586    decodeableControlMap.remove(oid);
587  }
588
589
590
591  /**
592   * Retrieves a hash code for this control.
593   *
594   * @return  A hash code for this control.
595   */
596  @Override()
597  public final int hashCode()
598  {
599    int hashCode = oid.hashCode();
600
601    if (isCritical)
602    {
603      hashCode++;
604    }
605
606    if (value != null)
607    {
608      hashCode += value.hashCode();
609    }
610
611    return hashCode;
612  }
613
614
615
616  /**
617   * Indicates whether the provided object may be considered equal to this
618   * control.
619   *
620   * @param  o  The object for which to make the determination.
621   *
622   * @return  {@code true} if the provided object may be considered equal to
623   *          this control, or {@code false} if not.
624   */
625  @Override()
626  public final boolean equals(final Object o)
627  {
628    if (o == null)
629    {
630      return false;
631    }
632
633    if (o == this)
634    {
635      return true;
636    }
637
638    if (! (o instanceof Control))
639    {
640      return false;
641    }
642
643    final Control c = (Control) o;
644    if (! oid.equals(c.oid))
645    {
646      return false;
647    }
648
649    if (isCritical != c.isCritical)
650    {
651      return false;
652    }
653
654    if (value == null)
655    {
656      if (c.value != null)
657      {
658        return false;
659      }
660    }
661    else
662    {
663      if (c.value == null)
664      {
665        return false;
666      }
667
668      if (! value.equals(c.value))
669      {
670        return false;
671      }
672    }
673
674
675    return true;
676  }
677
678
679
680  /**
681   * Retrieves the user-friendly name for this control, if available.  If no
682   * user-friendly name has been defined, then the OID will be returned.
683   *
684   * @return  The user-friendly name for this control, or the OID if no
685   *          user-friendly name is available.
686   */
687  public String getControlName()
688  {
689    // By default, we will return the OID.  Subclasses should override this to
690    // provide the user-friendly name.
691    return oid;
692  }
693
694
695
696  /**
697   * Retrieves a string representation of this LDAP control.
698   *
699   * @return  A string representation of this LDAP control.
700   */
701  @Override()
702  public String toString()
703  {
704    final StringBuilder buffer = new StringBuilder();
705    toString(buffer);
706    return buffer.toString();
707  }
708
709
710
711  /**
712   * Appends a string representation of this LDAP control to the provided
713   * buffer.
714   *
715   * @param  buffer  The buffer to which to append the string representation of
716   *                 this buffer.
717   */
718  public void toString(final StringBuilder buffer)
719  {
720    buffer.append("Control(oid=");
721    buffer.append(oid);
722    buffer.append(", isCritical=");
723    buffer.append(isCritical);
724    buffer.append(", value=");
725
726    if (value == null)
727    {
728      buffer.append("{null}");
729    }
730    else
731    {
732      buffer.append("{byte[");
733      buffer.append(value.getValue().length);
734      buffer.append("]}");
735    }
736
737    buffer.append(')');
738  }
739}