mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
25.33.2012 263d085885df024dca9250cc03c807912b0a7662
opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPReader.java
@@ -6,17 +6,16 @@
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opendj3/legal-notices/CDDLv1_0.txt
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
@@ -28,8 +27,6 @@
package com.forgerock.opendj.ldap;
import static com.forgerock.opendj.ldap.LDAPConstants.*;
import static org.forgerock.opendj.asn1.ASN1Constants.UNIVERSAL_BOOLEAN_TYPE;
import static org.forgerock.opendj.asn1.ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE;
@@ -42,1778 +39,1444 @@
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.asn1.ASN1Reader;
import org.forgerock.opendj.ldap.*;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.DereferenceAliasesPolicy;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.Modification;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.RDN;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.controls.Control;
import org.forgerock.opendj.ldap.controls.GenericControl;
import org.forgerock.opendj.ldap.requests.*;
import org.forgerock.opendj.ldap.responses.*;
import org.forgerock.opendj.ldap.requests.AbandonRequest;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.CompareRequest;
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.GenericBindRequest;
import org.forgerock.opendj.ldap.requests.GenericExtendedRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Request;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.requests.UnbindRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.GenericExtendedResult;
import org.forgerock.opendj.ldap.responses.GenericIntermediateResponse;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
import org.forgerock.opendj.ldap.schema.Schema;
import com.forgerock.opendj.util.StaticUtils;
/**
 * Static methods for decoding LDAP messages.
 */
final class LDAPReader
{
  static SearchResultEntry decodeEntry(final ASN1Reader reader,
      final DecodeOptions options) throws IOException
  {
    Entry entry;
final class LDAPReader {
    static SearchResultEntry decodeEntry(final ASN1Reader reader, final DecodeOptions options)
            throws IOException {
        Entry entry;
    reader.readStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
    try
    {
      final String dnString = reader.readOctetStringAsString();
      final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
      DN dn;
      try
      {
        dn = DN.valueOf(dnString, schema);
      }
      catch (final LocalizedIllegalArgumentException e)
      {
        throw DecodeException.error(e.getMessageObject());
      }
      entry = options.getEntryFactory().newEntry(dn);
      reader.readStartSequence();
      try
      {
        while (reader.hasNextElement())
        {
          reader.readStartSequence();
          try
          {
            final String ads = reader.readOctetStringAsString();
            AttributeDescription ad;
            try
            {
              ad = AttributeDescription.valueOf(ads, schema);
            }
            catch (final LocalizedIllegalArgumentException e)
            {
              throw DecodeException.error(e.getMessageObject());
        reader.readStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
        try {
            final String dnString = reader.readOctetStringAsString();
            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
            DN dn;
            try {
                dn = DN.valueOf(dnString, schema);
            } catch (final LocalizedIllegalArgumentException e) {
                throw DecodeException.error(e.getMessageObject());
            }
            final Attribute attribute = options.getAttributeFactory()
                .newAttribute(ad);
            reader.readStartSet();
            try
            {
              while (reader.hasNextElement())
              {
                attribute.add(reader.readOctetString());
              }
              entry.addAttribute(attribute);
            }
            finally
            {
              reader.readEndSet();
            }
          }
          finally
          {
            reader.readEndSequence();
          }
        }
      }
      finally
      {
        reader.readEndSequence();
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    return Responses.newSearchResultEntry(entry);
  }
  private final DecodeOptions options;
  LDAPReader(final DecodeOptions options)
  {
    this.options = options;
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP message.
   *
   * @param <P>
   *          The type of {@code param}.
   * @param reader
   *          The ASN.1 reader.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle a decoded
   *          message.
   * @param param
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  <P> void decode(final ASN1Reader reader, final LDAPMessageHandler<P> handler,
      final P param) throws IOException
  {
    reader.readStartSequence();
    try
    {
      final int messageID = (int) reader.readInteger();
      decodeProtocolOp(reader, messageID, handler, param);
    }
    finally
    {
      reader.readEndSequence();
    }
  }
  /**
   * Decodes the elements from the provided ASN.1 read as an LDAP abandon
   * request protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeAbandonRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    final int msgToAbandon = (int) reader.readInteger(OP_TYPE_ABANDON_REQUEST);
    final AbandonRequest message = Requests.newAbandonRequest(msgToAbandon);
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP ABANDON REQUEST(messageID=%d, request=%s)", messageID,
          message));
    }
    handler.abandonRequest(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP add request
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeAddRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    Entry entry;
    reader.readStartSequence(OP_TYPE_ADD_REQUEST);
    try
    {
      final String dnString = reader.readOctetStringAsString();
      final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
      final DN dn = decodeDN(dnString, schema);
      entry = options.getEntryFactory().newEntry(dn);
      reader.readStartSequence();
      try
      {
        while (reader.hasNextElement())
        {
          reader.readStartSequence();
          try
          {
            final String ads = reader.readOctetStringAsString();
            final AttributeDescription ad = decodeAttributeDescription(ads,
                schema);
            final Attribute attribute = options.getAttributeFactory()
                .newAttribute(ad);
            reader.readStartSet();
            try
            {
              while (reader.hasNextElement())
              {
                attribute.add(reader.readOctetString());
              }
              entry.addAttribute(attribute);
            }
            finally
            {
              reader.readEndSet();
            }
          }
          finally
          {
            reader.readEndSequence();
          }
        }
      }
      finally
      {
        reader.readEndSequence();
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    final AddRequest message = Requests.newAddRequest(entry);
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP ADD REQUEST(messageID=%d, request=%s)", messageID,
          message));
    }
    handler.addRequest(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an add response
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeAddResult(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    Result message;
    reader.readStartSequence(OP_TYPE_ADD_RESPONSE);
    try
    {
      final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
      final String matchedDN = reader.readOctetStringAsString();
      final String diagnosticMessage = reader.readOctetStringAsString();
      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
          .setDiagnosticMessage(diagnosticMessage);
      decodeResponseReferrals(reader, message);
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String
          .format("DECODE LDAP ADD RESULT(messageID=%d, result=%s)", messageID,
              message));
    }
    handler.addResult(p, messageID, message);
  }
  private AttributeDescription decodeAttributeDescription(
      final String attributeDescription, final Schema schema)
      throws DecodeException
  {
    try
    {
      return AttributeDescription.valueOf(attributeDescription, schema);
    }
    catch (final LocalizedIllegalArgumentException e)
    {
      throw DecodeException.error(e.getMessageObject());
    }
  }
  /**
   * Decodes the elements from the provided ASN.1 read as an LDAP bind request
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeBindRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    reader.readStartSequence(OP_TYPE_BIND_REQUEST);
    try
    {
      final int protocolVersion = (int) reader.readInteger();
      final String authName = reader.readOctetStringAsString();
      final byte authType = reader.peekType();
      final byte[] authBytes = reader.readOctetString(authType).toByteArray();
      final GenericBindRequest request = Requests.newGenericBindRequest(
          authName, authType, authBytes);
      decodeControls(reader, request);
      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
      {
        StaticUtils.DEBUG_LOG.finer(String.format(
            "DECODE LDAP BIND REQUEST(messageID=%d, auth=0x%x, request=%s)",
            messageID, request.getAuthenticationType(), request));
      }
      handler.bindRequest(p, messageID, protocolVersion, request);
    }
    finally
    {
      reader.readEndSequence();
    }
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a bind response
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeBindResult(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    BindResult message;
    reader.readStartSequence(OP_TYPE_BIND_RESPONSE);
    try
    {
      final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
      final String matchedDN = reader.readOctetStringAsString();
      final String diagnosticMessage = reader.readOctetStringAsString();
      message = Responses.newBindResult(resultCode).setMatchedDN(matchedDN)
          .setDiagnosticMessage(diagnosticMessage);
      decodeResponseReferrals(reader, message);
      if (reader.hasNextElement()
          && (reader.peekType() == TYPE_SERVER_SASL_CREDENTIALS))
      {
        message.setServerSASLCredentials(reader
            .readOctetString(TYPE_SERVER_SASL_CREDENTIALS));
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP BIND RESULT(messageID=%d, result=%s)", messageID,
          message));
    }
    handler.bindResult(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP compare
   * request protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeCompareRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    CompareRequest message;
    reader.readStartSequence(OP_TYPE_COMPARE_REQUEST);
    try
    {
      final String dnString = reader.readOctetStringAsString();
      final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
      final DN dn = decodeDN(dnString, schema);
      reader.readStartSequence();
      try
      {
        final String ads = reader.readOctetStringAsString();
        final AttributeDescription ad = decodeAttributeDescription(ads, schema);
        final ByteString assertionValue = reader.readOctetString();
        message = Requests.newCompareRequest(dn, ad, assertionValue);
      }
      finally
      {
        reader.readEndSequence();
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP COMPARE REQUEST(messageID=%d, request=%s)", messageID,
          message));
    }
    handler.compareRequest(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a compare response
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeCompareResult(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    CompareResult message;
    reader.readStartSequence(OP_TYPE_COMPARE_RESPONSE);
    try
    {
      final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
      final String matchedDN = reader.readOctetStringAsString();
      final String diagnosticMessage = reader.readOctetStringAsString();
      message = Responses.newCompareResult(resultCode).setMatchedDN(matchedDN)
          .setDiagnosticMessage(diagnosticMessage);
      decodeResponseReferrals(reader, message);
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP COMPARE RESULT(messageID=%d, result=%s)", messageID,
          message));
    }
    handler.compareResult(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP control.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param request
   *          The decoded request to decode controls for.
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private void decodeControl(final ASN1Reader reader, final Request request)
      throws IOException
  {
    String oid;
    boolean isCritical;
    ByteString value;
    reader.readStartSequence();
    try
    {
      oid = reader.readOctetStringAsString();
      isCritical = false;
      value = null;
      if (reader.hasNextElement()
          && (reader.peekType() == UNIVERSAL_BOOLEAN_TYPE))
      {
        isCritical = reader.readBoolean();
      }
      if (reader.hasNextElement()
          && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
      {
        value = reader.readOctetString();
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    final Control c = GenericControl.newControl(oid, isCritical, value);
    request.addControl(c);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP control.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param response
   *          The decoded message to decode controls for.
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private void decodeControl(final ASN1Reader reader, final Response response)
      throws IOException
  {
    String oid;
    boolean isCritical;
    ByteString value;
    reader.readStartSequence();
    try
    {
      oid = reader.readOctetStringAsString();
      isCritical = false;
      value = null;
      if (reader.hasNextElement()
          && (reader.peekType() == UNIVERSAL_BOOLEAN_TYPE))
      {
        isCritical = reader.readBoolean();
      }
      if (reader.hasNextElement()
          && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
      {
        value = reader.readOctetString();
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    final Control c = GenericControl.newControl(oid, isCritical, value);
    response.addControl(c);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a set of controls.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param request
   *          The decoded message to decode controls for.
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private void decodeControls(final ASN1Reader reader, final Request request)
      throws IOException
  {
    if (reader.hasNextElement() && (reader.peekType() == TYPE_CONTROL_SEQUENCE))
    {
      reader.readStartSequence(TYPE_CONTROL_SEQUENCE);
      try
      {
        while (reader.hasNextElement())
        {
          decodeControl(reader, request);
        }
      }
      finally
      {
        reader.readEndSequence();
      }
    }
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a set of controls.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param response
   *          The decoded message to decode controls for.
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private void decodeControls(final ASN1Reader reader, final Response response)
      throws IOException
  {
    if (reader.hasNextElement() && (reader.peekType() == TYPE_CONTROL_SEQUENCE))
    {
      reader.readStartSequence(TYPE_CONTROL_SEQUENCE);
      try
      {
        while (reader.hasNextElement())
        {
          decodeControl(reader, response);
        }
      }
      finally
      {
        reader.readEndSequence();
      }
    }
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP delete
   * request protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeDeleteRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    final String dnString = reader
        .readOctetStringAsString(OP_TYPE_DELETE_REQUEST);
    final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
    final DN dn = decodeDN(dnString, schema);
    final DeleteRequest message = Requests.newDeleteRequest(dn);
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP DELETE REQUEST(messageID=%d, request=%s)", messageID,
          message));
    }
    handler.deleteRequest(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a delete response
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeDeleteResult(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    Result message;
    reader.readStartSequence(OP_TYPE_DELETE_RESPONSE);
    try
    {
      final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
      final String matchedDN = reader.readOctetStringAsString();
      final String diagnosticMessage = reader.readOctetStringAsString();
      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
          .setDiagnosticMessage(diagnosticMessage);
      decodeResponseReferrals(reader, message);
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP DELETE RESULT(messageID=%d, result=%s)", messageID,
          message));
    }
    handler.deleteResult(p, messageID, message);
  }
  private DN decodeDN(final String dn, final Schema schema)
      throws DecodeException
  {
    try
    {
      return DN.valueOf(dn, schema);
    }
    catch (final LocalizedIllegalArgumentException e)
    {
      throw DecodeException.error(e.getMessageObject());
    }
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP extended
   * request protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeExtendedRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    String oid;
    ByteString value;
    reader.readStartSequence(OP_TYPE_EXTENDED_REQUEST);
    try
    {
      oid = reader.readOctetStringAsString(TYPE_EXTENDED_REQUEST_OID);
      value = null;
      if (reader.hasNextElement()
          && (reader.peekType() == TYPE_EXTENDED_REQUEST_VALUE))
      {
        value = reader.readOctetString(TYPE_EXTENDED_REQUEST_VALUE);
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    final GenericExtendedRequest message = Requests.newGenericExtendedRequest(
        oid, value);
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP EXTENDED REQUEST(messageID=%d, request=%s)", messageID,
          message));
    }
    handler.extendedRequest(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a extended response
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeExtendedResult(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    GenericExtendedResult message;
    reader.readStartSequence(OP_TYPE_EXTENDED_RESPONSE);
    try
    {
      final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
      final String matchedDN = reader.readOctetStringAsString();
      final String diagnosticMessage = reader.readOctetStringAsString();
      message = Responses.newGenericExtendedResult(resultCode).setMatchedDN(
          matchedDN).setDiagnosticMessage(diagnosticMessage);
      decodeResponseReferrals(reader, message);
      if (reader.hasNextElement()
          && (reader.peekType() == TYPE_EXTENDED_RESPONSE_OID))
      {
        message.setOID(reader
            .readOctetStringAsString(TYPE_EXTENDED_RESPONSE_OID));
      }
      if (reader.hasNextElement()
          && (reader.peekType() == TYPE_EXTENDED_RESPONSE_VALUE))
      {
        message.setValue(reader.readOctetString(TYPE_EXTENDED_RESPONSE_VALUE));
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP EXTENDED RESULT(messageID=%d, result=%s)", messageID,
          message));
    }
    handler.extendedResult(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP intermediate
   * response protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeIntermediateResponse(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    GenericIntermediateResponse message;
    reader.readStartSequence(OP_TYPE_INTERMEDIATE_RESPONSE);
    try
    {
      message = Responses.newGenericIntermediateResponse();
      if (reader.hasNextElement()
          && (reader.peekType() == TYPE_INTERMEDIATE_RESPONSE_OID))
      {
        message.setOID(reader
            .readOctetStringAsString(TYPE_INTERMEDIATE_RESPONSE_OID));
      }
      if (reader.hasNextElement()
          && (reader.peekType() == TYPE_INTERMEDIATE_RESPONSE_VALUE))
      {
        message.setValue(reader
            .readOctetString(TYPE_INTERMEDIATE_RESPONSE_VALUE));
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP INTERMEDIATE RESPONSE(messageID=%d, response=%s)",
          messageID, message));
    }
    handler.intermediateResponse(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a modify DN request
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeModifyDNRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    ModifyDNRequest message;
    reader.readStartSequence(OP_TYPE_MODIFY_DN_REQUEST);
    try
    {
      final String dnString = reader.readOctetStringAsString();
      final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
      final DN dn = decodeDN(dnString, schema);
      final String newRDNString = reader.readOctetStringAsString();
      final RDN newRDN = decodeRDN(newRDNString, schema);
      message = Requests.newModifyDNRequest(dn, newRDN);
      message.setDeleteOldRDN(reader.readBoolean());
      if (reader.hasNextElement()
          && (reader.peekType() == TYPE_MODIFY_DN_NEW_SUPERIOR))
      {
        final String newSuperiorString = reader
            .readOctetStringAsString(TYPE_MODIFY_DN_NEW_SUPERIOR);
        final DN newSuperior = decodeDN(newSuperiorString, schema);
        message.setNewSuperior(newSuperior);
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP MODIFY DN REQUEST(messageID=%d, request=%s)", messageID,
          message));
    }
    handler.modifyDNRequest(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a modify DN response
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeModifyDNResult(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    Result message;
    reader.readStartSequence(OP_TYPE_MODIFY_DN_RESPONSE);
    try
    {
      final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
      final String matchedDN = reader.readOctetStringAsString();
      final String diagnosticMessage = reader.readOctetStringAsString();
      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
          .setDiagnosticMessage(diagnosticMessage);
      decodeResponseReferrals(reader, message);
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP MODIFY DN RESULT(messageID=%d, result=%s)", messageID,
          message));
    }
    handler.modifyDNResult(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP modify
   * request protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeModifyRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    ModifyRequest message;
    reader.readStartSequence(OP_TYPE_MODIFY_REQUEST);
    try
    {
      final String dnString = reader.readOctetStringAsString();
      final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
      final DN dn = decodeDN(dnString, schema);
      message = Requests.newModifyRequest(dn);
      reader.readStartSequence();
      try
      {
        while (reader.hasNextElement())
        {
          reader.readStartSequence();
          try
          {
            final int typeIntValue = reader.readEnumerated();
            final ModificationType type = ModificationType
                .valueOf(typeIntValue);
            if (type == null)
            {
              throw DecodeException
                  .error(ERR_LDAP_MODIFICATION_DECODE_INVALID_MOD_TYPE
                      .get(typeIntValue));
            }
            entry = options.getEntryFactory().newEntry(dn);
            reader.readStartSequence();
            try
            {
              final String ads = reader.readOctetStringAsString();
              final AttributeDescription ad = decodeAttributeDescription(ads,
                  schema);
              final Attribute attribute = options.getAttributeFactory()
                  .newAttribute(ad);
            try {
                while (reader.hasNextElement()) {
                    reader.readStartSequence();
                    try {
                        final String ads = reader.readOctetStringAsString();
                        AttributeDescription ad;
                        try {
                            ad = AttributeDescription.valueOf(ads, schema);
                        } catch (final LocalizedIllegalArgumentException e) {
                            throw DecodeException.error(e.getMessageObject());
                        }
              reader.readStartSet();
              try
              {
                while (reader.hasNextElement())
                {
                  attribute.add(reader.readOctetString());
                        final Attribute attribute = options.getAttributeFactory().newAttribute(ad);
                        reader.readStartSet();
                        try {
                            while (reader.hasNextElement()) {
                                attribute.add(reader.readOctetString());
                            }
                            entry.addAttribute(attribute);
                        } finally {
                            reader.readEndSet();
                        }
                    } finally {
                        reader.readEndSequence();
                    }
                }
                message.addModification(new Modification(type, attribute));
              }
              finally
              {
                reader.readEndSet();
              }
            } finally {
                reader.readEndSequence();
            }
            finally
            {
              reader.readEndSequence();
            }
          }
          finally
          {
        } finally {
            reader.readEndSequence();
          }
        }
      }
      finally
      {
        reader.readEndSequence();
      }
    }
    finally
    {
      reader.readEndSequence();
        return Responses.newSearchResultEntry(entry);
    }
    decodeControls(reader, message);
    private final DecodeOptions options;
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP MODIFY REQUEST(messageID=%d, request=%s)", messageID,
          message));
    LDAPReader(final DecodeOptions options) {
        this.options = options;
    }
    handler.modifyRequest(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a modify response
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeModifyResult(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    Result message;
    reader.readStartSequence(OP_TYPE_MODIFY_RESPONSE);
    try
    {
      final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
      final String matchedDN = reader.readOctetStringAsString();
      final String diagnosticMessage = reader.readOctetStringAsString();
      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
          .setDiagnosticMessage(diagnosticMessage);
      decodeResponseReferrals(reader, message);
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP MODIFY RESULT(messageID=%d, result=%s)", messageID,
          message));
    }
    handler.modifyResult(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeProtocolOp(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    final byte type = reader.peekType();
    switch (type)
    {
    case OP_TYPE_UNBIND_REQUEST: // 0x42
      decodeUnbindRequest(reader, messageID, handler, p);
      break;
    case 0x43: // 0x43
    case 0x44: // 0x44
    case 0x45: // 0x45
    case 0x46: // 0x46
    case 0x47: // 0x47
    case 0x48: // 0x48
    case 0x49: // 0x49
      handler.unrecognizedMessage(p, messageID, type, reader
          .readOctetString(type));
      break;
    case OP_TYPE_DELETE_REQUEST: // 0x4A
      decodeDeleteRequest(reader, messageID, handler, p);
      break;
    case 0x4B: // 0x4B
    case 0x4C: // 0x4C
    case 0x4D: // 0x4D
    case 0x4E: // 0x4E
    case 0x4F: // 0x4F
      handler.unrecognizedMessage(p, messageID, type, reader
          .readOctetString(type));
      break;
    case OP_TYPE_ABANDON_REQUEST: // 0x50
      decodeAbandonRequest(reader, messageID, handler, p);
      break;
    case 0x51: // 0x51
    case 0x52: // 0x52
    case 0x53: // 0x53
    case 0x54: // 0x54
    case 0x55: // 0x55
    case 0x56: // 0x56
    case 0x57: // 0x57
    case 0x58: // 0x58
    case 0x59: // 0x59
    case 0x5A: // 0x5A
    case 0x5B: // 0x5B
    case 0x5C: // 0x5C
    case 0x5D: // 0x5D
    case 0x5E: // 0x5E
    case 0x5F: // 0x5F
      handler.unrecognizedMessage(p, messageID, type, reader
          .readOctetString(type));
      break;
    case OP_TYPE_BIND_REQUEST: // 0x60
      decodeBindRequest(reader, messageID, handler, p);
      break;
    case OP_TYPE_BIND_RESPONSE: // 0x61
      decodeBindResult(reader, messageID, handler, p);
      break;
    case 0x62: // 0x62
      handler.unrecognizedMessage(p, messageID, type, reader
          .readOctetString(type));
      break;
    case OP_TYPE_SEARCH_REQUEST: // 0x63
      decodeSearchRequest(reader, messageID, handler, p);
      break;
    case OP_TYPE_SEARCH_RESULT_ENTRY: // 0x64
      decodeSearchResultEntry(reader, messageID, handler, p);
      break;
    case OP_TYPE_SEARCH_RESULT_DONE: // 0x65
      decodeSearchResult(reader, messageID, handler, p);
      break;
    case OP_TYPE_MODIFY_REQUEST: // 0x66
      decodeModifyRequest(reader, messageID, handler, p);
      break;
    case OP_TYPE_MODIFY_RESPONSE: // 0x67
      decodeModifyResult(reader, messageID, handler, p);
      break;
    case OP_TYPE_ADD_REQUEST: // 0x68
      decodeAddRequest(reader, messageID, handler, p);
      break;
    case OP_TYPE_ADD_RESPONSE: // 0x69
      decodeAddResult(reader, messageID, handler, p);
      break;
    case 0x6A: // 0x6A
      handler.unrecognizedMessage(p, messageID, type, reader
          .readOctetString(type));
      break;
    case OP_TYPE_DELETE_RESPONSE: // 0x6B
      decodeDeleteResult(reader, messageID, handler, p);
      break;
    case OP_TYPE_MODIFY_DN_REQUEST: // 0x6C
      decodeModifyDNRequest(reader, messageID, handler, p);
      break;
    case OP_TYPE_MODIFY_DN_RESPONSE: // 0x6D
      decodeModifyDNResult(reader, messageID, handler, p);
      break;
    case OP_TYPE_COMPARE_REQUEST: // 0x6E
      decodeCompareRequest(reader, messageID, handler, p);
      break;
    case OP_TYPE_COMPARE_RESPONSE: // 0x6F
      decodeCompareResult(reader, messageID, handler, p);
      break;
    case 0x70: // 0x70
    case 0x71: // 0x71
    case 0x72: // 0x72
      handler.unrecognizedMessage(p, messageID, type, reader
          .readOctetString(type));
      break;
    case OP_TYPE_SEARCH_RESULT_REFERENCE: // 0x73
      decodeSearchResultReference(reader, messageID, handler, p);
      break;
    case 0x74: // 0x74
    case 0x75: // 0x75
    case 0x76: // 0x76
      handler.unrecognizedMessage(p, messageID, type, reader
          .readOctetString(type));
      break;
    case OP_TYPE_EXTENDED_REQUEST: // 0x77
      decodeExtendedRequest(reader, messageID, handler, p);
      break;
    case OP_TYPE_EXTENDED_RESPONSE: // 0x78
      decodeExtendedResult(reader, messageID, handler, p);
      break;
    case OP_TYPE_INTERMEDIATE_RESPONSE: // 0x79
      decodeIntermediateResponse(reader, messageID, handler, p);
      break;
    default:
      handler.unrecognizedMessage(p, messageID, type, reader
          .readOctetString(type));
      break;
    }
  }
  private RDN decodeRDN(final String rdn, final Schema schema)
      throws DecodeException
  {
    try
    {
      return RDN.valueOf(rdn, schema);
    }
    catch (final LocalizedIllegalArgumentException e)
    {
      throw DecodeException.error(e.getMessageObject());
    }
  }
  private void decodeResponseReferrals(final ASN1Reader reader,
      final Result message) throws IOException
  {
    if (reader.hasNextElement()
        && (reader.peekType() == TYPE_REFERRAL_SEQUENCE))
    {
      reader.readStartSequence(TYPE_REFERRAL_SEQUENCE);
      try
      {
        // Should have at least 1.
        do
        {
          message.addReferralURI((reader.readOctetStringAsString()));
        }
        while (reader.hasNextElement());
      }
      finally
      {
        reader.readEndSequence();
      }
    }
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP search
   * request protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeSearchRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    SearchRequest message;
    reader.readStartSequence(OP_TYPE_SEARCH_REQUEST);
    try
    {
      final String baseDNString = reader.readOctetStringAsString();
      final Schema schema = options.getSchemaResolver().resolveSchema(
          baseDNString);
      final DN baseDN = decodeDN(baseDNString, schema);
      final int scopeIntValue = reader.readEnumerated();
      final SearchScope scope = SearchScope.valueOf(scopeIntValue);
      if (scope == null)
      {
        throw DecodeException
            .error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_SCOPE
                .get(scopeIntValue));
      }
      final int dereferencePolicyIntValue = reader.readEnumerated();
      final DereferenceAliasesPolicy dereferencePolicy = DereferenceAliasesPolicy
          .valueOf(dereferencePolicyIntValue);
      if (dereferencePolicy == null)
      {
        throw DecodeException
            .error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_DEREF
                .get(dereferencePolicyIntValue));
      }
      final int sizeLimit = (int) reader.readInteger();
      final int timeLimit = (int) reader.readInteger();
      final boolean typesOnly = reader.readBoolean();
      final Filter filter = LDAPUtils.decodeFilter(reader);
      message = Requests.newSearchRequest(baseDN, scope, filter);
      message.setDereferenceAliasesPolicy(dereferencePolicy);
      try
      {
        message.setTimeLimit(timeLimit);
        message.setSizeLimit(sizeLimit);
      }
      catch (final LocalizedIllegalArgumentException e)
      {
        throw DecodeException.error(e.getMessageObject());
      }
      message.setTypesOnly(typesOnly);
      reader.readStartSequence();
      try
      {
        while (reader.hasNextElement())
        {
          message.addAttribute(reader.readOctetStringAsString());
        }
      }
      finally
      {
        reader.readEndSequence();
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP SEARCH REQUEST(messageID=%d, request=%s)", messageID,
          message));
    }
    handler.searchRequest(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as a search result done
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeSearchResult(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    Result message;
    reader.readStartSequence(OP_TYPE_SEARCH_RESULT_DONE);
    try
    {
      final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
      final String matchedDN = reader.readOctetStringAsString();
      final String diagnosticMessage = reader.readOctetStringAsString();
      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
          .setDiagnosticMessage(diagnosticMessage);
      decodeResponseReferrals(reader, message);
    }
    finally
    {
      reader.readEndSequence();
    }
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP SEARCH RESULT(messageID=%d, result=%s)", messageID,
          message));
    }
    handler.searchResult(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 reader as an LDAP search
   * result entry protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeSearchResultEntry(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    Entry entry;
    reader.readStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
    try
    {
      final String dnString = reader.readOctetStringAsString();
      final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
      final DN dn = decodeDN(dnString, schema);
      entry = options.getEntryFactory().newEntry(dn);
      reader.readStartSequence();
      try
      {
        while (reader.hasNextElement())
        {
          reader.readStartSequence();
          try
          {
            final String ads = reader.readOctetStringAsString();
            final AttributeDescription ad = decodeAttributeDescription(ads,
                schema);
            final Attribute attribute = options.getAttributeFactory()
                .newAttribute(ad);
            reader.readStartSet();
            try
            {
              while (reader.hasNextElement())
              {
                attribute.add(reader.readOctetString());
              }
              entry.addAttribute(attribute);
            }
            finally
            {
              reader.readEndSet();
            }
          }
          finally
          {
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP message.
     *
     * @param <P>
     *            The type of {@code param}.
     * @param reader
     *            The ASN.1 reader.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle a decoded
     *            message.
     * @param param
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    <P> void decode(final ASN1Reader reader, final LDAPMessageHandler<P> handler, final P param)
            throws IOException {
        reader.readStartSequence();
        try {
            final int messageID = (int) reader.readInteger();
            decodeProtocolOp(reader, messageID, handler, param);
        } finally {
            reader.readEndSequence();
          }
        }
      }
      finally
      {
        reader.readEndSequence();
      }
    }
    finally
    {
      reader.readEndSequence();
    }
    final SearchResultEntry message = Responses.newSearchResultEntry(entry);
    decodeControls(reader, message);
    /**
     * Decodes the elements from the provided ASN.1 read as an LDAP abandon
     * request protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeAbandonRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        final int msgToAbandon = (int) reader.readInteger(OP_TYPE_ABANDON_REQUEST);
        final AbandonRequest message = Requests.newAbandonRequest(msgToAbandon);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP SEARCH RESULT ENTRY(messageID=%d, entry=%s)", messageID,
          message));
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP ABANDON REQUEST(messageID=%d, request=%s)", messageID, message));
        }
        handler.abandonRequest(p, messageID, message);
    }
    handler.searchResultEntry(p, messageID, message);
  }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP add
     * request protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeAddRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        Entry entry;
        reader.readStartSequence(OP_TYPE_ADD_REQUEST);
        try {
            final String dnString = reader.readOctetStringAsString();
            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
            final DN dn = decodeDN(dnString, schema);
            entry = options.getEntryFactory().newEntry(dn);
            reader.readStartSequence();
            try {
                while (reader.hasNextElement()) {
                    reader.readStartSequence();
                    try {
                        final String ads = reader.readOctetStringAsString();
                        final AttributeDescription ad = decodeAttributeDescription(ads, schema);
                        final Attribute attribute = options.getAttributeFactory().newAttribute(ad);
  /**
   * Decodes the elements from the provided ASN.1 reader as a search result
   * reference protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeSearchResultReference(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    SearchResultReference message;
                        reader.readStartSet();
                        try {
                            while (reader.hasNextElement()) {
                                attribute.add(reader.readOctetString());
                            }
                            entry.addAttribute(attribute);
                        } finally {
                            reader.readEndSet();
                        }
                    } finally {
                        reader.readEndSequence();
                    }
                }
            } finally {
                reader.readEndSequence();
            }
        } finally {
            reader.readEndSequence();
        }
    reader.readStartSequence(OP_TYPE_SEARCH_RESULT_REFERENCE);
    try
    {
      message = Responses.newSearchResultReference(reader
          .readOctetStringAsString());
      while (reader.hasNextElement())
      {
        message.addURI(reader.readOctetStringAsString());
      }
    }
    finally
    {
      reader.readEndSequence();
        final AddRequest message = Requests.newAddRequest(entry);
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP ADD REQUEST(messageID=%d, request=%s)", messageID, message));
        }
        handler.addRequest(p, messageID, message);
    }
    decodeControls(reader, message);
    /**
     * Decodes the elements from the provided ASN.1 reader as an add response
     * protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeAddResult(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        Result message;
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP SEARCH RESULT REFERENCE(messageID=%d, reference=%s)",
          messageID, message));
        reader.readStartSequence(OP_TYPE_ADD_RESPONSE);
        try {
            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
            final String matchedDN = reader.readOctetStringAsString();
            final String diagnosticMessage = reader.readOctetStringAsString();
            message =
                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                            diagnosticMessage);
            decodeResponseReferrals(reader, message);
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP ADD RESULT(messageID=%d, result=%s)", messageID, message));
        }
        handler.addResult(p, messageID, message);
    }
    handler.searchResultReference(p, messageID, message);
  }
  /**
   * Decodes the elements from the provided ASN.1 read as an LDAP unbind request
   * protocol op.
   *
   * @param reader
   *          The ASN.1 reader.
   * @param messageID
   *          The decoded message ID for this message.
   * @param handler
   *          The <code>LDAPMessageHandler</code> that will handle this decoded
   *          message.
   * @param p
   *          The parameter to pass into the <code>LDAPMessageHandler</code>
   * @throws IOException
   *           If an error occurred while reading bytes to decode.
   */
  private <P> void decodeUnbindRequest(final ASN1Reader reader,
      final int messageID, final LDAPMessageHandler<P> handler, final P p)
      throws IOException
  {
    UnbindRequest message;
    reader.readNull(OP_TYPE_UNBIND_REQUEST);
    message = Requests.newUnbindRequest();
    decodeControls(reader, message);
    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
    {
      StaticUtils.DEBUG_LOG.finer(String.format(
          "DECODE LDAP UNBIND REQUEST(messageID=%d, request=%s)", messageID,
          message));
    private AttributeDescription decodeAttributeDescription(final String attributeDescription,
            final Schema schema) throws DecodeException {
        try {
            return AttributeDescription.valueOf(attributeDescription, schema);
        } catch (final LocalizedIllegalArgumentException e) {
            throw DecodeException.error(e.getMessageObject());
        }
    }
    handler.unbindRequest(p, messageID, message);
  }
    /**
     * Decodes the elements from the provided ASN.1 read as an LDAP bind request
     * protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeBindRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        reader.readStartSequence(OP_TYPE_BIND_REQUEST);
        try {
            final int protocolVersion = (int) reader.readInteger();
            final String authName = reader.readOctetStringAsString();
            final byte authType = reader.peekType();
            final byte[] authBytes = reader.readOctetString(authType).toByteArray();
            final GenericBindRequest request =
                    Requests.newGenericBindRequest(authName, authType, authBytes);
            decodeControls(reader, request);
            if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
                StaticUtils.DEBUG_LOG.finer(String.format(
                        "DECODE LDAP BIND REQUEST(messageID=%d, auth=0x%x, request=%s)", messageID,
                        request.getAuthenticationType(), request));
            }
            handler.bindRequest(p, messageID, protocolVersion, request);
        } finally {
            reader.readEndSequence();
        }
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a bind response
     * protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeBindResult(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        BindResult message;
        reader.readStartSequence(OP_TYPE_BIND_RESPONSE);
        try {
            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
            final String matchedDN = reader.readOctetStringAsString();
            final String diagnosticMessage = reader.readOctetStringAsString();
            message =
                    Responses.newBindResult(resultCode).setMatchedDN(matchedDN)
                            .setDiagnosticMessage(diagnosticMessage);
            decodeResponseReferrals(reader, message);
            if (reader.hasNextElement() && (reader.peekType() == TYPE_SERVER_SASL_CREDENTIALS)) {
                message.setServerSASLCredentials(reader
                        .readOctetString(TYPE_SERVER_SASL_CREDENTIALS));
            }
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP BIND RESULT(messageID=%d, result=%s)", messageID, message));
        }
        handler.bindResult(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP compare
     * request protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeCompareRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        CompareRequest message;
        reader.readStartSequence(OP_TYPE_COMPARE_REQUEST);
        try {
            final String dnString = reader.readOctetStringAsString();
            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
            final DN dn = decodeDN(dnString, schema);
            reader.readStartSequence();
            try {
                final String ads = reader.readOctetStringAsString();
                final AttributeDescription ad = decodeAttributeDescription(ads, schema);
                final ByteString assertionValue = reader.readOctetString();
                message = Requests.newCompareRequest(dn, ad, assertionValue);
            } finally {
                reader.readEndSequence();
            }
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP COMPARE REQUEST(messageID=%d, request=%s)", messageID, message));
        }
        handler.compareRequest(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a compare response
     * protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeCompareResult(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        CompareResult message;
        reader.readStartSequence(OP_TYPE_COMPARE_RESPONSE);
        try {
            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
            final String matchedDN = reader.readOctetStringAsString();
            final String diagnosticMessage = reader.readOctetStringAsString();
            message =
                    Responses.newCompareResult(resultCode).setMatchedDN(matchedDN)
                            .setDiagnosticMessage(diagnosticMessage);
            decodeResponseReferrals(reader, message);
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP COMPARE RESULT(messageID=%d, result=%s)", messageID, message));
        }
        handler.compareResult(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP control.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param request
     *            The decoded request to decode controls for.
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private void decodeControl(final ASN1Reader reader, final Request request) throws IOException {
        String oid;
        boolean isCritical;
        ByteString value;
        reader.readStartSequence();
        try {
            oid = reader.readOctetStringAsString();
            isCritical = false;
            value = null;
            if (reader.hasNextElement() && (reader.peekType() == UNIVERSAL_BOOLEAN_TYPE)) {
                isCritical = reader.readBoolean();
            }
            if (reader.hasNextElement() && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE)) {
                value = reader.readOctetString();
            }
        } finally {
            reader.readEndSequence();
        }
        final Control c = GenericControl.newControl(oid, isCritical, value);
        request.addControl(c);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP control.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param response
     *            The decoded message to decode controls for.
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private void decodeControl(final ASN1Reader reader, final Response response) throws IOException {
        String oid;
        boolean isCritical;
        ByteString value;
        reader.readStartSequence();
        try {
            oid = reader.readOctetStringAsString();
            isCritical = false;
            value = null;
            if (reader.hasNextElement() && (reader.peekType() == UNIVERSAL_BOOLEAN_TYPE)) {
                isCritical = reader.readBoolean();
            }
            if (reader.hasNextElement() && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE)) {
                value = reader.readOctetString();
            }
        } finally {
            reader.readEndSequence();
        }
        final Control c = GenericControl.newControl(oid, isCritical, value);
        response.addControl(c);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a set of controls.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param request
     *            The decoded message to decode controls for.
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private void decodeControls(final ASN1Reader reader, final Request request) throws IOException {
        if (reader.hasNextElement() && (reader.peekType() == TYPE_CONTROL_SEQUENCE)) {
            reader.readStartSequence(TYPE_CONTROL_SEQUENCE);
            try {
                while (reader.hasNextElement()) {
                    decodeControl(reader, request);
                }
            } finally {
                reader.readEndSequence();
            }
        }
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a set of controls.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param response
     *            The decoded message to decode controls for.
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private void decodeControls(final ASN1Reader reader, final Response response)
            throws IOException {
        if (reader.hasNextElement() && (reader.peekType() == TYPE_CONTROL_SEQUENCE)) {
            reader.readStartSequence(TYPE_CONTROL_SEQUENCE);
            try {
                while (reader.hasNextElement()) {
                    decodeControl(reader, response);
                }
            } finally {
                reader.readEndSequence();
            }
        }
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP delete
     * request protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeDeleteRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        final String dnString = reader.readOctetStringAsString(OP_TYPE_DELETE_REQUEST);
        final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
        final DN dn = decodeDN(dnString, schema);
        final DeleteRequest message = Requests.newDeleteRequest(dn);
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP DELETE REQUEST(messageID=%d, request=%s)", messageID, message));
        }
        handler.deleteRequest(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a delete response
     * protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeDeleteResult(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        Result message;
        reader.readStartSequence(OP_TYPE_DELETE_RESPONSE);
        try {
            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
            final String matchedDN = reader.readOctetStringAsString();
            final String diagnosticMessage = reader.readOctetStringAsString();
            message =
                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                            diagnosticMessage);
            decodeResponseReferrals(reader, message);
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP DELETE RESULT(messageID=%d, result=%s)", messageID, message));
        }
        handler.deleteResult(p, messageID, message);
    }
    private DN decodeDN(final String dn, final Schema schema) throws DecodeException {
        try {
            return DN.valueOf(dn, schema);
        } catch (final LocalizedIllegalArgumentException e) {
            throw DecodeException.error(e.getMessageObject());
        }
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP extended
     * request protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeExtendedRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        String oid;
        ByteString value;
        reader.readStartSequence(OP_TYPE_EXTENDED_REQUEST);
        try {
            oid = reader.readOctetStringAsString(TYPE_EXTENDED_REQUEST_OID);
            value = null;
            if (reader.hasNextElement() && (reader.peekType() == TYPE_EXTENDED_REQUEST_VALUE)) {
                value = reader.readOctetString(TYPE_EXTENDED_REQUEST_VALUE);
            }
        } finally {
            reader.readEndSequence();
        }
        final GenericExtendedRequest message = Requests.newGenericExtendedRequest(oid, value);
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP EXTENDED REQUEST(messageID=%d, request=%s)", messageID, message));
        }
        handler.extendedRequest(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a extended
     * response protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeExtendedResult(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        GenericExtendedResult message;
        reader.readStartSequence(OP_TYPE_EXTENDED_RESPONSE);
        try {
            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
            final String matchedDN = reader.readOctetStringAsString();
            final String diagnosticMessage = reader.readOctetStringAsString();
            message =
                    Responses.newGenericExtendedResult(resultCode).setMatchedDN(matchedDN)
                            .setDiagnosticMessage(diagnosticMessage);
            decodeResponseReferrals(reader, message);
            if (reader.hasNextElement() && (reader.peekType() == TYPE_EXTENDED_RESPONSE_OID)) {
                message.setOID(reader.readOctetStringAsString(TYPE_EXTENDED_RESPONSE_OID));
            }
            if (reader.hasNextElement() && (reader.peekType() == TYPE_EXTENDED_RESPONSE_VALUE)) {
                message.setValue(reader.readOctetString(TYPE_EXTENDED_RESPONSE_VALUE));
            }
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP EXTENDED RESULT(messageID=%d, result=%s)", messageID, message));
        }
        handler.extendedResult(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP
     * intermediate response protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeIntermediateResponse(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        GenericIntermediateResponse message;
        reader.readStartSequence(OP_TYPE_INTERMEDIATE_RESPONSE);
        try {
            message = Responses.newGenericIntermediateResponse();
            if (reader.hasNextElement() && (reader.peekType() == TYPE_INTERMEDIATE_RESPONSE_OID)) {
                message.setOID(reader.readOctetStringAsString(TYPE_INTERMEDIATE_RESPONSE_OID));
            }
            if (reader.hasNextElement() && (reader.peekType() == TYPE_INTERMEDIATE_RESPONSE_VALUE)) {
                message.setValue(reader.readOctetString(TYPE_INTERMEDIATE_RESPONSE_VALUE));
            }
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP INTERMEDIATE RESPONSE(messageID=%d, response=%s)", messageID,
                    message));
        }
        handler.intermediateResponse(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a modify DN
     * request protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeModifyDNRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        ModifyDNRequest message;
        reader.readStartSequence(OP_TYPE_MODIFY_DN_REQUEST);
        try {
            final String dnString = reader.readOctetStringAsString();
            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
            final DN dn = decodeDN(dnString, schema);
            final String newRDNString = reader.readOctetStringAsString();
            final RDN newRDN = decodeRDN(newRDNString, schema);
            message = Requests.newModifyDNRequest(dn, newRDN);
            message.setDeleteOldRDN(reader.readBoolean());
            if (reader.hasNextElement() && (reader.peekType() == TYPE_MODIFY_DN_NEW_SUPERIOR)) {
                final String newSuperiorString =
                        reader.readOctetStringAsString(TYPE_MODIFY_DN_NEW_SUPERIOR);
                final DN newSuperior = decodeDN(newSuperiorString, schema);
                message.setNewSuperior(newSuperior);
            }
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP MODIFY DN REQUEST(messageID=%d, request=%s)", messageID, message));
        }
        handler.modifyDNRequest(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a modify DN
     * response protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeModifyDNResult(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        Result message;
        reader.readStartSequence(OP_TYPE_MODIFY_DN_RESPONSE);
        try {
            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
            final String matchedDN = reader.readOctetStringAsString();
            final String diagnosticMessage = reader.readOctetStringAsString();
            message =
                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                            diagnosticMessage);
            decodeResponseReferrals(reader, message);
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP MODIFY DN RESULT(messageID=%d, result=%s)", messageID, message));
        }
        handler.modifyDNResult(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP modify
     * request protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeModifyRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        ModifyRequest message;
        reader.readStartSequence(OP_TYPE_MODIFY_REQUEST);
        try {
            final String dnString = reader.readOctetStringAsString();
            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
            final DN dn = decodeDN(dnString, schema);
            message = Requests.newModifyRequest(dn);
            reader.readStartSequence();
            try {
                while (reader.hasNextElement()) {
                    reader.readStartSequence();
                    try {
                        final int typeIntValue = reader.readEnumerated();
                        final ModificationType type = ModificationType.valueOf(typeIntValue);
                        if (type == null) {
                            throw DecodeException
                                    .error(ERR_LDAP_MODIFICATION_DECODE_INVALID_MOD_TYPE
                                            .get(typeIntValue));
                        }
                        reader.readStartSequence();
                        try {
                            final String ads = reader.readOctetStringAsString();
                            final AttributeDescription ad = decodeAttributeDescription(ads, schema);
                            final Attribute attribute =
                                    options.getAttributeFactory().newAttribute(ad);
                            reader.readStartSet();
                            try {
                                while (reader.hasNextElement()) {
                                    attribute.add(reader.readOctetString());
                                }
                                message.addModification(new Modification(type, attribute));
                            } finally {
                                reader.readEndSet();
                            }
                        } finally {
                            reader.readEndSequence();
                        }
                    } finally {
                        reader.readEndSequence();
                    }
                }
            } finally {
                reader.readEndSequence();
            }
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP MODIFY REQUEST(messageID=%d, request=%s)", messageID, message));
        }
        handler.modifyRequest(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a modify response
     * protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeModifyResult(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        Result message;
        reader.readStartSequence(OP_TYPE_MODIFY_RESPONSE);
        try {
            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
            final String matchedDN = reader.readOctetStringAsString();
            final String diagnosticMessage = reader.readOctetStringAsString();
            message =
                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                            diagnosticMessage);
            decodeResponseReferrals(reader, message);
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP MODIFY RESULT(messageID=%d, result=%s)", messageID, message));
        }
        handler.modifyResult(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP protocol
     * op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeProtocolOp(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        final byte type = reader.peekType();
        switch (type) {
        case OP_TYPE_UNBIND_REQUEST: // 0x42
            decodeUnbindRequest(reader, messageID, handler, p);
            break;
        case 0x43: // 0x43
        case 0x44: // 0x44
        case 0x45: // 0x45
        case 0x46: // 0x46
        case 0x47: // 0x47
        case 0x48: // 0x48
        case 0x49: // 0x49
            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
            break;
        case OP_TYPE_DELETE_REQUEST: // 0x4A
            decodeDeleteRequest(reader, messageID, handler, p);
            break;
        case 0x4B: // 0x4B
        case 0x4C: // 0x4C
        case 0x4D: // 0x4D
        case 0x4E: // 0x4E
        case 0x4F: // 0x4F
            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
            break;
        case OP_TYPE_ABANDON_REQUEST: // 0x50
            decodeAbandonRequest(reader, messageID, handler, p);
            break;
        case 0x51: // 0x51
        case 0x52: // 0x52
        case 0x53: // 0x53
        case 0x54: // 0x54
        case 0x55: // 0x55
        case 0x56: // 0x56
        case 0x57: // 0x57
        case 0x58: // 0x58
        case 0x59: // 0x59
        case 0x5A: // 0x5A
        case 0x5B: // 0x5B
        case 0x5C: // 0x5C
        case 0x5D: // 0x5D
        case 0x5E: // 0x5E
        case 0x5F: // 0x5F
            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
            break;
        case OP_TYPE_BIND_REQUEST: // 0x60
            decodeBindRequest(reader, messageID, handler, p);
            break;
        case OP_TYPE_BIND_RESPONSE: // 0x61
            decodeBindResult(reader, messageID, handler, p);
            break;
        case 0x62: // 0x62
            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
            break;
        case OP_TYPE_SEARCH_REQUEST: // 0x63
            decodeSearchRequest(reader, messageID, handler, p);
            break;
        case OP_TYPE_SEARCH_RESULT_ENTRY: // 0x64
            decodeSearchResultEntry(reader, messageID, handler, p);
            break;
        case OP_TYPE_SEARCH_RESULT_DONE: // 0x65
            decodeSearchResult(reader, messageID, handler, p);
            break;
        case OP_TYPE_MODIFY_REQUEST: // 0x66
            decodeModifyRequest(reader, messageID, handler, p);
            break;
        case OP_TYPE_MODIFY_RESPONSE: // 0x67
            decodeModifyResult(reader, messageID, handler, p);
            break;
        case OP_TYPE_ADD_REQUEST: // 0x68
            decodeAddRequest(reader, messageID, handler, p);
            break;
        case OP_TYPE_ADD_RESPONSE: // 0x69
            decodeAddResult(reader, messageID, handler, p);
            break;
        case 0x6A: // 0x6A
            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
            break;
        case OP_TYPE_DELETE_RESPONSE: // 0x6B
            decodeDeleteResult(reader, messageID, handler, p);
            break;
        case OP_TYPE_MODIFY_DN_REQUEST: // 0x6C
            decodeModifyDNRequest(reader, messageID, handler, p);
            break;
        case OP_TYPE_MODIFY_DN_RESPONSE: // 0x6D
            decodeModifyDNResult(reader, messageID, handler, p);
            break;
        case OP_TYPE_COMPARE_REQUEST: // 0x6E
            decodeCompareRequest(reader, messageID, handler, p);
            break;
        case OP_TYPE_COMPARE_RESPONSE: // 0x6F
            decodeCompareResult(reader, messageID, handler, p);
            break;
        case 0x70: // 0x70
        case 0x71: // 0x71
        case 0x72: // 0x72
            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
            break;
        case OP_TYPE_SEARCH_RESULT_REFERENCE: // 0x73
            decodeSearchResultReference(reader, messageID, handler, p);
            break;
        case 0x74: // 0x74
        case 0x75: // 0x75
        case 0x76: // 0x76
            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
            break;
        case OP_TYPE_EXTENDED_REQUEST: // 0x77
            decodeExtendedRequest(reader, messageID, handler, p);
            break;
        case OP_TYPE_EXTENDED_RESPONSE: // 0x78
            decodeExtendedResult(reader, messageID, handler, p);
            break;
        case OP_TYPE_INTERMEDIATE_RESPONSE: // 0x79
            decodeIntermediateResponse(reader, messageID, handler, p);
            break;
        default:
            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
            break;
        }
    }
    private RDN decodeRDN(final String rdn, final Schema schema) throws DecodeException {
        try {
            return RDN.valueOf(rdn, schema);
        } catch (final LocalizedIllegalArgumentException e) {
            throw DecodeException.error(e.getMessageObject());
        }
    }
    private void decodeResponseReferrals(final ASN1Reader reader, final Result message)
            throws IOException {
        if (reader.hasNextElement() && (reader.peekType() == TYPE_REFERRAL_SEQUENCE)) {
            reader.readStartSequence(TYPE_REFERRAL_SEQUENCE);
            try {
                // Should have at least 1.
                do {
                    message.addReferralURI((reader.readOctetStringAsString()));
                } while (reader.hasNextElement());
            } finally {
                reader.readEndSequence();
            }
        }
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP search
     * request protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeSearchRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        SearchRequest message;
        reader.readStartSequence(OP_TYPE_SEARCH_REQUEST);
        try {
            final String baseDNString = reader.readOctetStringAsString();
            final Schema schema = options.getSchemaResolver().resolveSchema(baseDNString);
            final DN baseDN = decodeDN(baseDNString, schema);
            final int scopeIntValue = reader.readEnumerated();
            final SearchScope scope = SearchScope.valueOf(scopeIntValue);
            if (scope == null) {
                throw DecodeException.error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_SCOPE
                        .get(scopeIntValue));
            }
            final int dereferencePolicyIntValue = reader.readEnumerated();
            final DereferenceAliasesPolicy dereferencePolicy =
                    DereferenceAliasesPolicy.valueOf(dereferencePolicyIntValue);
            if (dereferencePolicy == null) {
                throw DecodeException.error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_DEREF
                        .get(dereferencePolicyIntValue));
            }
            final int sizeLimit = (int) reader.readInteger();
            final int timeLimit = (int) reader.readInteger();
            final boolean typesOnly = reader.readBoolean();
            final Filter filter = LDAPUtils.decodeFilter(reader);
            message = Requests.newSearchRequest(baseDN, scope, filter);
            message.setDereferenceAliasesPolicy(dereferencePolicy);
            try {
                message.setTimeLimit(timeLimit);
                message.setSizeLimit(sizeLimit);
            } catch (final LocalizedIllegalArgumentException e) {
                throw DecodeException.error(e.getMessageObject());
            }
            message.setTypesOnly(typesOnly);
            reader.readStartSequence();
            try {
                while (reader.hasNextElement()) {
                    message.addAttribute(reader.readOctetStringAsString());
                }
            } finally {
                reader.readEndSequence();
            }
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP SEARCH REQUEST(messageID=%d, request=%s)", messageID, message));
        }
        handler.searchRequest(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a search result
     * done protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeSearchResult(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        Result message;
        reader.readStartSequence(OP_TYPE_SEARCH_RESULT_DONE);
        try {
            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
            final String matchedDN = reader.readOctetStringAsString();
            final String diagnosticMessage = reader.readOctetStringAsString();
            message =
                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                            diagnosticMessage);
            decodeResponseReferrals(reader, message);
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP SEARCH RESULT(messageID=%d, result=%s)", messageID, message));
        }
        handler.searchResult(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as an LDAP search
     * result entry protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeSearchResultEntry(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        Entry entry;
        reader.readStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
        try {
            final String dnString = reader.readOctetStringAsString();
            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
            final DN dn = decodeDN(dnString, schema);
            entry = options.getEntryFactory().newEntry(dn);
            reader.readStartSequence();
            try {
                while (reader.hasNextElement()) {
                    reader.readStartSequence();
                    try {
                        final String ads = reader.readOctetStringAsString();
                        final AttributeDescription ad = decodeAttributeDescription(ads, schema);
                        final Attribute attribute = options.getAttributeFactory().newAttribute(ad);
                        reader.readStartSet();
                        try {
                            while (reader.hasNextElement()) {
                                attribute.add(reader.readOctetString());
                            }
                            entry.addAttribute(attribute);
                        } finally {
                            reader.readEndSet();
                        }
                    } finally {
                        reader.readEndSequence();
                    }
                }
            } finally {
                reader.readEndSequence();
            }
        } finally {
            reader.readEndSequence();
        }
        final SearchResultEntry message = Responses.newSearchResultEntry(entry);
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP SEARCH RESULT ENTRY(messageID=%d, entry=%s)", messageID, message));
        }
        handler.searchResultEntry(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 reader as a search result
     * reference protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeSearchResultReference(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        SearchResultReference message;
        reader.readStartSequence(OP_TYPE_SEARCH_RESULT_REFERENCE);
        try {
            message = Responses.newSearchResultReference(reader.readOctetStringAsString());
            while (reader.hasNextElement()) {
                message.addURI(reader.readOctetStringAsString());
            }
        } finally {
            reader.readEndSequence();
        }
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP SEARCH RESULT REFERENCE(messageID=%d, reference=%s)", messageID,
                    message));
        }
        handler.searchResultReference(p, messageID, message);
    }
    /**
     * Decodes the elements from the provided ASN.1 read as an LDAP unbind
     * request protocol op.
     *
     * @param reader
     *            The ASN.1 reader.
     * @param messageID
     *            The decoded message ID for this message.
     * @param handler
     *            The <code>LDAPMessageHandler</code> that will handle this
     *            decoded message.
     * @param p
     *            The parameter to pass into the <code>LDAPMessageHandler</code>
     * @throws IOException
     *             If an error occurred while reading bytes to decode.
     */
    private <P> void decodeUnbindRequest(final ASN1Reader reader, final int messageID,
            final LDAPMessageHandler<P> handler, final P p) throws IOException {
        UnbindRequest message;
        reader.readNull(OP_TYPE_UNBIND_REQUEST);
        message = Requests.newUnbindRequest();
        decodeControls(reader, message);
        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER)) {
            StaticUtils.DEBUG_LOG.finer(String.format(
                    "DECODE LDAP UNBIND REQUEST(messageID=%d, request=%s)", messageID, message));
        }
        handler.unbindRequest(p, messageID, message);
    }
}