From 38fe921bd2d09734277828b638a4947e45a99bcd Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Fri, 25 Oct 2013 09:44:02 +0000
Subject: [PATCH] Checkpoint commit for OPENDJ-175: Decouple OpenDJ LDAP SDK from Grizzly

---
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnexpectedRequestException.java        |   25 
 opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferReader.java                |   11 
 opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java   |    2 
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/package-info.java                            |   31 
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/LDAPReader.java                              |  452 +++---
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLDAPMessageHandler.java        |  102 
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPMessageHandler.java                |  392 +++++
 opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java        |    4 
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnexpectedResponseException.java       |   23 
 opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferWriter.java                |   28 
 opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPServerFilter.java                |  621 ++++---
 opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPClientFilter.java                |  775 +++++-----
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/LDAPWriter.java                              |  724 ++++++++++
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnsupportedMessageException.java       |   30 
 opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java |   61 
 /dev/null                                                                                              |  481 ------
 opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPListener.java             |    2 
 opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java    |    7 
 opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java           |  123 +
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/TimeoutChecker.java                        |   56 
 opendj3/opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java                |  178 ++
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/TimeoutEventListener.java                  |   56 
 opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyUtils.java                    |   62 
 23 files changed, 2,755 insertions(+), 1,491 deletions(-)

diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPReader.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/LDAPReader.java
similarity index 73%
rename from opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPReader.java
rename to opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/LDAPReader.java
index 6e129f3..5323d2b 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPReader.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/LDAPReader.java
@@ -25,14 +25,11 @@
  *      Portions copyright 2011-2013 ForgeRock AS
  */
 
-package com.forgerock.opendj.grizzly;
+package org.forgerock.opendj.io;
 
+import static com.forgerock.opendj.ldap.CoreMessages.*;
 import static com.forgerock.opendj.ldap.LDAPConstants.*;
-import static com.forgerock.opendj.ldap.CoreMessages.ERR_LDAP_MODIFICATION_DECODE_INVALID_MOD_TYPE;
-import static com.forgerock.opendj.ldap.CoreMessages.ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_DEREF;
-import static com.forgerock.opendj.ldap.CoreMessages.ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_SCOPE;
-import static com.forgerock.opendj.util.StaticUtils.IO_LOG;
-import static com.forgerock.opendj.util.StaticUtils.byteToHex;
+import static com.forgerock.opendj.util.StaticUtils.*;
 
 import java.io.IOException;
 
@@ -77,73 +74,99 @@
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldap.responses.SearchResultReference;
 import org.forgerock.opendj.ldap.schema.Schema;
+import org.forgerock.opendj.ldap.spi.LDAPMessageHandler;
 
 import com.forgerock.opendj.ldap.LDAPUtils;
 
 /**
- * Static methods for decoding LDAP messages.
+ * Responsible for reading LDAP messages.
+ *
+ * @param <R>
+ *            type of ASN1 reader used to decode elements
  */
-final class LDAPReader {
+public final class LDAPReader<R extends ASN1Reader> {
 
+    private final R reader;
 
     private final DecodeOptions options;
 
-    LDAPReader(final DecodeOptions options) {
+    /**
+     * Creates a reader with the provided ASN1 reader and decoding options.
+     *
+     * @param asn1Reader
+     *            reader capable of decoding ASN1 elements
+     * @param options
+     *            allow to control how responses and requests are decoded
+     */
+    public LDAPReader(final R asn1Reader, final DecodeOptions options) {
+        this.reader = asn1Reader;
         this.options = options;
     }
 
     /**
-     * Decodes the elements from the provided ASN.1 reader as an LDAP message.
+     * Returns the ASN1 reader used to decode elements.
      *
-     * @param <P>
-     *            The type of {@code param}.
-     * @param reader
-     *            The ASN.1 reader.
+     * @return ASN1 reader
+     */
+    public R getASN1Reader() {
+        return reader;
+    }
+
+    /**
+     * Read a LDAP message by decoding the elements from the provided ASN.1 asn1Reader.
+     *
      * @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)
+    public void readMessage(final LDAPMessageHandler handler)
             throws IOException {
         reader.readStartSequence();
         try {
             final int messageID = (int) reader.readInteger();
-            decodeProtocolOp(reader, messageID, handler, param);
+            readProtocolOp(messageID, handler);
         } finally {
             reader.readEndSequence();
         }
     }
 
     /**
+     * Indicates whether or not the next message can be read without blocking.
+     *
+     * @return {@code true} if a complete element is available or {@code false}
+     *         otherwise.
+     * @throws DecodeException
+     *             If the available data was not valid ASN.1.
+     * @throws IOException
+     *             If an unexpected IO error occurred.
+     */
+    public boolean hasMessageAvailable() throws DecodeException, IOException {
+        return reader.elementAvailable();
+    }
+
+    /**
      * 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 {
+    private void readAbandonRequest(final int messageID, final LDAPMessageHandler handler) throws IOException {
         final int msgToAbandon = (int) reader.readInteger(OP_TYPE_ABANDON_REQUEST);
         final AbandonRequest message = Requests.newAbandonRequest(msgToAbandon);
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP ABANDON REQUEST(messageID={}, request={})", messageID, message);
 
-        handler.abandonRequest(p, messageID, message);
+        handler.abandonRequest(messageID, message);
     }
 
     /**
@@ -151,26 +174,24 @@
      * request protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readAddRequest(final int messageID,
+            final LDAPMessageHandler handler) 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);
+            final DN dn = readDN(dnString, schema);
             entry = options.getEntryFactory().newEntry(dn);
 
             reader.readStartSequence();
@@ -179,7 +200,7 @@
                     reader.readStartSequence();
                     try {
                         final String ads = reader.readOctetStringAsString();
-                        final AttributeDescription ad = decodeAttributeDescription(ads, schema);
+                        final AttributeDescription ad = readAttributeDescription(ads, schema);
                         final Attribute attribute = options.getAttributeFactory().newAttribute(ad);
 
                         reader.readStartSet();
@@ -203,11 +224,11 @@
         }
 
         final AddRequest message = Requests.newAddRequest(entry);
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP ADD REQUEST(messageID={}, request={})", messageID, message);
 
-        handler.addRequest(p, messageID, message);
+        handler.addRequest(messageID, message);
     }
 
     /**
@@ -215,19 +236,17 @@
      * protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readAddResult(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         Result message;
 
         reader.readStartSequence(OP_TYPE_ADD_RESPONSE);
@@ -238,19 +257,19 @@
             message =
                     Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                             diagnosticMessage);
-            decodeResponseReferrals(reader, message);
+            readResponseReferrals(message);
         } finally {
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP ADD RESULT(messageID={}, result={})", messageID, message);
 
-        handler.addResult(p, messageID, message);
+        handler.addResult(messageID, message);
     }
 
-    private AttributeDescription decodeAttributeDescription(final String attributeDescription,
+    private AttributeDescription readAttributeDescription(final String attributeDescription,
             final Schema schema) throws DecodeException {
         try {
             return AttributeDescription.valueOf(attributeDescription, schema);
@@ -264,19 +283,17 @@
      * protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readBindRequest(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         reader.readStartSequence(OP_TYPE_BIND_REQUEST);
         try {
             final int protocolVersion = (int) reader.readInteger();
@@ -287,12 +304,12 @@
             final GenericBindRequest request =
                     Requests.newGenericBindRequest(authName, authType, authBytes);
 
-            decodeControls(reader, request);
+            readControls(request);
 
             IO_LOG.trace("DECODE LDAP BIND REQUEST(messageID={}, auth=0x{}, request={})", messageID,
                     byteToHex(request.getAuthenticationType()), request);
 
-            handler.bindRequest(p, messageID, protocolVersion, request);
+            handler.bindRequest(messageID, protocolVersion, request);
         } finally {
             reader.readEndSequence();
         }
@@ -303,19 +320,17 @@
      * protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readBindResult(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         BindResult message;
 
         reader.readStartSequence(OP_TYPE_BIND_RESPONSE);
@@ -326,7 +341,7 @@
             message =
                     Responses.newBindResult(resultCode).setMatchedDN(matchedDN)
                             .setDiagnosticMessage(diagnosticMessage);
-            decodeResponseReferrals(reader, message);
+            readResponseReferrals(message);
             if (reader.hasNextElement() && (reader.peekType() == TYPE_SERVER_SASL_CREDENTIALS)) {
                 message.setServerSASLCredentials(reader
                         .readOctetString(TYPE_SERVER_SASL_CREDENTIALS));
@@ -335,11 +350,11 @@
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP BIND RESULT(messageID={}, result={})", messageID, message);
 
-        handler.bindResult(p, messageID, message);
+        handler.bindResult(messageID, message);
     }
 
     /**
@@ -347,31 +362,29 @@
      * request protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readCompareRequest(final int messageID,
+            final LDAPMessageHandler handler) 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);
+            final DN dn = readDN(dnString, schema);
 
             reader.readStartSequence();
             try {
                 final String ads = reader.readOctetStringAsString();
-                final AttributeDescription ad = decodeAttributeDescription(ads, schema);
+                final AttributeDescription ad = readAttributeDescription(ads, schema);
                 final ByteString assertionValue = reader.readOctetString();
                 message = Requests.newCompareRequest(dn, ad, assertionValue);
             } finally {
@@ -381,11 +394,11 @@
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP COMPARE REQUEST(messageID={}, request={})", messageID, message);
 
-        handler.compareRequest(p, messageID, message);
+        handler.compareRequest(messageID, message);
     }
 
     /**
@@ -393,19 +406,17 @@
      * protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readCompareResult(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         CompareResult message;
 
         reader.readStartSequence(OP_TYPE_COMPARE_RESPONSE);
@@ -416,29 +427,29 @@
             message =
                     Responses.newCompareResult(resultCode).setMatchedDN(matchedDN)
                             .setDiagnosticMessage(diagnosticMessage);
-            decodeResponseReferrals(reader, message);
+            readResponseReferrals(message);
         } finally {
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP COMPARE RESULT(messageID={}, result={})", messageID, message);
 
-        handler.compareResult(p, messageID, message);
+        handler.compareResult(messageID, message);
     }
 
     /**
      * Decodes the elements from the provided ASN.1 reader as an LDAP control.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readControl(final Request request) throws IOException {
         String oid;
         boolean isCritical;
         ByteString value;
@@ -466,13 +477,13 @@
      * Decodes the elements from the provided ASN.1 reader as an LDAP control.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readControl(final Response response) throws IOException {
         String oid;
         boolean isCritical;
         ByteString value;
@@ -500,18 +511,18 @@
      * Decodes the elements from the provided ASN.1 reader as a set of controls.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readControls(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);
+                    readControl(request);
                 }
             } finally {
                 reader.readEndSequence();
@@ -523,19 +534,19 @@
      * Decodes the elements from the provided ASN.1 reader as a set of controls.
      *
      * @param reader
-     *            The ASN.1 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)
+    private void readControls(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);
+                    readControl(response);
                 }
             } finally {
                 reader.readEndSequence();
@@ -548,29 +559,27 @@
      * request protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readDeleteRequest(final int messageID,
+            final LDAPMessageHandler handler) 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 DN dn = readDN(dnString, schema);
         final DeleteRequest message = Requests.newDeleteRequest(dn);
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP DELETE REQUEST(messageID={}, request={})", messageID, message);
 
-        handler.deleteRequest(p, messageID, message);
+        handler.deleteRequest(messageID, message);
     }
 
     /**
@@ -578,19 +587,17 @@
      * protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readDeleteResult(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         Result message;
 
         reader.readStartSequence(OP_TYPE_DELETE_RESPONSE);
@@ -601,19 +608,19 @@
             message =
                     Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                             diagnosticMessage);
-            decodeResponseReferrals(reader, message);
+            readResponseReferrals(message);
         } finally {
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP DELETE RESULT(messageID={}, result={})", messageID, message);
 
-        handler.deleteResult(p, messageID, message);
+        handler.deleteResult(messageID, message);
     }
 
-    private DN decodeDN(final String dn, final Schema schema) throws DecodeException {
+    private DN readDN(final String dn, final Schema schema) throws DecodeException {
         try {
             return DN.valueOf(dn, schema);
         } catch (final LocalizedIllegalArgumentException e) {
@@ -626,19 +633,17 @@
      * request protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readExtendedRequest(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         String oid;
         ByteString value;
 
@@ -655,11 +660,11 @@
 
         final GenericExtendedRequest message = Requests.newGenericExtendedRequest(oid, value);
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP EXTENDED REQUEST(messageID={}, request={})", messageID, message);
 
-        handler.extendedRequest(p, messageID, message);
+        handler.extendedRequest(messageID, message);
     }
 
     /**
@@ -667,19 +672,17 @@
      * response protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readExtendedResult(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
 
         GenericExtendedResult message;
 
@@ -691,7 +694,7 @@
             message =
                     Responses.newGenericExtendedResult(resultCode).setMatchedDN(matchedDN)
                             .setDiagnosticMessage(diagnosticMessage);
-            decodeResponseReferrals(reader, message);
+            readResponseReferrals(message);
             if (reader.hasNextElement() && (reader.peekType() == TYPE_EXTENDED_RESPONSE_OID)) {
                 message.setOID(reader.readOctetStringAsString(TYPE_EXTENDED_RESPONSE_OID));
             }
@@ -702,11 +705,11 @@
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP EXTENDED RESULT(messageID={}, result={})", messageID, message);
 
-        handler.extendedResult(p, messageID, message);
+        handler.extendedResult(messageID, message);
     }
 
     /**
@@ -714,19 +717,17 @@
      * intermediate response protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readIntermediateResponse(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         GenericIntermediateResponse message;
 
         reader.readStartSequence(OP_TYPE_INTERMEDIATE_RESPONSE);
@@ -742,12 +743,12 @@
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP INTERMEDIATE RESPONSE(messageID={}, response={})",
                 messageID, message);
 
-        handler.intermediateResponse(p, messageID, message);
+        handler.intermediateResponse(messageID, message);
     }
 
     /**
@@ -755,29 +756,27 @@
      * request protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readModifyDNRequest(final int messageID,
+            final LDAPMessageHandler handler) 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 DN dn = readDN(dnString, schema);
 
             final String newRDNString = reader.readOctetStringAsString();
-            final RDN newRDN = decodeRDN(newRDNString, schema);
+            final RDN newRDN = readRDN(newRDNString, schema);
 
             message = Requests.newModifyDNRequest(dn, newRDN);
 
@@ -786,18 +785,18 @@
             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);
+                final DN newSuperior = readDN(newSuperiorString, schema);
                 message.setNewSuperior(newSuperior);
             }
         } finally {
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP MODIFY DN REQUEST(messageID={}, request={})", messageID, message);
 
-        handler.modifyDNRequest(p, messageID, message);
+        handler.modifyDNRequest(messageID, message);
     }
 
     /**
@@ -805,19 +804,17 @@
      * response protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readModifyDNResult(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         Result message;
 
         reader.readStartSequence(OP_TYPE_MODIFY_DN_RESPONSE);
@@ -828,16 +825,16 @@
             message =
                     Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                             diagnosticMessage);
-            decodeResponseReferrals(reader, message);
+            readResponseReferrals(message);
         } finally {
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE MODIFY DN RESULT(messageID={}, result={})", messageID, message);
 
-        handler.modifyDNResult(p, messageID, message);
+        handler.modifyDNResult(messageID, message);
     }
 
     /**
@@ -845,26 +842,24 @@
      * request protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readModifyRequest(final int messageID,
+            final LDAPMessageHandler handler) 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);
+            final DN dn = readDN(dnString, schema);
             message = Requests.newModifyRequest(dn);
 
             reader.readStartSequence();
@@ -882,7 +877,7 @@
                         reader.readStartSequence();
                         try {
                             final String ads = reader.readOctetStringAsString();
-                            final AttributeDescription ad = decodeAttributeDescription(ads, schema);
+                            final AttributeDescription ad = readAttributeDescription(ads, schema);
                             final Attribute attribute =
                                     options.getAttributeFactory().newAttribute(ad);
 
@@ -909,11 +904,11 @@
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP MODIFY REQUEST(messageID={}, request={})", messageID, message);
 
-        handler.modifyRequest(p, messageID, message);
+        handler.modifyRequest(messageID, message);
     }
 
     /**
@@ -921,19 +916,17 @@
      * protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readModifyResult(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         Result message;
 
         reader.readStartSequence(OP_TYPE_MODIFY_RESPONSE);
@@ -944,16 +937,16 @@
             message =
                     Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                             diagnosticMessage);
-            decodeResponseReferrals(reader, message);
+            readResponseReferrals(message);
         } finally {
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP MODIFY RESULT(messageID={}, result={})", messageID, message);
 
-        handler.modifyResult(p, messageID, message);
+        handler.modifyResult(messageID, message);
     }
 
     /**
@@ -961,24 +954,22 @@
      * op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readProtocolOp(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         final byte type = reader.peekType();
 
         switch (type) {
         case OP_TYPE_UNBIND_REQUEST: // 0x42
-            decodeUnbindRequest(reader, messageID, handler, p);
+            readUnbindRequest(messageID, handler);
             break;
         case 0x43: // 0x43
         case 0x44: // 0x44
@@ -987,20 +978,20 @@
         case 0x47: // 0x47
         case 0x48: // 0x48
         case 0x49: // 0x49
-            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
+            handler.unrecognizedMessage(messageID, type, reader.readOctetString(type));
             break;
         case OP_TYPE_DELETE_REQUEST: // 0x4A
-            decodeDeleteRequest(reader, messageID, handler, p);
+            readDeleteRequest(messageID, handler);
             break;
         case 0x4B: // 0x4B
         case 0x4C: // 0x4C
         case 0x4D: // 0x4D
         case 0x4E: // 0x4E
         case 0x4F: // 0x4F
-            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
+            handler.unrecognizedMessage(messageID, type, reader.readOctetString(type));
             break;
         case OP_TYPE_ABANDON_REQUEST: // 0x50
-            decodeAbandonRequest(reader, messageID, handler, p);
+            readAbandonRequest(messageID, handler);
             break;
         case 0x51: // 0x51
         case 0x52: // 0x52
@@ -1017,85 +1008,85 @@
         case 0x5D: // 0x5D
         case 0x5E: // 0x5E
         case 0x5F: // 0x5F
-            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
+            handler.unrecognizedMessage(messageID, type, reader.readOctetString(type));
             break;
         case OP_TYPE_BIND_REQUEST: // 0x60
-            decodeBindRequest(reader, messageID, handler, p);
+            readBindRequest(messageID, handler);
             break;
         case OP_TYPE_BIND_RESPONSE: // 0x61
-            decodeBindResult(reader, messageID, handler, p);
+            readBindResult(messageID, handler);
             break;
         case 0x62: // 0x62
-            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
+            handler.unrecognizedMessage(messageID, type, reader.readOctetString(type));
             break;
         case OP_TYPE_SEARCH_REQUEST: // 0x63
-            decodeSearchRequest(reader, messageID, handler, p);
+            readSearchRequest(messageID, handler);
             break;
         case OP_TYPE_SEARCH_RESULT_ENTRY: // 0x64
-            decodeSearchResultEntry(reader, messageID, handler, p);
+            readSearchResultEntry(messageID, handler);
             break;
         case OP_TYPE_SEARCH_RESULT_DONE: // 0x65
-            decodeSearchResult(reader, messageID, handler, p);
+            readSearchResult(messageID, handler);
             break;
         case OP_TYPE_MODIFY_REQUEST: // 0x66
-            decodeModifyRequest(reader, messageID, handler, p);
+            readModifyRequest(messageID, handler);
             break;
         case OP_TYPE_MODIFY_RESPONSE: // 0x67
-            decodeModifyResult(reader, messageID, handler, p);
+            readModifyResult(messageID, handler);
             break;
         case OP_TYPE_ADD_REQUEST: // 0x68
-            decodeAddRequest(reader, messageID, handler, p);
+            readAddRequest(messageID, handler);
             break;
         case OP_TYPE_ADD_RESPONSE: // 0x69
-            decodeAddResult(reader, messageID, handler, p);
+            readAddResult(messageID, handler);
             break;
         case 0x6A: // 0x6A
-            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
+            handler.unrecognizedMessage(messageID, type, reader.readOctetString(type));
             break;
         case OP_TYPE_DELETE_RESPONSE: // 0x6B
-            decodeDeleteResult(reader, messageID, handler, p);
+            readDeleteResult(messageID, handler);
             break;
         case OP_TYPE_MODIFY_DN_REQUEST: // 0x6C
-            decodeModifyDNRequest(reader, messageID, handler, p);
+            readModifyDNRequest(messageID, handler);
             break;
         case OP_TYPE_MODIFY_DN_RESPONSE: // 0x6D
-            decodeModifyDNResult(reader, messageID, handler, p);
+            readModifyDNResult(messageID, handler);
             break;
         case OP_TYPE_COMPARE_REQUEST: // 0x6E
-            decodeCompareRequest(reader, messageID, handler, p);
+            readCompareRequest(messageID, handler);
             break;
         case OP_TYPE_COMPARE_RESPONSE: // 0x6F
-            decodeCompareResult(reader, messageID, handler, p);
+            readCompareResult(messageID, handler);
             break;
         case 0x70: // 0x70
         case 0x71: // 0x71
         case 0x72: // 0x72
-            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
+            handler.unrecognizedMessage(messageID, type, reader.readOctetString(type));
             break;
         case OP_TYPE_SEARCH_RESULT_REFERENCE: // 0x73
-            decodeSearchResultReference(reader, messageID, handler, p);
+            readSearchResultReference(messageID, handler);
             break;
         case 0x74: // 0x74
         case 0x75: // 0x75
         case 0x76: // 0x76
-            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
+            handler.unrecognizedMessage(messageID, type, reader.readOctetString(type));
             break;
         case OP_TYPE_EXTENDED_REQUEST: // 0x77
-            decodeExtendedRequest(reader, messageID, handler, p);
+            readExtendedRequest(messageID, handler);
             break;
         case OP_TYPE_EXTENDED_RESPONSE: // 0x78
-            decodeExtendedResult(reader, messageID, handler, p);
+            readExtendedResult(messageID, handler);
             break;
         case OP_TYPE_INTERMEDIATE_RESPONSE: // 0x79
-            decodeIntermediateResponse(reader, messageID, handler, p);
+            readIntermediateResponse(messageID, handler);
             break;
         default:
-            handler.unrecognizedMessage(p, messageID, type, reader.readOctetString(type));
+            handler.unrecognizedMessage(messageID, type, reader.readOctetString(type));
             break;
         }
     }
 
-    private RDN decodeRDN(final String rdn, final Schema schema) throws DecodeException {
+    private RDN readRDN(final String rdn, final Schema schema) throws DecodeException {
         try {
             return RDN.valueOf(rdn, schema);
         } catch (final LocalizedIllegalArgumentException e) {
@@ -1103,7 +1094,7 @@
         }
     }
 
-    private void decodeResponseReferrals(final ASN1Reader reader, final Result message)
+    private void readResponseReferrals(final Result message)
             throws IOException {
         if (reader.hasNextElement() && (reader.peekType() == TYPE_REFERRAL_SEQUENCE)) {
             reader.readStartSequence(TYPE_REFERRAL_SEQUENCE);
@@ -1123,26 +1114,24 @@
      * request protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readSearchRequest(final int messageID,
+            final LDAPMessageHandler handler) 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 DN baseDN = readDN(baseDNString, schema);
 
             final int scopeIntValue = reader.readEnumerated();
             final SearchScope scope = SearchScope.valueOf(scopeIntValue);
@@ -1186,11 +1175,11 @@
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP SEARCH REQUEST(messageID={}, request={})", messageID, message);
 
-        handler.searchRequest(p, messageID, message);
+        handler.searchRequest(messageID, message);
     }
 
     /**
@@ -1198,19 +1187,17 @@
      * done protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readSearchResult(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
 
         Result message;
 
@@ -1222,16 +1209,16 @@
             message =
                     Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
                             diagnosticMessage);
-            decodeResponseReferrals(reader, message);
+            readResponseReferrals(message);
         } finally {
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP SEARCH RESULT(messageID={}, result={})", messageID, message);
 
-        handler.searchResult(p, messageID, message);
+        handler.searchResult(messageID, message);
     }
 
     /**
@@ -1239,26 +1226,25 @@
      * result entry protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readSearchResultEntry(final int messageID,
+            final LDAPMessageHandler handler) 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);
+            final DN dn = readDN(dnString, schema);
             entry = options.getEntryFactory().newEntry(dn);
 
             reader.readStartSequence();
@@ -1267,7 +1253,7 @@
                     reader.readStartSequence();
                     try {
                         final String ads = reader.readOctetStringAsString();
-                        final AttributeDescription ad = decodeAttributeDescription(ads, schema);
+                        final AttributeDescription ad = readAttributeDescription(ads, schema);
                         final Attribute attribute = options.getAttributeFactory().newAttribute(ad);
 
                         reader.readStartSet();
@@ -1291,11 +1277,11 @@
         }
 
         final SearchResultEntry message = Responses.newSearchResultEntry(entry);
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP SEARCH RESULT ENTRY(messageID={}, entry={})", messageID, message);
 
-        handler.searchResultEntry(p, messageID, message);
+        handler.searchResultEntry(messageID, message);
     }
 
     /**
@@ -1303,19 +1289,17 @@
      * reference protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readSearchResultReference(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         SearchResultReference message;
 
         reader.readStartSequence(OP_TYPE_SEARCH_RESULT_REFERENCE);
@@ -1328,12 +1312,12 @@
             reader.readEndSequence();
         }
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP SEARCH RESULT REFERENCE(messageID={}, result={})",
                 messageID, message);
 
-        handler.searchResultReference(p, messageID, message);
+        handler.searchResultReference(messageID, message);
     }
 
     /**
@@ -1341,27 +1325,25 @@
      * request protocol op.
      *
      * @param reader
-     *            The ASN.1 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 {
+    private void readUnbindRequest(final int messageID,
+            final LDAPMessageHandler handler) throws IOException {
         UnbindRequest message;
         reader.readNull(OP_TYPE_UNBIND_REQUEST);
         message = Requests.newUnbindRequest();
 
-        decodeControls(reader, message);
+        readControls(message);
 
         IO_LOG.trace("DECODE LDAP UNBIND REQUEST(messageID={}, request={})", messageID, message);
 
-        handler.unbindRequest(p, messageID, message);
+        handler.unbindRequest(messageID, message);
     }
 }
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/LDAPWriter.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/LDAPWriter.java
new file mode 100644
index 0000000..802524f
--- /dev/null
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/LDAPWriter.java
@@ -0,0 +1,724 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * 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 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
+ *
+ *
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
+ *      Portions copyright 2011-2013 ForgeRock AS
+ */
+
+package org.forgerock.opendj.io;
+
+import static com.forgerock.opendj.ldap.LDAPConstants.*;
+import static com.forgerock.opendj.util.StaticUtils.IO_LOG;
+import static com.forgerock.opendj.util.StaticUtils.byteToHex;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.forgerock.opendj.asn1.ASN1Writer;
+import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Modification;
+import org.forgerock.opendj.ldap.controls.Control;
+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.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.GenericBindRequest;
+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.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.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.IntermediateResponse;
+import org.forgerock.opendj.ldap.responses.Response;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.forgerock.opendj.ldap.responses.SearchResultReference;
+
+import com.forgerock.opendj.ldap.LDAPUtils;
+import com.forgerock.opendj.util.StaticUtils;
+
+/**
+ * Responsible for writing LDAP messages.
+ *
+ * @param <W>
+ *            type of ASN1 writer used to encode elements
+ */
+public final class LDAPWriter<W extends ASN1Writer> {
+
+    private final W writer;
+
+    /**
+     * Creates a writer based on the provided ASN1 writer.
+     *
+     * @param asn1Writer
+     *            writer to encode ASN.1 elements
+     */
+    public LDAPWriter(W asn1Writer) {
+        this.writer = asn1Writer;
+    }
+
+    /**
+     * Returns the ASN1 writer used to encode elements.
+     *
+     * @return the ASN1 writer
+     */
+    public W getASN1Writer() {
+        return writer;
+    }
+
+    /**
+     * Write the provided control.
+     *
+     * @param control
+     *            the control
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeControl(final Control control)
+            throws IOException {
+        writer.writeStartSequence();
+        writer.writeOctetString(control.getOID());
+        if (control.isCritical()) {
+            writer.writeBoolean(control.isCritical());
+        }
+        if (control.getValue() != null) {
+            writer.writeOctetString(control.getValue());
+        }
+        writer.writeEndSequence();
+    }
+
+    /**
+     * Write the provided entry.
+     *
+     * @param searchResultEntry
+     *            entry
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeEntry(final SearchResultEntry searchResultEntry) throws IOException {
+        writer.writeStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
+        writer.writeOctetString(searchResultEntry.getName().toString());
+
+        writer.writeStartSequence();
+        for (final Attribute attr : searchResultEntry.getAllAttributes()) {
+            writeAttribute(attr);
+        }
+        writer.writeEndSequence();
+        writer.writeEndSequence();
+    }
+
+    private void writeAttribute(final Attribute attribute)
+            throws IOException {
+        writer.writeStartSequence();
+        writer.writeOctetString(attribute.getAttributeDescriptionAsString());
+
+        writer.writeStartSet();
+        for (final ByteString value : attribute) {
+            writer.writeOctetString(value);
+        }
+        writer.writeEndSequence();
+
+        writer.writeEndSequence();
+    }
+
+    private void writeChange(final Modification change)
+            throws IOException {
+        writer.writeStartSequence();
+        writer.writeEnumerated(change.getModificationType().intValue());
+        writeAttribute(change.getAttribute());
+        writer.writeEndSequence();
+    }
+
+    private void writeMessageFooter(final Request request)
+            throws IOException {
+        final List<Control> controls = request.getControls();
+        if (!controls.isEmpty()) {
+            writer.writeStartSequence(TYPE_CONTROL_SEQUENCE);
+            for (final Control control : controls) {
+                writeControl(control);
+            }
+            writer.writeEndSequence();
+        }
+
+        writer.writeEndSequence();
+    }
+
+    private void writeMessageFooter(final Response response)
+            throws IOException {
+        final List<Control> controls = response.getControls();
+        if (!controls.isEmpty()) {
+            writer.writeStartSequence(TYPE_CONTROL_SEQUENCE);
+            for (final Control control : controls) {
+                writeControl(control);
+            }
+            writer.writeEndSequence();
+        }
+
+        writer.writeEndSequence();
+    }
+
+    private void writeMessageHeader(final int messageID)
+            throws IOException {
+        writer.writeStartSequence();
+        writer.writeInteger(messageID);
+    }
+
+    private void writeResultFooter(final ASN1Writer writer) throws IOException {
+        writer.writeEndSequence();
+    }
+
+    private void writeResultHeader(final byte typeTag,
+            final Result rawMessage) throws IOException {
+        writer.writeStartSequence(typeTag);
+        writer.writeEnumerated(rawMessage.getResultCode().intValue());
+        writer.writeOctetString(rawMessage.getMatchedDN());
+        writer.writeOctetString(rawMessage.getDiagnosticMessage());
+
+        final List<String> referralURIs = rawMessage.getReferralURIs();
+        if (!referralURIs.isEmpty()) {
+            writer.writeStartSequence(TYPE_REFERRAL_SEQUENCE);
+            for (final String s : referralURIs) {
+                writer.writeOctetString(s);
+            }
+            writer.writeEndSequence();
+        }
+    }
+
+    /**
+     * Write the provided abandon request.
+     *
+     * @param messageID
+     *            identifier of the request message
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeAbandonRequest(final int messageID, final AbandonRequest request) throws IOException {
+        IO_LOG.trace("ENCODE LDAP ABANDON REQUEST(messageID={}, request={})", messageID, request);
+        writeMessageHeader(messageID);
+        writer.writeInteger(OP_TYPE_ABANDON_REQUEST, request.getRequestID());
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write the provided add request.
+     *
+     * @param messageID
+     *            identifier of the request message
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeAddRequest(final int messageID, final AddRequest request)
+            throws IOException {
+        IO_LOG.trace("ENCODE LDAP ADD REQUEST(messageID={}, request={})", messageID, request);
+        writeMessageHeader(messageID);
+        writer.writeStartSequence(OP_TYPE_ADD_REQUEST);
+        writer.writeOctetString(request.getName().toString());
+
+        // Write the attributes
+        writer.writeStartSequence();
+        for (final Attribute attr : request.getAllAttributes()) {
+            writeAttribute(attr);
+        }
+        writer.writeEndSequence();
+
+        writer.writeEndSequence();
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write the provided add result.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param result
+     *            the result
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeAddResult(final int messageID, final Result result)
+            throws IOException {
+        IO_LOG.trace("ENCODE LDAP ADD RESULT(messageID={}, result={})", messageID, result);
+        writeMessageHeader(messageID);
+        writeResultHeader(OP_TYPE_ADD_RESPONSE, result);
+        writeResultFooter(writer);
+        writeMessageFooter(result);
+    }
+
+    /**
+     * Write the provided bind request.
+     *
+     * @param messageID
+     *            identifier of the request message
+     * @param version
+     *            version of the protocol
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeBindRequest(final int messageID, final int version,
+            final GenericBindRequest request) throws IOException {
+        IO_LOG.trace("ENCODE LDAP BIND REQUEST(messageID={}, auth=0x{}, request={})",
+                messageID, byteToHex(request.getAuthenticationType()), request);
+        writeMessageHeader(messageID);
+        writer.writeStartSequence(OP_TYPE_BIND_REQUEST);
+
+        writer.writeInteger(version);
+        writer.writeOctetString(request.getName());
+        writer.writeOctetString(request.getAuthenticationType(), request.getAuthenticationValue());
+
+        writer.writeEndSequence();
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write the provided bind result.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param result
+     *            the result
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeBindResult(final int messageID, final BindResult result)
+            throws IOException {
+        IO_LOG.trace("ENCODE LDAP BIND RESULT(messageID={}, result={})", messageID, result);
+        writeMessageHeader(messageID);
+        writeResultHeader(OP_TYPE_BIND_RESPONSE, result);
+
+        final ByteString saslCredentials = result.getServerSASLCredentials();
+        if (saslCredentials != null && saslCredentials.length() > 0) {
+            writer.writeOctetString(TYPE_SERVER_SASL_CREDENTIALS, result.getServerSASLCredentials());
+        }
+
+        writeResultFooter(writer);
+        writeMessageFooter(result);
+    }
+
+    /**
+     * Write the provided compare request.
+     *
+     * @param messageID
+     *            identifier of the request message
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeCompareRequest(final int messageID,
+            final CompareRequest request) throws IOException {
+        IO_LOG.trace("ENCODE LDAP COMPARE REQUEST(messageID={}, request={})", messageID, request);
+        writeMessageHeader(messageID);
+        writer.writeStartSequence(OP_TYPE_COMPARE_REQUEST);
+        writer.writeOctetString(request.getName().toString());
+
+        writer.writeStartSequence();
+        writer.writeOctetString(request.getAttributeDescription().toString());
+        writer.writeOctetString(request.getAssertionValue());
+        writer.writeEndSequence();
+
+        writer.writeEndSequence();
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write the provided compare result.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param result
+     *            the result
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeCompareResult(final int messageID,
+            final CompareResult result) throws IOException {
+        IO_LOG.trace("ENCODE LDAP COMPARE RESULT(messageID={}, result={})", messageID, result);
+        writeMessageHeader(messageID);
+        writeResultHeader(OP_TYPE_COMPARE_RESPONSE, result);
+        writeResultFooter(writer);
+        writeMessageFooter(result);
+    }
+
+    /**
+     * Write the provided delete request.
+     *
+     * @param messageID
+     *            identifier of the request message
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeDeleteRequest(final int messageID,
+            final DeleteRequest request) throws IOException {
+        IO_LOG.trace("ENCODE LDAP DELETE REQUEST(messageID={}, request={})", messageID, request);
+        writeMessageHeader(messageID);
+        writer.writeOctetString(OP_TYPE_DELETE_REQUEST, request.getName().toString());
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write the provided delete result.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param result
+     *            the result
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeDeleteResult(final int messageID, final Result result)
+            throws IOException {
+        IO_LOG.trace("ENCODE LDAP DELETE RESULT(messageID={}, result={})", messageID, result);
+        writeMessageHeader(messageID);
+        writeResultHeader(OP_TYPE_DELETE_RESPONSE, result);
+        writeResultFooter(writer);
+        writeMessageFooter(result);
+    }
+
+    /**
+     * Write the provided extended request.
+     *
+     * @param <R>
+     *            type of extended result returned by the request
+     * @param messageID
+     *            identifier of the request message
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public <R extends ExtendedResult> void writeExtendedRequest(final int messageID,
+            final ExtendedRequest<R> request) throws IOException {
+        IO_LOG.trace("ENCODE LDAP EXTENDED REQUEST(messageID={}, request={})", messageID, request);
+        writeMessageHeader(messageID);
+        writer.writeStartSequence(OP_TYPE_EXTENDED_REQUEST);
+        writer.writeOctetString(TYPE_EXTENDED_REQUEST_OID, request.getOID());
+
+        final ByteString requestValue = request.getValue();
+        if (requestValue != null) {
+            writer.writeOctetString(TYPE_EXTENDED_REQUEST_VALUE, requestValue);
+        }
+
+        writer.writeEndSequence();
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write the provided extended result.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param result
+     *            the result
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeExtendedResult(final int messageID,
+            final ExtendedResult result) throws IOException {
+        IO_LOG.trace("ENCODE LDAP EXTENDED RESULT(messageID={}, result={})", messageID, result);
+        writeMessageHeader(messageID);
+        writeResultHeader(OP_TYPE_EXTENDED_RESPONSE, result);
+
+        final String responseName = result.getOID();
+        final ByteString responseValue = result.getValue();
+
+        if (responseName != null) {
+            writer.writeOctetString(TYPE_EXTENDED_RESPONSE_OID, responseName);
+        }
+
+        if (responseValue != null) {
+            writer.writeOctetString(TYPE_EXTENDED_RESPONSE_VALUE, responseValue);
+        }
+
+        writeResultFooter(writer);
+        writeMessageFooter(result);
+    }
+
+    /**
+     * Write the provided intermediate response.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param response
+     *            the response
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeIntermediateResponse(final int messageID,
+            final IntermediateResponse response) throws IOException {
+        IO_LOG.trace("ENCODE LDAP INTERMEDIATE RESPONSE(messageID={}, response={})", messageID, response);
+        writeMessageHeader(messageID);
+        writer.writeStartSequence(OP_TYPE_INTERMEDIATE_RESPONSE);
+
+        final String responseName = response.getOID();
+        final ByteString responseValue = response.getValue();
+
+        if (responseName != null) {
+            writer.writeOctetString(TYPE_INTERMEDIATE_RESPONSE_OID, response.getOID());
+        }
+
+        if (responseValue != null) {
+            writer.writeOctetString(TYPE_INTERMEDIATE_RESPONSE_VALUE, response.getValue());
+        }
+
+        writer.writeEndSequence();
+        writeMessageFooter(response);
+    }
+
+    /**
+     * Write the provided modify DN request.
+     *
+     * @param messageID
+     *            identifier of the request message
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeModifyDNRequest(final int messageID,
+            final ModifyDNRequest request) throws IOException {
+        IO_LOG.trace("ENCODE LDAP MODIFY DN REQUEST(messageID={}, request={})", messageID, request);
+        writeMessageHeader(messageID);
+        writer.writeStartSequence(OP_TYPE_MODIFY_DN_REQUEST);
+        writer.writeOctetString(request.getName().toString());
+        writer.writeOctetString(request.getNewRDN().toString());
+        writer.writeBoolean(request.isDeleteOldRDN());
+
+        final DN newSuperior = request.getNewSuperior();
+        if (newSuperior != null) {
+            writer.writeOctetString(TYPE_MODIFY_DN_NEW_SUPERIOR, newSuperior.toString());
+        }
+
+        writer.writeEndSequence();
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write the provided modify DN result.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param result
+     *            the result
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeModifyDNResult(final int messageID, final Result result)
+            throws IOException {
+        IO_LOG.trace("ENCODE LDAP MODIFY DN RESULT(messageID={}, result={})", messageID, result);
+        writeMessageHeader(messageID);
+        writeResultHeader(OP_TYPE_MODIFY_DN_RESPONSE, result);
+        writeResultFooter(writer);
+        writeMessageFooter(result);
+    }
+
+    /**
+     * Write the provided modify request.
+     *
+     * @param messageID
+     *            identifier of the request message
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeModifyRequest(final int messageID,
+            final ModifyRequest request) throws IOException {
+        IO_LOG.trace("ENCODE LDAP MODIFY REQUEST(messageID={}, request={})", messageID, request);
+        writeMessageHeader(messageID);
+        writer.writeStartSequence(OP_TYPE_MODIFY_REQUEST);
+        writer.writeOctetString(request.getName().toString());
+
+        writer.writeStartSequence();
+        for (final Modification change : request.getModifications()) {
+            writeChange(change);
+        }
+        writer.writeEndSequence();
+
+        writer.writeEndSequence();
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write the provided extended result.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param result
+     *            the result
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeModifyResult(final int messageID, final Result result)
+            throws IOException {
+        IO_LOG.trace("ENCODE LDAP MODIFY RESULT(messageID={}, result={})", messageID, result);
+        writeMessageHeader(messageID);
+        writeResultHeader(OP_TYPE_MODIFY_RESPONSE, result);
+        writeResultFooter(writer);
+        writeMessageFooter(result);
+    }
+
+    /**
+     * Write the provided search request.
+     *
+     * @param messageID
+     *            identifier of the request message
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeSearchRequest(final int messageID,
+            final SearchRequest request) throws IOException {
+        IO_LOG.trace("ENCODE LDAP SEARCH REQUEST(messageID={}, request={})", messageID, request);
+        writeMessageHeader(messageID);
+        writer.writeStartSequence(OP_TYPE_SEARCH_REQUEST);
+        writer.writeOctetString(request.getName().toString());
+        writer.writeEnumerated(request.getScope().intValue());
+        writer.writeEnumerated(request.getDereferenceAliasesPolicy().intValue());
+        writer.writeInteger(request.getSizeLimit());
+        writer.writeInteger(request.getTimeLimit());
+        writer.writeBoolean(request.isTypesOnly());
+        LDAPUtils.encodeFilter(writer, request.getFilter());
+
+        writer.writeStartSequence();
+        for (final String attribute : request.getAttributes()) {
+            writer.writeOctetString(attribute);
+        }
+        writer.writeEndSequence();
+
+        writer.writeEndSequence();
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write the provided search result.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param result
+     *            the result
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeSearchResult(final int messageID, final Result result)
+            throws IOException {
+        IO_LOG.trace("ENCODE LDAP SEARCH RESULT(messageID={}, result={})", messageID, result);
+        writeMessageHeader(messageID);
+        writeResultHeader(OP_TYPE_SEARCH_RESULT_DONE, result);
+        writeResultFooter(writer);
+        writeMessageFooter(result);
+    }
+
+    /**
+     * Write the provided search result entry.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param entry
+     *            the entry
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeSearchResultEntry(final int messageID,
+            final SearchResultEntry entry) throws IOException {
+        IO_LOG.trace("ENCODE LDAP SEARCH RESULT ENTRY(messageID={}, entry={})", messageID, entry);
+        writeMessageHeader(messageID);
+        writeEntry(entry);
+        writeMessageFooter(entry);
+    }
+
+    /**
+     * Write the provided search result reference.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param reference
+     *            the reference
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeSearchResultReference(final int messageID,
+            final SearchResultReference reference) throws IOException {
+        IO_LOG.trace("ENCODE LDAP SEARCH RESULT REFERENCE(messageID={}, reference={})", messageID, reference);
+        writeMessageHeader(messageID);
+        writer.writeStartSequence(OP_TYPE_SEARCH_RESULT_REFERENCE);
+        for (final String url : reference.getURIs()) {
+            writer.writeOctetString(url);
+        }
+        writer.writeEndSequence();
+        writeMessageFooter(reference);
+    }
+
+    /**
+     * Write the provided unbind request.
+     *
+     * @param messageID
+     *            identifier of the request message
+     * @param request
+     *            the request
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeUnbindRequest(final int messageID,
+            final UnbindRequest request) throws IOException {
+        IO_LOG.trace("ENCODE LDAP UNBIND REQUEST(messageID={}, request={})", messageID, request);
+        writeMessageHeader(messageID);
+        writer.writeNull(OP_TYPE_UNBIND_REQUEST);
+        writeMessageFooter(request);
+    }
+
+    /**
+     * Write a message with the provided id, tag and content bytes.
+     *
+     * @param messageID
+     *            identifier of the result message
+     * @param messageTag
+     *            tag identifying the type of message
+     * @param messageBytes
+     *            content of message
+     * @throws IOException
+     *             if an error occurs during write operation
+     */
+    public void writeUnrecognizedMessage(final int messageID,
+            final byte messageTag, final ByteString messageBytes) throws IOException {
+        IO_LOG.trace("ENCODE LDAP UNKNOWN MESSAGE(messageID={}, messageTag={}, messageBytes={})",
+                messageID, StaticUtils.byteToHex(messageTag), messageBytes);
+        writeMessageHeader(messageID);
+        writer.writeOctetString(messageTag, messageBytes);
+        writer.writeEndSequence();
+    }
+}
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/package-info.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/package-info.java
new file mode 100644
index 0000000..d00f4bd
--- /dev/null
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/io/package-info.java
@@ -0,0 +1,31 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * 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 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
+ *
+ *
+ *      Copyright 2013 ForgeRock AS.
+ */
+
+/**
+ * Classes providing I/O functionality.
+ */
+package org.forgerock.opendj.io;
+
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/TimeoutChecker.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/TimeoutChecker.java
similarity index 70%
rename from opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/TimeoutChecker.java
rename to opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/TimeoutChecker.java
index 7cf8bfc..943cdd9 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/TimeoutChecker.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/TimeoutChecker.java
@@ -25,7 +25,7 @@
  *      Portions copyright 2013 ForgeRock AS.
  */
 
-package com.forgerock.opendj.grizzly;
+package org.forgerock.opendj.ldap;
 
 import static com.forgerock.opendj.util.StaticUtils.DEFAULT_LOG;
 import static java.util.Collections.newSetFromMap;
@@ -35,13 +35,19 @@
 import com.forgerock.opendj.util.ReferenceCountedObject;
 
 /**
- * Checks connection for pending requests that have timed out.
+ * Checks {@code TimeoutEventListener listeners} for events that have timed out.
+ * <p>
+ * All listeners registered with the {@code #addListener()} method are called
+ * back with {@code TimeoutEventListener#handleTimeout()} to be able to handle
+ * the timeout.
+ * <p>
+ *
  */
-final class TimeoutChecker {
+public final class TimeoutChecker {
     /**
-     * Global reference counted instance.
+     * Global reference on the timeout checker.
      */
-    static final ReferenceCountedObject<TimeoutChecker> TIMEOUT_CHECKER =
+    public static final ReferenceCountedObject<TimeoutChecker> TIMEOUT_CHECKER =
             new ReferenceCountedObject<TimeoutChecker>() {
                 @Override
                 protected void destroyInstance(final TimeoutChecker instance) {
@@ -60,11 +66,12 @@
     private final Object available = new Object();
 
     /**
-     * The connection set must be safe from CMEs because expiring requests can
+     * The listener set must be safe from CMEs.
+     * For example, if the listener is a connection, expiring requests can
      * cause the connection to be closed.
      */
-    private final Set<GrizzlyLDAPConnection> connections =
-            newSetFromMap(new ConcurrentHashMap<GrizzlyLDAPConnection, Boolean>());
+    private final Set<TimeoutEventListener> listeners =
+            newSetFromMap(new ConcurrentHashMap<TimeoutEventListener, Boolean>());
 
     /**
      * Used to signal thread shutdown.
@@ -72,19 +79,18 @@
     private volatile boolean shutdownRequested = false;
 
     private TimeoutChecker() {
-        final Thread checkerThread = new Thread("OpenDJ LDAP SDK Connection Timeout Checker") {
+        final Thread checkerThread = new Thread("OpenDJ LDAP SDK Timeout Checker") {
             @Override
             public void run() {
                 DEFAULT_LOG.debug("Timeout Checker Starting");
                 while (!shutdownRequested) {
                     final long currentTime = System.currentTimeMillis();
                     long delay = 0;
-
-                    for (final GrizzlyLDAPConnection connection : connections) {
-                        DEFAULT_LOG.trace("Checking connection {} delay = {}", connection, delay);
+                    for (final TimeoutEventListener listener : listeners) {
+                        DEFAULT_LOG.trace("Checking connection {} delay = {}", listener, delay);
 
                         // May update the connections set.
-                        final long newDelay = connection.cancelExpiredRequests(currentTime);
+                        final long newDelay = listener.handleTimeout(currentTime);
                         if (newDelay > 0) {
                             if (delay > 0) {
                                 delay = Math.min(newDelay, delay);
@@ -113,13 +119,27 @@
         checkerThread.start();
     }
 
-    void addConnection(final GrizzlyLDAPConnection connection) {
-        connections.add(connection);
-        signal();
+    /**
+     * Add a listener to check.
+     *
+     * @param listener
+     *            listener to check for timeout event
+     */
+    public void addListener(final TimeoutEventListener listener) {
+        listeners.add(listener);
+        if (listener.getTimeout() > 0) {
+            signal();
+        }
     }
 
-    void removeConnection(final GrizzlyLDAPConnection connection) {
-        connections.remove(connection);
+    /**
+     * Stop checking a listener.
+     *
+     * @param listener
+     *            listener that was previously added to check for timeout event
+     */
+    public void removeListener(final TimeoutEventListener listener) {
+        listeners.remove(listener);
         // No need to signal.
     }
 
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/TimeoutEventListener.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/TimeoutEventListener.java
new file mode 100644
index 0000000..d67b385
--- /dev/null
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/TimeoutEventListener.java
@@ -0,0 +1,56 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * 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 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
+ *
+ *
+ *      Copyright 2013 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap;
+
+/**
+ * Listener on timeout events.
+ * <p>
+ * The listener must register itself with a {@code TimeoutChecker} using
+ * {@code TimeoutChecker#addListener()} method to be called back with
+ * {@code #handleTimeout} method.
+ * <p>
+ * The listener must deregister itself using
+ * {@code TimeoutChecker#removeListener()} to stop being called back.
+ */
+public interface TimeoutEventListener {
+
+    /**
+     * Handle a timeout event.
+     *
+     * @param currentTime
+     *            time to use as current time for any check
+     * @return the delay to wait before next timeout callback in milliseconds
+     */
+    long handleTimeout(final long currentTime);
+
+    /**
+     * Returns the timeout for this listener.
+     *
+     * @return the timeout in milliseconds
+     */
+    long getTimeout();
+
+}
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/AbstractLDAPMessageHandler.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLDAPMessageHandler.java
similarity index 63%
rename from opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/AbstractLDAPMessageHandler.java
rename to opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLDAPMessageHandler.java
index d653fcc..8d848f9 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/AbstractLDAPMessageHandler.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLDAPMessageHandler.java
@@ -22,9 +22,10 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
+ *      Portions copyright 2013 ForgeRock AS.
  */
 
-package com.forgerock.opendj.grizzly;
+package org.forgerock.opendj.ldap.spi;
 
 import java.io.IOException;
 
@@ -48,119 +49,140 @@
 import org.forgerock.opendj.ldap.responses.SearchResultReference;
 
 /**
- * Abstract LDAP message handler.
- *
- * @param <P>
- *            A user provided handler parameter.
+ * Provides a LDAPMessageHandler implementation that throw an exception for all
+ * methods.
  */
-abstract class AbstractLDAPMessageHandler<P> implements LDAPMessageHandler<P> {
-    public void abandonRequest(final P param, final int messageID, final AbandonRequest request)
+public abstract class AbstractLDAPMessageHandler implements LDAPMessageHandler {
+
+    @Override
+    public void abandonRequest(final int messageID, final AbandonRequest request)
             throws UnexpectedRequestException, IOException {
         throw new UnexpectedRequestException(messageID, request);
     }
 
-    public void addRequest(final P param, final int messageID, final AddRequest request)
+    @Override
+    public void addRequest(final int messageID, final AddRequest request)
             throws UnexpectedRequestException, IOException {
         throw new UnexpectedRequestException(messageID, request);
     }
 
-    public void addResult(final P param, final int messageID, final Result result)
+    @Override
+    public void addResult(final int messageID, final Result result)
             throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, result);
     }
 
-    public void bindRequest(final P param, final int messageID, final int version,
-            final GenericBindRequest request) throws UnexpectedRequestException, IOException {
-        throw new UnexpectedRequestException(messageID, request);
-    }
-
-    public void bindResult(final P param, final int messageID, final BindResult result)
-            throws UnexpectedResponseException, IOException {
-        throw new UnexpectedResponseException(messageID, result);
-    }
-
-    public void compareRequest(final P param, final int messageID, final CompareRequest request)
+    @Override
+    public void bindRequest(final int messageID, final int version, final GenericBindRequest request)
             throws UnexpectedRequestException, IOException {
         throw new UnexpectedRequestException(messageID, request);
     }
 
-    public void compareResult(final P param, final int messageID, final CompareResult result)
+    @Override
+    public void bindResult(final int messageID, final BindResult result)
             throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, result);
     }
 
-    public void deleteRequest(final P param, final int messageID, final DeleteRequest request)
+    @Override
+    public void compareRequest(final int messageID, final CompareRequest request)
             throws UnexpectedRequestException, IOException {
         throw new UnexpectedRequestException(messageID, request);
     }
 
-    public void deleteResult(final P param, final int messageID, final Result result)
+    @Override
+    public void compareResult(final int messageID, final CompareResult result)
             throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, result);
     }
 
-    public <R extends ExtendedResult> void extendedRequest(final P param, final int messageID,
-            final ExtendedRequest<R> request) throws UnexpectedRequestException, IOException {
+    @Override
+    public void deleteRequest(final int messageID, final DeleteRequest request)
+            throws UnexpectedRequestException, IOException {
         throw new UnexpectedRequestException(messageID, request);
     }
 
-    public void extendedResult(final P param, final int messageID, final ExtendedResult result)
+    @Override
+    public void deleteResult(final int messageID, final Result result)
             throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, result);
     }
 
-    public void intermediateResponse(final P param, final int messageID,
-            final IntermediateResponse response) throws UnexpectedResponseException, IOException {
+    @Override
+    public <R extends ExtendedResult> void extendedRequest(final int messageID, final ExtendedRequest<R> request)
+            throws UnexpectedRequestException, IOException {
+        throw new UnexpectedRequestException(messageID, request);
+    }
+
+    @Override
+    public void extendedResult(final int messageID, final ExtendedResult result)
+            throws UnexpectedResponseException, IOException {
+        throw new UnexpectedResponseException(messageID, result);
+    }
+
+    @Override
+    public void intermediateResponse(final int messageID, final IntermediateResponse response)
+            throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, response);
     }
 
-    public void modifyDNRequest(final P param, final int messageID, final ModifyDNRequest request)
+    @Override
+    public void modifyDNRequest(final int messageID, final ModifyDNRequest request)
             throws UnexpectedRequestException, IOException {
         throw new UnexpectedRequestException(messageID, request);
     }
 
-    public void modifyDNResult(final P param, final int messageID, final Result result)
+    @Override
+    public void modifyDNResult(final int messageID, final Result result)
             throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, result);
     }
 
-    public void modifyRequest(final P param, final int messageID, final ModifyRequest request)
+    @Override
+    public void modifyRequest(final int messageID, final ModifyRequest request)
             throws UnexpectedRequestException, IOException {
         throw new UnexpectedRequestException(messageID, request);
     }
 
-    public void modifyResult(final P param, final int messageID, final Result result)
+    @Override
+    public void modifyResult(final int messageID, final Result result)
             throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, result);
     }
 
-    public void searchRequest(final P param, final int messageID, final SearchRequest request)
+    @Override
+    public void searchRequest(final int messageID, final SearchRequest request)
             throws UnexpectedRequestException, IOException {
         throw new UnexpectedRequestException(messageID, request);
     }
 
-    public void searchResult(final P param, final int messageID, final Result result)
+    @Override
+    public void searchResult(final int messageID, final Result result)
             throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, result);
     }
 
-    public void searchResultEntry(final P param, final int messageID, final SearchResultEntry entry)
+    @Override
+    public void searchResultEntry(final int messageID, final SearchResultEntry entry)
             throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, entry);
     }
 
-    public void searchResultReference(final P param, final int messageID,
-            final SearchResultReference reference) throws UnexpectedResponseException, IOException {
+    @Override
+    public void searchResultReference(final int messageID, final SearchResultReference reference)
+            throws UnexpectedResponseException, IOException {
         throw new UnexpectedResponseException(messageID, reference);
     }
 
-    public void unbindRequest(final P param, final int messageID, final UnbindRequest request)
+    @Override
+    public void unbindRequest(final int messageID, final UnbindRequest request)
             throws UnexpectedRequestException, IOException {
         throw new UnexpectedRequestException(messageID, request);
     }
 
-    public void unrecognizedMessage(final P param, final int messageID, final byte messageTag,
-            final ByteString messageBytes) throws UnsupportedMessageException, IOException {
+    @Override
+    public void unrecognizedMessage(final int messageID, final byte messageTag, final ByteString messageBytes)
+            throws UnsupportedMessageException, IOException {
         throw new UnsupportedMessageException(messageID, messageTag, messageBytes);
     }
 }
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPMessageHandler.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPMessageHandler.java
new file mode 100644
index 0000000..3ddf76a
--- /dev/null
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPMessageHandler.java
@@ -0,0 +1,392 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * 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 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
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ *      Portions copyright 2013 ForgeRock AS.
+ */
+
+package org.forgerock.opendj.ldap.spi;
+
+import java.io.IOException;
+
+import org.forgerock.opendj.ldap.ByteString;
+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.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.GenericBindRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+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.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.IntermediateResponse;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.forgerock.opendj.ldap.responses.SearchResultReference;
+
+/**
+ * Interface for handler of LDAP messages (requests and responses).
+ *
+ */
+public interface LDAPMessageHandler {
+
+    /**
+     * Handle an abandon request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param request
+     *            abandon request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void abandonRequest(int messageID, AbandonRequest request)
+            throws UnexpectedRequestException, IOException;
+
+    /**
+     * Handle an add request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param request
+     *            add request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void addRequest(int messageID, AddRequest request) throws UnexpectedRequestException,
+            IOException;
+
+    /**
+     * Handle a response to an add request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param result
+     *            response received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void addResult(int messageID, Result result) throws UnexpectedResponseException,
+            IOException;
+
+    /**
+     * Handle a bind request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param version
+     *            protocol version for this bind request
+     * @param request
+     *            bind request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void bindRequest(int messageID, int version, GenericBindRequest request)
+            throws UnexpectedRequestException, IOException;
+
+    /**
+     * Handle a response to a bind request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param result
+     *            response received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void bindResult(int messageID, BindResult result) throws UnexpectedResponseException,
+            IOException;
+
+    /**
+     * Handle a compare request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param request
+     *            compare request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void compareRequest(int messageID, CompareRequest request)
+            throws UnexpectedRequestException, IOException;
+
+    /**
+     * Handle a response to a compare request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param result
+     *            response received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void compareResult(int messageID, CompareResult result)
+            throws UnexpectedResponseException, IOException;
+
+    /**
+     * Handle an delete request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param request
+     *            delete request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void deleteRequest(int messageID, DeleteRequest request)
+            throws UnexpectedRequestException, IOException;
+
+    /**
+     * Handle a response to a delete request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param result
+     *            response received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void deleteResult(int messageID, Result result) throws UnexpectedResponseException,
+            IOException;
+
+    /**
+     * Handle an extended request.
+     *
+     * @param <R>
+     *            type of extended result
+     * @param messageID
+     *            identifier of message
+     * @param request
+     *            extended request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    <R extends ExtendedResult> void extendedRequest(int messageID, ExtendedRequest<R> request)
+            throws UnexpectedRequestException, IOException;
+
+    /**
+     * Handle a response to an extended request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param result
+     *            response received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void extendedResult(int messageID, ExtendedResult result)
+            throws UnexpectedResponseException, IOException;
+
+    /**
+     * Handle an intermediate response.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param response
+     *            intermediate response to handle
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void intermediateResponse(int messageID, IntermediateResponse response)
+            throws UnexpectedResponseException, IOException;
+
+    /**
+     * Handle a modify DN request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param request
+     *            modify DN request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void modifyDNRequest(int messageID, ModifyDNRequest request)
+            throws UnexpectedRequestException, IOException;
+
+    /**
+     * Handle a response to a modify DN request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param result
+     *            response received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void modifyDNResult(int messageID, Result result) throws UnexpectedResponseException,
+            IOException;
+
+    /**
+     * Handle a modify request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param request
+     *            modify request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void modifyRequest(int messageID, ModifyRequest request)
+            throws UnexpectedRequestException, IOException;
+
+    /**
+     * Handle a response to a modify request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param result
+     *            response received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void modifyResult(int messageID, Result result) throws UnexpectedResponseException,
+            IOException;
+
+    /**
+     * Handle a search request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param request
+     *            search request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void searchRequest(int messageID, SearchRequest request)
+            throws UnexpectedRequestException, IOException;
+
+    /**
+     * Handle a response to a search request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param result
+     *            response received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void searchResult(int messageID, Result result) throws UnexpectedResponseException,
+            IOException;
+
+    /**
+     * Handle a response to a search request returning an entry.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param entry
+     *            entry received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void searchResultEntry(int messageID, SearchResultEntry entry)
+            throws UnexpectedResponseException, IOException;
+
+    /**
+     * Handle a response to a search request returning an reference.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param reference
+     *            reference received
+     * @throws UnexpectedResponseException
+     *             if response is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void searchResultReference(int messageID, SearchResultReference reference)
+            throws UnexpectedResponseException, IOException;
+
+    /**
+     * Handle an unbind request.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param request
+     *            unbind request to handle
+     * @throws UnexpectedRequestException
+     *             if request is not expected by this handler
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void unbindRequest(int messageID, UnbindRequest request)
+            throws UnexpectedRequestException, IOException;
+
+    /**
+     * Handle an unrecognized message.
+     *
+     * @param messageID
+     *            identifier of message
+     * @param messageTag
+     *            tag identifying the message type
+     * @param messageBytes
+     *          content of the message
+     * @throws UnsupportedMessageException
+     *             if the received message is not supported
+     * @throws IOException
+     *             if an errors occurs when sending a subsequent message
+     */
+    void unrecognizedMessage(int messageID, byte messageTag, ByteString messageBytes)
+            throws UnsupportedMessageException, IOException;
+}
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/UnexpectedRequestException.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnexpectedRequestException.java
similarity index 72%
rename from opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/UnexpectedRequestException.java
rename to opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnexpectedRequestException.java
index 368b264..2d01713 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/UnexpectedRequestException.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnexpectedRequestException.java
@@ -22,9 +22,10 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
+ *      Portions copyright 2013 ForgeRock AS.
  */
 
-package com.forgerock.opendj.grizzly;
+package org.forgerock.opendj.ldap.spi;
 
 import java.io.IOException;
 
@@ -32,13 +33,21 @@
 import org.forgerock.opendj.ldap.requests.Request;
 
 /**
- * Thrown when an expected LDAP request is received.
+ * Thrown when an unexpected LDAP request is received.
  */
 @SuppressWarnings("serial")
-final class UnexpectedRequestException extends IOException {
+public final class UnexpectedRequestException extends IOException {
     private final int messageID;
     private final Request request;
 
+    /**
+     * Creates the exception with a message id and a request.
+     *
+     * @param messageID
+     *            id of message
+     * @param request
+     *            request received
+     */
     public UnexpectedRequestException(final int messageID, final Request request) {
         super(LocalizableMessage.raw("Unexpected LDAP request: id=%d, message=%s", messageID,
                 request).toString());
@@ -46,10 +55,20 @@
         this.request = request;
     }
 
+    /**
+     * Returns the identifier of the message.
+     *
+     * @return the identifier
+     */
     public int getMessageID() {
         return messageID;
     }
 
+    /**
+     * Returns the request.
+     *
+     * @return the received request
+     */
     public Request getRequest() {
         return request;
     }
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/UnexpectedResponseException.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnexpectedResponseException.java
similarity index 74%
rename from opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/UnexpectedResponseException.java
rename to opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnexpectedResponseException.java
index a386fd7..2f1b02d 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/UnexpectedResponseException.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnexpectedResponseException.java
@@ -22,9 +22,10 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
+ *      Portions copyright 2013 ForgeRock AS.
  */
 
-package com.forgerock.opendj.grizzly;
+package org.forgerock.opendj.ldap.spi;
 
 import java.io.IOException;
 
@@ -35,10 +36,18 @@
  * Thrown when an unexpected LDAP response is received.
  */
 @SuppressWarnings("serial")
-final class UnexpectedResponseException extends IOException {
+public final class UnexpectedResponseException extends IOException {
     private final int messageID;
     private final Response response;
 
+    /**
+     * Creates the exception with a message id and a response.
+     *
+     * @param messageID
+     *            id of message
+     * @param response
+     *            response received
+     */
     public UnexpectedResponseException(final int messageID, final Response response) {
         super(LocalizableMessage.raw("Unexpected LDAP response: id=%d, message=%s", messageID,
                 response).toString());
@@ -46,10 +55,20 @@
         this.response = response;
     }
 
+    /**
+     * Returns the identifier of the message.
+     *
+     * @return the identifier
+     */
     public int getMessageID() {
         return messageID;
     }
 
+    /**
+     * Returns the response.
+     *
+     * @return the received response
+     */
     public Response getResponse() {
         return response;
     }
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/UnsupportedMessageException.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnsupportedMessageException.java
similarity index 71%
rename from opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/UnsupportedMessageException.java
rename to opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnsupportedMessageException.java
index 129ec01..1dc337d 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/UnsupportedMessageException.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/UnsupportedMessageException.java
@@ -22,9 +22,10 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
+ *      Portions copyright 2013 ForgeRock AS.
  */
 
-package com.forgerock.opendj.grizzly;
+package org.forgerock.opendj.ldap.spi;
 
 import java.io.IOException;
 
@@ -35,11 +36,21 @@
  * Thrown when an unsupported LDAP message is received.
  */
 @SuppressWarnings("serial")
-final class UnsupportedMessageException extends IOException {
+public final class UnsupportedMessageException extends IOException {
     private final int id;
     private final byte tag;
     private final ByteString content;
 
+    /**
+     * Creates the exception with message id, tag and content.
+     *
+     * @param id
+     *            identifier of received message
+     * @param tag
+     *            identify message type
+     * @param content
+     *            message content
+     */
     public UnsupportedMessageException(final int id, final byte tag, final ByteString content) {
         super(LocalizableMessage.raw("Unsupported LDAP message: id=%d, tag=%d, content=%s", id,
                 tag, content).toString());
@@ -48,14 +59,29 @@
         this.content = content;
     }
 
+    /**
+     * Returns the content.
+     *
+     * @return the message content
+     */
     public ByteString getContent() {
         return content;
     }
 
+    /**
+     * Returns the id.
+     *
+     * @return the message identifier
+     */
     public int getID() {
         return id;
     }
 
+    /**
+     * Returns the tag.
+     *
+     * @return the message tag
+     */
     public byte getTag() {
         return tag;
     }
diff --git a/opendj3/opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java
new file mode 100644
index 0000000..65c7ded
--- /dev/null
+++ b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java
@@ -0,0 +1,178 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * 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 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
+ *
+ *
+ *      Copyright 2013 ForgeRock AS.
+ */
+package org.forgerock.opendj.io;
+
+import static org.fest.assertions.Assertions.*;
+
+import java.io.IOException;
+
+import org.forgerock.opendj.asn1.ASN1Reader;
+import org.forgerock.opendj.asn1.ASN1Writer;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.SdkTestCase;
+import org.forgerock.opendj.ldap.requests.AbandonRequest;
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.GenericBindRequest;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.responses.Responses;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.spi.AbstractLDAPMessageHandler;
+import org.forgerock.opendj.ldap.spi.LDAPMessageHandler;
+import org.forgerock.opendj.ldap.spi.UnexpectedRequestException;
+import org.forgerock.opendj.ldap.spi.UnexpectedResponseException;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Support class for testing {@code LDAPWriter} and {@code LDAPReader}
+ * classes in a specific transport provider.
+ *
+ * A specific transport provider should provide a test case by :
+ * <ul>
+ *   <li>Extending this class</li>
+ *   <li>Implementing the 3 abstract methods {@code getLDAPReader()}, {@code getLDAPReader()}
+ *   and {@code transferFromWriterToReader()}</li>
+ * </ul>
+ */
+public abstract class LDAPReaderWriterTestCase extends SdkTestCase {
+
+    // DN used is several tests
+    private static final String TEST_DN = "cn=test";
+
+    interface LDAPWrite {
+        public void perform(LDAPWriter<? extends ASN1Writer> writer) throws IOException;
+    }
+
+    @DataProvider
+    protected Object[][] messagesFactories() {
+        return new Object[][] {
+                addRequest(),
+                addResult(),
+                abandonRequest(),
+                bindRequest(),
+        };
+    }
+
+    Object[] addRequest() {
+        return new Object[] { new LDAPWrite() {
+            @Override
+            public void perform(LDAPWriter<? extends ASN1Writer> writer) throws IOException {
+                writer.writeAddRequest(0, Requests.newAddRequest(TEST_DN));
+            }
+        }, new AbstractLDAPMessageHandler() {
+            @Override
+            public void addRequest(int messageID, AddRequest request) throws UnexpectedRequestException, IOException {
+                assertThat(request.getName().toString()).isEqualTo(TEST_DN);
+            }
+        } };
+    }
+
+    Object[] addResult() {
+        final ResultCode resultCode = ResultCode.SUCCESS;
+        return new Object[] { new LDAPWrite() {
+            @Override
+            public void perform(LDAPWriter<? extends ASN1Writer> writer) throws IOException {
+                writer.writeAddResult(1, Responses.newResult(resultCode).setMatchedDN(TEST_DN));
+            }
+        }, new AbstractLDAPMessageHandler() {
+            @Override
+            public void addResult(int messageID, Result result) throws UnexpectedResponseException, IOException {
+                assertThat(result.getResultCode()).isEqualTo(resultCode);
+                assertThat(result.getMatchedDN().toString()).isEqualTo(TEST_DN);
+            }
+        } };
+    }
+
+    Object[] abandonRequest() {
+        final int requestID = 1;
+        return new Object[] { new LDAPWrite() {
+            @Override
+            public void perform(LDAPWriter<? extends ASN1Writer> writer) throws IOException {
+                writer.writeAbandonRequest(0, Requests.newAbandonRequest(requestID));
+            }
+        }, new AbstractLDAPMessageHandler() {
+            @Override
+            public void abandonRequest(int messageID, AbandonRequest request) throws UnexpectedRequestException,
+            IOException {
+                assertThat(request.getRequestID()).isEqualTo(requestID);
+            }
+        } };
+    }
+
+    Object[] bindRequest() {
+        final byte type = 0x01;
+        final byte[] value = new byte[] {0x01, 0x02};
+        return new Object[] { new LDAPWrite() {
+            @Override
+            public void perform(LDAPWriter<? extends ASN1Writer> writer) throws IOException {
+                writer.writeBindRequest(0, 1,
+                        Requests.newGenericBindRequest(TEST_DN, type, value));
+            }
+        }, new AbstractLDAPMessageHandler() {
+            @Override
+            public void bindRequest(final int messageID, final int version, final GenericBindRequest request)
+                    throws UnexpectedRequestException, IOException {
+                assertThat(request.getAuthenticationType()).isEqualTo(type);
+                assertThat(request.getAuthenticationValue()).isEqualTo(value);
+                assertThat(request.getName()).isEqualTo(TEST_DN);
+            }
+        } };
+    }
+
+    /**
+     * Test that a LDAP message written by LDAPWriter is read correctly using LDAPReader.
+     *
+     * @param writing write instruction to perform
+     * @param messageHandler handler of message read, containing assertion
+     *
+     * @throws Exception
+     */
+    @Test(dataProvider = "messagesFactories")
+    public void testWriteReadMessage(LDAPWrite writing, LDAPMessageHandler messageHandler)
+            throws Exception {
+        LDAPWriter<? extends ASN1Writer> writer = getLDAPWriter();
+        writing.perform(writer);
+        LDAPReader<? extends ASN1Reader> reader = getLDAPReader();
+        transferFromWriterToReader(writer, reader);
+        reader.readMessage(messageHandler);
+    }
+
+    /**
+     * Returns a writer specific to the transport module.
+     */
+    abstract protected LDAPWriter<? extends ASN1Writer> getLDAPWriter();
+
+    /**
+     * Returns a reader specific to the transport module.
+     */
+    abstract protected LDAPReader<? extends ASN1Reader> getLDAPReader();
+
+    /**
+     * Transfer raw data from writer to the reader.
+     */
+    abstract protected void transferFromWriterToReader(LDAPWriter<? extends ASN1Writer> writer,
+            LDAPReader<? extends ASN1Reader> reader);
+}
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferReader.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferReader.java
index c2c12f8..ce6ead2 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferReader.java
+++ b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferReader.java
@@ -27,15 +27,12 @@
 
 package com.forgerock.opendj.grizzly;
 
-import static com.forgerock.opendj.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
-import static com.forgerock.opendj.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
-import static com.forgerock.opendj.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_TYPE;
-import static com.forgerock.opendj.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_VALUE_BYTES;
 import static com.forgerock.opendj.ldap.CoreMessages.*;
-import static com.forgerock.opendj.util.StaticUtils.IO_LOG;
-import static com.forgerock.opendj.util.StaticUtils.byteToHex;
+import static com.forgerock.opendj.ldap.LDAPConstants.*;
+import static com.forgerock.opendj.util.StaticUtils.*;
 
 import java.io.IOException;
+
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.opendj.asn1.ASN1Reader;
 import org.forgerock.opendj.asn1.AbstractASN1Reader;
@@ -50,7 +47,7 @@
 /**
  * Grizzly ASN1 reader implementation.
  */
-final class ASN1BufferReader extends AbstractASN1Reader implements ASN1Reader {
+final class ASN1BufferReader extends AbstractASN1Reader {
     private final class ChildSequenceLimiter implements SequenceLimiter {
         private SequenceLimiter parent;
 
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferWriter.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferWriter.java
index 82fff73..18f652b 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferWriter.java
+++ b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ASN1BufferWriter.java
@@ -40,7 +40,6 @@
 import org.forgerock.opendj.ldap.ByteStringBuilder;
 import org.glassfish.grizzly.Buffer;
 import org.glassfish.grizzly.Cacheable;
-import org.glassfish.grizzly.ThreadCache;
 import org.glassfish.grizzly.memory.ByteBufferWrapper;
 
 import com.forgerock.opendj.util.StaticUtils;
@@ -48,7 +47,7 @@
 /**
  * Grizzly ASN1 writer implementation.
  */
-final class ASN1BufferWriter extends AbstractASN1Writer implements ASN1Writer, Cacheable {
+final class ASN1BufferWriter extends AbstractASN1Writer implements Cacheable {
     private class ChildSequenceBuffer implements SequenceBuffer {
         private SequenceBuffer parent;
 
@@ -162,21 +161,16 @@
     }
 
     private static final int BUFFER_INIT_SIZE = 1024;
-    private final static ThreadCache.CachedTypeIndex<ASN1BufferWriter> WRITER_INDEX = ThreadCache
-            .obtainIndex(ASN1BufferWriter.class, 1);
 
-    static ASN1BufferWriter getWriter() {
-        ASN1BufferWriter asn1Writer = ThreadCache.takeFromCache(WRITER_INDEX);
-        if (asn1Writer == null) {
-            asn1Writer = new ASN1BufferWriter();
-        }
-
-        if (!asn1Writer.outBuffer.usable) {
+    /**
+     * Reset the writer.
+     */
+    void reset() {
+        if (!outBuffer.usable) {
             // If the output buffer is unusable, create a new one.
-            asn1Writer.outBuffer = new RecyclableBuffer();
+            outBuffer = new RecyclableBuffer();
         }
-        asn1Writer.outBuffer.clear();
-        return asn1Writer;
+        outBuffer.clear();
     }
 
     private SequenceBuffer sequenceBuffer;
@@ -187,7 +181,7 @@
     /**
      * Creates a new ASN.1 writer that writes to a StreamWriter.
      */
-    private ASN1BufferWriter() {
+    ASN1BufferWriter() {
         this.sequenceBuffer = this.rootBuffer = new RootSequenceBuffer();
         this.outBuffer = new RecyclableBuffer();
     }
@@ -213,10 +207,12 @@
         // Do nothing
     }
 
+    /**
+     * Recycle the writer to allow re-use.
+     */
     public void recycle() {
         sequenceBuffer = rootBuffer;
         outBuffer.clear();
-        ThreadCache.putToCache(WRITER_INDEX, this);
     }
 
     /**
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
index 6a2f5d8..c348a33 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
+++ b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
@@ -44,8 +44,8 @@
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 
+import org.forgerock.opendj.io.LDAPWriter;
 import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
-import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ConnectionEventListener;
 import org.forgerock.opendj.ldap.ErrorResultException;
 import org.forgerock.opendj.ldap.FutureResult;
@@ -55,6 +55,7 @@
 import org.forgerock.opendj.ldap.ResultHandler;
 import org.forgerock.opendj.ldap.SSLContextBuilder;
 import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.TimeoutEventListener;
 import org.forgerock.opendj.ldap.TrustManagers;
 import org.forgerock.opendj.ldap.requests.AbandonRequest;
 import org.forgerock.opendj.ldap.requests.AddRequest;
@@ -93,7 +94,7 @@
 /**
  * LDAP connection implementation.
  */
-final class GrizzlyLDAPConnection extends AbstractAsynchronousConnection implements Connection {
+final class GrizzlyLDAPConnection extends AbstractAsynchronousConnection implements TimeoutEventListener {
     /**
      * A dummy SSL client engine configurator as SSLFilter only needs client
      * config. This prevents Grizzly from needlessly using JVM defaults which
@@ -113,7 +114,6 @@
 
     private final AtomicBoolean bindOrStartTLSInProgress = new AtomicBoolean(false);
     private final org.glassfish.grizzly.Connection<?> connection;
-    private final LDAPWriter ldapWriter = new LDAPWriter();
     private final AtomicInteger nextMsgID = new AtomicInteger(1);
     private final GrizzlyLDAPConnectionFactory factory;
     private final ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>> pendingRequests =
@@ -126,6 +126,15 @@
     private boolean isFailed = false;
     private List<ConnectionEventListener> listeners = null;
 
+    /**
+     * Create a LDAP Connection with provided Grizzly connection and LDAP
+     * connection factory.
+     *
+     * @param connection
+     *            actual connection
+     * @param factory
+     *            factory that provides LDAP connections
+     */
     GrizzlyLDAPConnection(final org.glassfish.grizzly.Connection<?> connection,
             final GrizzlyLDAPConnectionFactory factory) {
         this.connection = connection;
@@ -155,13 +164,13 @@
             }
             pendingRequest.cancel(false);
             try {
-                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                 try {
-                    ldapWriter.abandonRequest(asn1Writer, messageID, request);
-                    connection.write(asn1Writer.getBuffer(), null);
+                    writer.writeAbandonRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
                     return new CompletedFutureResult<Void>((Void) null, messageID);
                 } finally {
-                    asn1Writer.recycle();
+                    GrizzlyUtils.recycleWriter(writer);
                 }
             } catch (final IOException e) {
                 throw adaptRequestIOException(e);
@@ -186,12 +195,12 @@
                 pendingRequests.put(messageID, future);
             }
             try {
-                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                 try {
-                    ldapWriter.addRequest(asn1Writer, messageID, request);
-                    connection.write(asn1Writer.getBuffer(), null);
+                    writer.writeAddRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
                 } finally {
-                    asn1Writer.recycle();
+                    GrizzlyUtils.recycleWriter(writer);
                 }
             } catch (final IOException e) {
                 pendingRequests.remove(messageID);
@@ -275,15 +284,15 @@
             }
 
             try {
-                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                 try {
                     // Use the bind client to get the initial request instead of
                     // using the bind request passed to this method.
                     final GenericBindRequest initialRequest = context.nextBindRequest();
-                    ldapWriter.bindRequest(asn1Writer, messageID, 3, initialRequest);
-                    connection.write(asn1Writer.getBuffer(), null);
+                    writer.writeBindRequest(messageID, 3, initialRequest);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
                 } finally {
-                    asn1Writer.recycle();
+                    GrizzlyUtils.recycleWriter(writer);
                 }
             } catch (final IOException e) {
                 pendingRequests.remove(messageID);
@@ -320,12 +329,12 @@
                 pendingRequests.put(messageID, future);
             }
             try {
-                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                 try {
-                    ldapWriter.compareRequest(asn1Writer, messageID, request);
-                    connection.write(asn1Writer.getBuffer(), null);
+                    writer.writeCompareRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
                 } finally {
-                    asn1Writer.recycle();
+                    GrizzlyUtils.recycleWriter(writer);
                 }
             } catch (final IOException e) {
                 pendingRequests.remove(messageID);
@@ -352,12 +361,12 @@
                 pendingRequests.put(messageID, future);
             }
             try {
-                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                 try {
-                    ldapWriter.deleteRequest(asn1Writer, messageID, request);
-                    connection.write(asn1Writer.getBuffer(), null);
+                    writer.writeDeleteRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
                 } finally {
-                    asn1Writer.recycle();
+                    GrizzlyUtils.recycleWriter(writer);
                 }
             } catch (final IOException e) {
                 pendingRequests.remove(messageID);
@@ -404,12 +413,12 @@
                 pendingRequests.put(messageID, future);
             }
             try {
-                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                 try {
-                    ldapWriter.extendedRequest(asn1Writer, messageID, request);
-                    connection.write(asn1Writer.getBuffer(), null);
+                    writer.writeExtendedRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
                 } finally {
-                    asn1Writer.recycle();
+                    GrizzlyUtils.recycleWriter(writer);
                 }
             } catch (final IOException e) {
                 pendingRequests.remove(messageID);
@@ -451,12 +460,12 @@
                 pendingRequests.put(messageID, future);
             }
             try {
-                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                 try {
-                    ldapWriter.modifyRequest(asn1Writer, messageID, request);
-                    connection.write(asn1Writer.getBuffer(), null);
+                    writer.writeModifyRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
                 } finally {
-                    asn1Writer.recycle();
+                    GrizzlyUtils.recycleWriter(writer);
                 }
             } catch (final IOException e) {
                 pendingRequests.remove(messageID);
@@ -483,12 +492,12 @@
                 pendingRequests.put(messageID, future);
             }
             try {
-                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                 try {
-                    ldapWriter.modifyDNRequest(asn1Writer, messageID, request);
-                    connection.write(asn1Writer.getBuffer(), null);
+                    writer.writeModifyDNRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
                 } finally {
-                    asn1Writer.recycle();
+                    GrizzlyUtils.recycleWriter(writer);
                 }
             } catch (final IOException e) {
                 pendingRequests.remove(messageID);
@@ -525,12 +534,12 @@
                 pendingRequests.put(messageID, future);
             }
             try {
-                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                 try {
-                    ldapWriter.searchRequest(asn1Writer, messageID, request);
-                    connection.write(asn1Writer.getBuffer(), null);
+                    writer.writeSearchRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
                 } finally {
-                    asn1Writer.recycle();
+                    GrizzlyUtils.recycleWriter(writer);
                 }
             } catch (final IOException e) {
                 pendingRequests.remove(messageID);
@@ -553,19 +562,26 @@
         return builder.toString();
     }
 
-    long cancelExpiredRequests(final long currentTime) {
-        final long timeout = factory.getLDAPOptions().getTimeout(TimeUnit.MILLISECONDS);
+    @Override
+    public long handleTimeout(final long currentTime) {
+        final long timeout = getTimeout();
         long delay = timeout;
         if (timeout > 0) {
             for (final int requestID : pendingRequests.keySet()) {
                 final AbstractLDAPFutureResultImpl<?> future = pendingRequests.get(requestID);
                 if (future != null && future.checkForTimeout()) {
                     final long diff = (future.getTimestamp() + timeout) - currentTime;
-                    if (diff <= 0 && pendingRequests.remove(requestID) != null) {
-                        DEFAULT_LOG.debug("Cancelling expired future result: {}", future);
-                        final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT);
-                        future.adaptErrorResult(result);
-                        abandonAsync(Requests.newAbandonRequest(future.getRequestID()));
+                    if (diff <= 0) {
+                        if (pendingRequests.remove(requestID) != null) {
+                            DEFAULT_LOG.debug("Cancelling expired future result: {}", future);
+                            final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT);
+                            future.adaptErrorResult(result);
+                            abandonAsync(Requests.newAbandonRequest(future.getRequestID()));
+                        } else {
+                            DEFAULT_LOG.debug(
+                                    "Pending request {} has already been removed, not cancelling future result",
+                                    requestID);
+                        }
                     } else {
                         delay = Math.min(delay, diff);
                     }
@@ -575,6 +591,11 @@
         return delay;
     }
 
+    @Override
+    public long getTimeout() {
+        return factory.getLDAPOptions().getTimeout(TimeUnit.MILLISECONDS);
+    }
+
     /**
      * Closes this connection, invoking event listeners as needed.
      *
@@ -633,19 +654,19 @@
          * connection and release resources.
          */
         if (notifyClose) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
             try {
-                ldapWriter.unbindRequest(asn1Writer, nextMsgID.getAndIncrement(), unbindRequest);
-                connection.write(asn1Writer.getBuffer(), null);
+                writer.writeUnbindRequest(nextMsgID.getAndIncrement(), unbindRequest);
+                connection.write(writer.getASN1Writer().getBuffer(), null);
             } catch (final Exception ignore) {
                 /*
                  * Underlying channel probably blown up. Ignore all errors,
                  * including possibly runtime exceptions (see OPENDJ-672).
                  */
             } finally {
-                asn1Writer.recycle();
+                GrizzlyUtils.recycleWriter(writer);
             }
-            factory.getTimeoutChecker().removeConnection(this);
+            factory.getTimeoutChecker().removeListener(this);
             connection.closeSilently();
             factory.releaseTransportAndTimeoutChecker();
         }
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
index 165e400..4fa7811 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
+++ b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
@@ -28,8 +28,8 @@
 package com.forgerock.opendj.grizzly;
 
 import static com.forgerock.opendj.grizzly.DefaultTCPNIOTransport.DEFAULT_TRANSPORT;
-import static com.forgerock.opendj.grizzly.TimeoutChecker.TIMEOUT_CHECKER;
 import static org.forgerock.opendj.ldap.ErrorResultException.*;
+import static org.forgerock.opendj.ldap.TimeoutChecker.TIMEOUT_CHECKER;
 
 import java.io.IOException;
 import java.net.SocketAddress;
@@ -45,6 +45,7 @@
 import org.forgerock.opendj.ldap.LDAPOptions;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.ResultHandler;
+import org.forgerock.opendj.ldap.TimeoutChecker;
 import org.forgerock.opendj.ldap.requests.Requests;
 import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
 import org.forgerock.opendj.ldap.responses.ExtendedResult;
@@ -165,7 +166,7 @@
             connection.configureBlocking(true);
             final GrizzlyLDAPConnection ldapConnection =
                     new GrizzlyLDAPConnection(connection, GrizzlyLDAPConnectionFactory.this);
-            timeoutChecker.get().addConnection(ldapConnection);
+            timeoutChecker.get().addListener(ldapConnection);
             clientFilter.registerConnection(connection, ldapConnection);
             return ldapConnection;
         }
@@ -254,7 +255,7 @@
         this.transport = DEFAULT_TRANSPORT.acquireIfNull(transport);
         this.socketAddress = address;
         this.options = new LDAPOptions(options);
-        this.clientFilter = new LDAPClientFilter(new LDAPReader(this.options.getDecodeOptions()), 0);
+        this.clientFilter = new LDAPClientFilter(this.options.getDecodeOptions(), 0);
         this.defaultFilterChain = GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), clientFilter);
 
     }
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPListener.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
index b441b64..e9748cd 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
+++ b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
@@ -102,7 +102,7 @@
         this.connectionFactory = factory;
 
         final DecodeOptions decodeOptions = new DecodeOptions(options.getDecodeOptions());
-        final LDAPServerFilter serverFilter = new LDAPServerFilter(this, new LDAPReader(decodeOptions), options
+        final LDAPServerFilter serverFilter = new LDAPServerFilter(this, decodeOptions, options
                 .getMaxRequestSize());
         final FilterChain ldapChain = GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), serverFilter);
         final TCPNIOBindingHandler bindingHandler =
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyUtils.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyUtils.java
index 4750bdd..d81e72f 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyUtils.java
+++ b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyUtils.java
@@ -25,11 +25,16 @@
  */
 package com.forgerock.opendj.grizzly;
 
+import org.forgerock.opendj.io.LDAPReader;
+import org.forgerock.opendj.io.LDAPWriter;
+import org.forgerock.opendj.ldap.DecodeOptions;
 import org.glassfish.grizzly.Processor;
+import org.glassfish.grizzly.ThreadCache;
 import org.glassfish.grizzly.filterchain.Filter;
 import org.glassfish.grizzly.filterchain.FilterChain;
 import org.glassfish.grizzly.filterchain.FilterChainBuilder;
 import org.glassfish.grizzly.filterchain.TransportFilter;
+import org.glassfish.grizzly.memory.MemoryManager;
 import org.glassfish.grizzly.ssl.SSLFilter;
 
 /**
@@ -38,6 +43,10 @@
 final class GrizzlyUtils {
 
 
+    @SuppressWarnings("rawtypes")
+    private static final ThreadCache.CachedTypeIndex<LDAPWriter> WRITER_INDEX = ThreadCache
+            .obtainIndex(LDAPWriter.class, 1);
+
     /**
      * Build a filter chain from the provided processor if possible and the
      * provided filter.
@@ -57,7 +66,7 @@
      *         is a {@code FilterChain}, and having the provided filter as the
      *         last filter
      */
-    public static FilterChain buildFilterChain(Processor processor, Filter filter) {
+    public static FilterChain buildFilterChain(Processor<?> processor, Filter filter) {
         if (processor instanceof FilterChain) {
             return FilterChainBuilder.stateless().addAll((FilterChain) processor).add(filter).build();
         } else {
@@ -118,6 +127,57 @@
         return FilterChainBuilder.stateless().addAll(chain).add(indexToAddFilter, filter).build();
     }
 
+    /**
+     * Creates a new LDAP Reader with the provided maximum size of ASN1 element,
+     * options and memory manager.
+     *
+     * @param decodeOptions
+     *            allow to control how responses and requests are decoded
+     * @param maxASN1ElementSize
+     *            The maximum BER element size, or <code>0</code> to indicate
+     *            that there is no limit.
+     * @param memoryManager
+     *            The memory manager to use for buffering.
+     * @return a LDAP reader
+     */
+    public static LDAPReader<ASN1BufferReader> createReader(DecodeOptions decodeOptions, int maxASN1ElementSize,
+            MemoryManager<?> memoryManager) {
+        ASN1BufferReader asn1Reader = new ASN1BufferReader(maxASN1ElementSize, memoryManager);
+        return new LDAPReader<ASN1BufferReader>(asn1Reader, decodeOptions);
+    }
+
+    /**
+     * Returns a LDAP writer, with a clean ASN1Writer, possibly from
+     * the thread local cache.
+     * <p>
+     * The writer is either returned from thread local cache or created.
+     * In the former case, the writer is removed from the cache.
+     *
+     * @return a LDAP writer
+     */
+    @SuppressWarnings("unchecked")
+    public static LDAPWriter<ASN1BufferWriter> getWriter() {
+        LDAPWriter<ASN1BufferWriter> writer = ThreadCache.takeFromCache(WRITER_INDEX);
+        if (writer == null) {
+            writer = new LDAPWriter<ASN1BufferWriter>(new ASN1BufferWriter());
+        }
+        writer.getASN1Writer().reset();
+        return writer;
+    }
+
+    /**
+     * Recycle a LDAP writer to a thread local cache.
+     * <p>
+     * The LDAP writer is then available for the thread using the
+     * {@get()} method.
+     *
+     * @param writer LDAP writer to recycle
+     */
+    public static void recycleWriter(LDAPWriter<ASN1BufferWriter> writer) {
+        writer.getASN1Writer().recycle();
+        ThreadCache.putToCache(WRITER_INDEX, writer);
+    }
+
     // Prevent instantiation.
     private GrizzlyUtils() {
         // No implementation required.
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPClientFilter.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPClientFilter.java
index 9b8ab08..a529127 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPClientFilter.java
+++ b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPClientFilter.java
@@ -34,8 +34,11 @@
 
 import javax.net.ssl.SSLEngine;
 
+import org.forgerock.opendj.io.LDAPReader;
+import org.forgerock.opendj.io.LDAPWriter;
 import org.forgerock.opendj.ldap.ConnectionSecurityLayer;
 import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.DecodeOptions;
 import org.forgerock.opendj.ldap.ErrorResultException;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.requests.AddRequest;
@@ -54,11 +57,13 @@
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldap.responses.SearchResultReference;
 import org.forgerock.opendj.ldap.spi.AbstractLDAPFutureResultImpl;
+import org.forgerock.opendj.ldap.spi.AbstractLDAPMessageHandler;
 import org.forgerock.opendj.ldap.spi.LDAPBindFutureResultImpl;
 import org.forgerock.opendj.ldap.spi.LDAPCompareFutureResultImpl;
 import org.forgerock.opendj.ldap.spi.LDAPExtendedFutureResultImpl;
 import org.forgerock.opendj.ldap.spi.LDAPFutureResultImpl;
 import org.forgerock.opendj.ldap.spi.LDAPSearchFutureResultImpl;
+import org.forgerock.opendj.ldap.spi.UnexpectedResponseException;
 import org.glassfish.grizzly.Buffer;
 import org.glassfish.grizzly.Connection;
 import org.glassfish.grizzly.EmptyCompletionHandler;
@@ -75,382 +80,413 @@
 final class LDAPClientFilter extends BaseFilter {
     private static final Attribute<GrizzlyLDAPConnection> LDAP_CONNECTION_ATTR =
             Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPClientConnection");
-    private static final Attribute<ASN1BufferReader> LDAP_ASN1_READER_ATTR =
-            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPASN1Reader");
+
+    private static final Attribute<ClientResponseHandler> RESPONSE_HANDLER_ATTR =
+            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("ClientResponseHandler");
 
     private final int maxASN1ElementSize;
-    private final LDAPReader ldapReader;
+    private final DecodeOptions decodeOptions;
 
-    private static final AbstractLDAPMessageHandler<FilterChainContext> CLIENT_RESPONSE_HANDLER =
-            new AbstractLDAPMessageHandler<FilterChainContext>() {
-                @Override
-                public void addResult(final FilterChainContext ctx, final int messageID,
-                        final Result result) throws UnexpectedResponseException, IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.removePendingRequest(messageID);
+    private static final class ClientResponseHandler extends AbstractLDAPMessageHandler {
 
-                        if (pendingRequest != null) {
-                            if (pendingRequest instanceof LDAPFutureResultImpl) {
-                                final LDAPFutureResultImpl future =
-                                        (LDAPFutureResultImpl) pendingRequest;
-                                if (future.getRequest() instanceof AddRequest) {
-                                    future.setResultOrError(result);
-                                    return;
-                                }
-                            }
-                            throw new UnexpectedResponseException(messageID, result);
+        private final LDAPReader<ASN1BufferReader> reader;
+        private FilterChainContext context;
+
+        /**
+         * Creates a handler with the provided reader.
+         *
+         * @param reader
+         *            LDAP reader to use for reading incoming messages
+         */
+        public ClientResponseHandler(LDAPReader<ASN1BufferReader> reader) {
+            this.reader = reader;
+        }
+
+        void setFilterChainContext(FilterChainContext context) {
+            this.context = context;
+        }
+
+        /**
+         * Returns the LDAP reader.
+         *
+         * @return the reader to read incoming LDAP messages
+         */
+        public LDAPReader<ASN1BufferReader> getReader() {
+            return this.reader;
+        }
+
+        @Override
+        public void addResult(final int messageID, final Result result)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.removePendingRequest(messageID);
+
+                if (pendingRequest != null) {
+                    if (pendingRequest instanceof LDAPFutureResultImpl) {
+                        final LDAPFutureResultImpl future =
+                                (LDAPFutureResultImpl) pendingRequest;
+                        if (future.getRequest() instanceof AddRequest) {
+                            future.setResultOrError(result);
+                            return;
                         }
                     }
+                    throw new UnexpectedResponseException(messageID, result);
                 }
+            }
+        }
 
-                @Override
-                public void bindResult(final FilterChainContext ctx, final int messageID,
-                        final BindResult result) throws UnexpectedResponseException, IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.removePendingRequest(messageID);
+        @Override
+        public void bindResult(final int messageID, final BindResult result)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.removePendingRequest(messageID);
 
-                        if (pendingRequest != null) {
-                            if (pendingRequest instanceof LDAPBindFutureResultImpl) {
-                                final LDAPBindFutureResultImpl future =
-                                        ((LDAPBindFutureResultImpl) pendingRequest);
-                                final BindClient bindClient = future.getBindClient();
+                if (pendingRequest != null) {
+                    if (pendingRequest instanceof LDAPBindFutureResultImpl) {
+                        final LDAPBindFutureResultImpl future =
+                                ((LDAPBindFutureResultImpl) pendingRequest);
+                        final BindClient bindClient = future.getBindClient();
 
+                        try {
+                            if (!bindClient.evaluateResult(result)) {
+                                // The server is expecting a multi stage
+                                // bind response.
+                                final int msgID =
+                                        ldapConnection.continuePendingBindRequest(future);
+
+                                LDAPWriter<ASN1BufferWriter> ldapWriter = GrizzlyUtils.getWriter();
                                 try {
-                                    if (!bindClient.evaluateResult(result)) {
-                                        // The server is expecting a multi stage
-                                        // bind response.
-                                        final int msgID =
-                                                ldapConnection.continuePendingBindRequest(future);
-
-                                        final ASN1BufferWriter asn1Writer =
-                                                ASN1BufferWriter.getWriter();
-                                        try {
-                                            final GenericBindRequest nextRequest =
-                                                    bindClient.nextBindRequest();
-                                            new LDAPWriter().bindRequest(asn1Writer, msgID, 3,
-                                                    nextRequest);
-                                            ctx.write(asn1Writer.getBuffer(), null);
-                                        } finally {
-                                            asn1Writer.recycle();
-                                        }
-                                        return;
-                                    }
-                                } catch (final ErrorResultException e) {
-                                    ldapConnection.setBindOrStartTLSInProgress(false);
-                                    future.adaptErrorResult(e.getResult());
-                                    return;
-                                } catch (final IOException e) {
-                                    // FIXME: I18N need to have a better error
-                                    // message.
-                                    // FIXME: Is this the best result code?
-                                    ldapConnection.setBindOrStartTLSInProgress(false);
-                                    final Result errorResult =
-                                            Responses
-                                                    .newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
-                                                    .setDiagnosticMessage(
-                                                            "An error occurred during multi-stage authentication")
-                                                    .setCause(e);
-                                    future.adaptErrorResult(errorResult);
-                                    return;
+                                    final GenericBindRequest nextRequest =
+                                            bindClient.nextBindRequest();
+                                    ldapWriter.writeBindRequest(msgID, 3, nextRequest);
+                                    context.write(ldapWriter.getASN1Writer().getBuffer(), null);
+                                } finally {
+                                    GrizzlyUtils.recycleWriter(ldapWriter);
                                 }
-
-                                if (result.getResultCode() == ResultCode.SUCCESS) {
-                                    final ConnectionSecurityLayer l =
-                                            bindClient.getConnectionSecurityLayer();
-                                    if (l != null) {
-                                        // The connection needs to be secured by
-                                        // the SASL mechanism.
-                                        ldapConnection
-                                                .installFilter(new ConnectionSecurityLayerFilter(l,
-                                                        ctx.getConnection().getTransport()
-                                                                .getMemoryManager()));
-                                    }
-                                }
-
-                                ldapConnection.setBindOrStartTLSInProgress(false);
-                                future.setResultOrError(result);
                                 return;
                             }
-                            throw new UnexpectedResponseException(messageID, result);
+                        } catch (final ErrorResultException e) {
+                            ldapConnection.setBindOrStartTLSInProgress(false);
+                            future.adaptErrorResult(e.getResult());
+                            return;
+                        } catch (final IOException e) {
+                            // FIXME: I18N need to have a better error
+                            // message.
+                            // FIXME: Is this the best result code?
+                            ldapConnection.setBindOrStartTLSInProgress(false);
+                            final Result errorResult =
+                                    Responses
+                                            .newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+                                            .setDiagnosticMessage(
+                                                    "An error occurred during multi-stage authentication")
+                                            .setCause(e);
+                            future.adaptErrorResult(errorResult);
+                            return;
                         }
-                    }
-                }
 
-                @Override
-                public void compareResult(final FilterChainContext ctx, final int messageID,
-                        final CompareResult result) throws UnexpectedResponseException, IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.removePendingRequest(messageID);
-
-                        if (pendingRequest != null) {
-                            if (pendingRequest instanceof LDAPCompareFutureResultImpl) {
-                                final LDAPCompareFutureResultImpl future =
-                                        (LDAPCompareFutureResultImpl) pendingRequest;
-                                future.setResultOrError(result);
-                                return;
+                        if (result.getResultCode() == ResultCode.SUCCESS) {
+                            final ConnectionSecurityLayer l =
+                                    bindClient.getConnectionSecurityLayer();
+                            if (l != null) {
+                                // The connection needs to be secured by
+                                // the SASL mechanism.
+                                ldapConnection
+                                        .installFilter(new ConnectionSecurityLayerFilter(l,
+                                                context.getConnection().getTransport()
+                                                        .getMemoryManager()));
                             }
-                            throw new UnexpectedResponseException(messageID, result);
+                        }
+
+                        ldapConnection.setBindOrStartTLSInProgress(false);
+                        future.setResultOrError(result);
+                        return;
+                    }
+                    throw new UnexpectedResponseException(messageID, result);
+                }
+            }
+        }
+
+        @Override
+        public void compareResult(final int messageID, final CompareResult result)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.removePendingRequest(messageID);
+
+                if (pendingRequest != null) {
+                    if (pendingRequest instanceof LDAPCompareFutureResultImpl) {
+                        final LDAPCompareFutureResultImpl future =
+                                (LDAPCompareFutureResultImpl) pendingRequest;
+                        future.setResultOrError(result);
+                        return;
+                    }
+                    throw new UnexpectedResponseException(messageID, result);
+                }
+            }
+        }
+
+        @Override
+        public void deleteResult(final int messageID, final Result result)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.removePendingRequest(messageID);
+
+                if (pendingRequest != null) {
+                    if (pendingRequest instanceof LDAPFutureResultImpl) {
+                        final LDAPFutureResultImpl future =
+                                (LDAPFutureResultImpl) pendingRequest;
+                        if (future.getRequest() instanceof DeleteRequest) {
+                            future.setResultOrError(result);
+                            return;
                         }
                     }
+                    throw new UnexpectedResponseException(messageID, result);
                 }
+            }
+        }
 
-                @Override
-                public void deleteResult(final FilterChainContext ctx, final int messageID,
-                        final Result result) throws UnexpectedResponseException, IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.removePendingRequest(messageID);
-
-                        if (pendingRequest != null) {
-                            if (pendingRequest instanceof LDAPFutureResultImpl) {
-                                final LDAPFutureResultImpl future =
-                                        (LDAPFutureResultImpl) pendingRequest;
-                                if (future.getRequest() instanceof DeleteRequest) {
-                                    future.setResultOrError(result);
-                                    return;
-                                }
-                            }
-                            throw new UnexpectedResponseException(messageID, result);
-                        }
+        @Override
+        public void extendedResult(final int messageID, final ExtendedResult result)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                if (messageID == 0) {
+                    // Unsolicited notification received.
+                    if ((result.getOID() != null)
+                            && result.getOID().equals(OID_NOTICE_OF_DISCONNECTION)) {
+                        // Treat this as a connection error.
+                        final Result errorResult =
+                                Responses
+                                        .newResult(result.getResultCode())
+                                        .setDiagnosticMessage(result.getDiagnosticMessage());
+                        ldapConnection.close(null, true, errorResult);
+                    } else {
+                        ldapConnection.handleUnsolicitedNotification(result);
                     }
-                }
+                } else {
+                    final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                            ldapConnection.removePendingRequest(messageID);
 
-                @Override
-                public void extendedResult(final FilterChainContext ctx, final int messageID,
-                        final ExtendedResult result) throws UnexpectedResponseException,
-                        IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        if (messageID == 0) {
-                            // Unsolicited notification received.
-                            if ((result.getOID() != null)
-                                    && result.getOID().equals(OID_NOTICE_OF_DISCONNECTION)) {
-                                // Treat this as a connection error.
+                    if (pendingRequest != null) {
+                        if (pendingRequest instanceof LDAPExtendedFutureResultImpl<?>) {
+                            final LDAPExtendedFutureResultImpl<?> extendedFuture =
+                                    ((LDAPExtendedFutureResultImpl<?>) pendingRequest);
+                            try {
+                                handleExtendedResult0(ldapConnection, extendedFuture,
+                                        result);
+                            } catch (final DecodeException de) {
+                                // FIXME: should the connection be closed as
+                                // well?
                                 final Result errorResult =
-                                        Responses
-                                                .newResult(result.getResultCode())
-                                                .setDiagnosticMessage(result.getDiagnosticMessage());
-                                ldapConnection.close(null, true, errorResult);
-                            } else {
-                                ldapConnection.handleUnsolicitedNotification(result);
+                                        Responses.newResult(
+                                                ResultCode.CLIENT_SIDE_DECODING_ERROR)
+                                                .setDiagnosticMessage(
+                                                        de.getLocalizedMessage()).setCause(
+                                                        de);
+                                extendedFuture.adaptErrorResult(errorResult);
                             }
                         } else {
-                            final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                    ldapConnection.removePendingRequest(messageID);
+                            throw new UnexpectedResponseException(messageID, result);
+                        }
+                    }
+                }
+            }
+        }
 
-                            if (pendingRequest != null) {
-                                if (pendingRequest instanceof LDAPExtendedFutureResultImpl<?>) {
-                                    final LDAPExtendedFutureResultImpl<?> extendedFuture =
-                                            ((LDAPExtendedFutureResultImpl<?>) pendingRequest);
-                                    try {
-                                        handleExtendedResult0(ldapConnection, extendedFuture,
-                                                result);
-                                    } catch (final DecodeException de) {
-                                        // FIXME: should the connection be closed as
-                                        // well?
+        @Override
+        public void intermediateResponse(final int messageID, final IntermediateResponse response)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.getPendingRequest(messageID);
+
+                if (pendingRequest != null) {
+                    pendingRequest.handleIntermediateResponse(response);
+                }
+            }
+        }
+
+        @Override
+        public void modifyDNResult(final int messageID, final Result result)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.removePendingRequest(messageID);
+
+                if (pendingRequest != null) {
+                    if (pendingRequest instanceof LDAPFutureResultImpl) {
+                        final LDAPFutureResultImpl future =
+                                (LDAPFutureResultImpl) pendingRequest;
+                        if (future.getRequest() instanceof ModifyDNRequest) {
+                            future.setResultOrError(result);
+                            return;
+                        }
+                    }
+                    throw new UnexpectedResponseException(messageID, result);
+                }
+            }
+        }
+
+        @Override
+        public void modifyResult(final int messageID, final Result result)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.removePendingRequest(messageID);
+
+                if (pendingRequest != null) {
+                    if (pendingRequest instanceof LDAPFutureResultImpl) {
+                        final LDAPFutureResultImpl future =
+                                (LDAPFutureResultImpl) pendingRequest;
+                        if (future.getRequest() instanceof ModifyRequest) {
+                            future.setResultOrError(result);
+                            return;
+                        }
+                    }
+                    throw new UnexpectedResponseException(messageID, result);
+                }
+            }
+        }
+
+        @Override
+        public void searchResult(final int messageID, final Result result)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.removePendingRequest(messageID);
+
+                if (pendingRequest != null) {
+                    if (pendingRequest instanceof LDAPSearchFutureResultImpl) {
+                        ((LDAPSearchFutureResultImpl) pendingRequest)
+                                .setResultOrError(result);
+                    } else {
+                        throw new UnexpectedResponseException(messageID, result);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void searchResultEntry(final int messageID, final SearchResultEntry entry)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.getPendingRequest(messageID);
+
+                if (pendingRequest != null) {
+                    if (pendingRequest instanceof LDAPSearchFutureResultImpl) {
+                        ((LDAPSearchFutureResultImpl) pendingRequest).handleEntry(entry);
+                    } else {
+                        throw new UnexpectedResponseException(messageID, entry);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void searchResultReference(final int messageID, final SearchResultReference reference)
+                throws UnexpectedResponseException, IOException {
+            final GrizzlyLDAPConnection ldapConnection =
+                    LDAP_CONNECTION_ATTR.get(context.getConnection());
+            if (ldapConnection != null) {
+                final AbstractLDAPFutureResultImpl<?> pendingRequest =
+                        ldapConnection.getPendingRequest(messageID);
+
+                if (pendingRequest != null) {
+                    if (pendingRequest instanceof LDAPSearchFutureResultImpl) {
+                        ((LDAPSearchFutureResultImpl) pendingRequest)
+                                .handleReference(reference);
+                    } else {
+                        throw new UnexpectedResponseException(messageID, reference);
+                    }
+                }
+            }
+        }
+
+        // Needed in order to expose type information.
+        private <R extends ExtendedResult> void handleExtendedResult0(
+                final GrizzlyLDAPConnection conn, final LDAPExtendedFutureResultImpl<R> future,
+                final ExtendedResult result) throws DecodeException {
+            final R decodedResponse =
+                    future.decodeResult(result, conn.getLDAPOptions().getDecodeOptions());
+
+            if (future.getRequest() instanceof StartTLSExtendedRequest) {
+                if (result.getResultCode() == ResultCode.SUCCESS) {
+                    try {
+                        final StartTLSExtendedRequest request =
+                                (StartTLSExtendedRequest) future.getRequest();
+                        conn.startTLS(request.getSSLContext(), request
+                                .getEnabledProtocols(), request.getEnabledCipherSuites(),
+                                new EmptyCompletionHandler<SSLEngine>() {
+                                    @Override
+                                    public void completed(final SSLEngine result) {
+                                        conn.setBindOrStartTLSInProgress(false);
+                                        future.setResultOrError(decodedResponse);
+                                    }
+
+                                    @Override
+                                    public void failed(final Throwable throwable) {
                                         final Result errorResult =
                                                 Responses.newResult(
-                                                        ResultCode.CLIENT_SIDE_DECODING_ERROR)
+                                                        ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+                                                        .setCause(throwable)
                                                         .setDiagnosticMessage(
-                                                                de.getLocalizedMessage()).setCause(
-                                                                de);
-                                        extendedFuture.adaptErrorResult(errorResult);
+                                                                "SSL handshake failed");
+                                        conn.setBindOrStartTLSInProgress(false);
+                                        conn.close(null, false, errorResult);
+                                        future.adaptErrorResult(errorResult);
                                     }
-                                } else {
-                                    throw new UnexpectedResponseException(messageID, result);
-                                }
-                            }
-                        }
+                                });
+                        return;
+                    } catch (final IOException e) {
+                        final Result errorResult =
+                                Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+                                        .setCause(e).setDiagnosticMessage(e.getMessage());
+                        future.adaptErrorResult(errorResult);
+                        conn.close(null, false, errorResult);
+                        return;
                     }
                 }
+            }
 
-                @Override
-                public void intermediateResponse(final FilterChainContext ctx, final int messageID,
-                        final IntermediateResponse response) throws UnexpectedResponseException,
-                        IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.getPendingRequest(messageID);
+            future.setResultOrError(decodedResponse);
+        }
+    }
 
-                        if (pendingRequest != null) {
-                            pendingRequest.handleIntermediateResponse(response);
-                        }
-                    }
-                }
-
-                @Override
-                public void modifyDNResult(final FilterChainContext ctx, final int messageID,
-                        final Result result) throws UnexpectedResponseException, IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.removePendingRequest(messageID);
-
-                        if (pendingRequest != null) {
-                            if (pendingRequest instanceof LDAPFutureResultImpl) {
-                                final LDAPFutureResultImpl future =
-                                        (LDAPFutureResultImpl) pendingRequest;
-                                if (future.getRequest() instanceof ModifyDNRequest) {
-                                    future.setResultOrError(result);
-                                    return;
-                                }
-                            }
-                            throw new UnexpectedResponseException(messageID, result);
-                        }
-                    }
-                }
-
-                @Override
-                public void modifyResult(final FilterChainContext ctx, final int messageID,
-                        final Result result) throws UnexpectedResponseException, IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.removePendingRequest(messageID);
-
-                        if (pendingRequest != null) {
-                            if (pendingRequest instanceof LDAPFutureResultImpl) {
-                                final LDAPFutureResultImpl future =
-                                        (LDAPFutureResultImpl) pendingRequest;
-                                if (future.getRequest() instanceof ModifyRequest) {
-                                    future.setResultOrError(result);
-                                    return;
-                                }
-                            }
-                            throw new UnexpectedResponseException(messageID, result);
-                        }
-                    }
-                }
-
-                @Override
-                public void searchResult(final FilterChainContext ctx, final int messageID,
-                        final Result result) throws UnexpectedResponseException, IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.removePendingRequest(messageID);
-
-                        if (pendingRequest != null) {
-                            if (pendingRequest instanceof LDAPSearchFutureResultImpl) {
-                                ((LDAPSearchFutureResultImpl) pendingRequest)
-                                        .setResultOrError(result);
-                            } else {
-                                throw new UnexpectedResponseException(messageID, result);
-                            }
-                        }
-                    }
-                }
-
-                @Override
-                public void searchResultEntry(final FilterChainContext ctx, final int messageID,
-                        final SearchResultEntry entry) throws UnexpectedResponseException,
-                        IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.getPendingRequest(messageID);
-
-                        if (pendingRequest != null) {
-                            if (pendingRequest instanceof LDAPSearchFutureResultImpl) {
-                                ((LDAPSearchFutureResultImpl) pendingRequest).handleEntry(entry);
-                            } else {
-                                throw new UnexpectedResponseException(messageID, entry);
-                            }
-                        }
-                    }
-                }
-
-                @Override
-                public void searchResultReference(final FilterChainContext ctx,
-                        final int messageID, final SearchResultReference reference)
-                        throws UnexpectedResponseException, IOException {
-                    final GrizzlyLDAPConnection ldapConnection =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (ldapConnection != null) {
-                        final AbstractLDAPFutureResultImpl<?> pendingRequest =
-                                ldapConnection.getPendingRequest(messageID);
-
-                        if (pendingRequest != null) {
-                            if (pendingRequest instanceof LDAPSearchFutureResultImpl) {
-                                ((LDAPSearchFutureResultImpl) pendingRequest)
-                                        .handleReference(reference);
-                            } else {
-                                throw new UnexpectedResponseException(messageID, reference);
-                            }
-                        }
-                    }
-                }
-
-                // Needed in order to expose type information.
-                private <R extends ExtendedResult> void handleExtendedResult0(
-                        final GrizzlyLDAPConnection conn, final LDAPExtendedFutureResultImpl<R> future,
-                        final ExtendedResult result) throws DecodeException {
-                    final R decodedResponse =
-                            future.decodeResult(result, conn.getLDAPOptions().getDecodeOptions());
-
-                    if (future.getRequest() instanceof StartTLSExtendedRequest) {
-                        if (result.getResultCode() == ResultCode.SUCCESS) {
-                            try {
-                                final StartTLSExtendedRequest request =
-                                        (StartTLSExtendedRequest) future.getRequest();
-                                conn.startTLS(request.getSSLContext(), request
-                                        .getEnabledProtocols(), request.getEnabledCipherSuites(),
-                                        new EmptyCompletionHandler<SSLEngine>() {
-                                            @Override
-                                            public void completed(final SSLEngine result) {
-                                                conn.setBindOrStartTLSInProgress(false);
-                                                future.setResultOrError(decodedResponse);
-                                            }
-
-                                            @Override
-                                            public void failed(final Throwable throwable) {
-                                                final Result errorResult =
-                                                        Responses.newResult(
-                                                                ResultCode.CLIENT_SIDE_LOCAL_ERROR)
-                                                                .setCause(throwable)
-                                                                .setDiagnosticMessage(
-                                                                        "SSL handshake failed");
-                                                conn.setBindOrStartTLSInProgress(false);
-                                                conn.close(null, false, errorResult);
-                                                future.adaptErrorResult(errorResult);
-                                            }
-                                        });
-                                return;
-                            } catch (final IOException e) {
-                                final Result errorResult =
-                                        Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
-                                                .setCause(e).setDiagnosticMessage(e.getMessage());
-                                future.adaptErrorResult(errorResult);
-                                conn.close(null, false, errorResult);
-                                return;
-                            }
-                        }
-                    }
-
-                    future.setResultOrError(decodedResponse);
-                }
-            };
-
-    LDAPClientFilter(final LDAPReader ldapReader, final int maxASN1ElementSize) {
-        this.ldapReader = ldapReader;
+    /**
+     * Creates a client filter with provided options and max size of ASN1
+     * elements.
+     *
+     * @param options
+     *            allow to control how request and responses are decoded
+     * @param maxASN1ElementSize
+     *            The maximum BER element size, or <code>0</code> to indicate
+     *            that there is no limit.
+     */
+    LDAPClientFilter(final DecodeOptions options, final int maxASN1ElementSize) {
+        this.decodeOptions = options;
         this.maxASN1ElementSize = maxASN1ElementSize;
     }
 
@@ -490,27 +526,23 @@
 
     @Override
     public NextAction handleRead(final FilterChainContext ctx) throws IOException {
+        final ClientResponseHandler handler = getResponseHandler(ctx);
+        final LDAPReader<ASN1BufferReader> reader = handler.getReader();
+        final ASN1BufferReader asn1Reader = reader.getASN1Reader();
         final Buffer buffer = (Buffer) ctx.getMessage();
-        ASN1BufferReader asn1Reader = LDAP_ASN1_READER_ATTR.get(ctx.getConnection());
-        if (asn1Reader == null) {
-            asn1Reader =
-                    new ASN1BufferReader(maxASN1ElementSize, ctx.getConnection().getTransport()
-                            .getMemoryManager());
-            LDAP_ASN1_READER_ATTR.set(ctx.getConnection(), asn1Reader);
-        }
-        asn1Reader.appendBytesRead(buffer);
 
+        asn1Reader.appendBytesRead(buffer);
         try {
-            while (asn1Reader.elementAvailable()) {
-                ldapReader.decode(asn1Reader, CLIENT_RESPONSE_HANDLER, ctx);
+            while (reader.hasMessageAvailable()) {
+                reader.readMessage(handler);
             }
-        } catch (IOException ioe) {
+        } catch (IOException e) {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(ctx.getConnection());
             final Result errorResult =
-                    Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(ioe)
-                            .setDiagnosticMessage(ioe.getMessage());
+                    Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(e)
+                            .setDiagnosticMessage(e.getMessage());
             ldapConnection.close(null, false, errorResult);
-            throw ioe;
+            throw e;
         } finally {
             asn1Reader.disposeBytesRead();
         }
@@ -518,6 +550,39 @@
         return ctx.getStopAction();
     }
 
+    /**
+     * Returns the response handler associated to the provided connection and
+     * context.
+     * <p>
+     * If no handler exists yet for this context, a new one is created and
+     * recorded for the connection.
+     *
+     * @param ctx
+     *            current filter chain context
+     * @return the response handler associated to the context, which can be a
+     *         new one if no handler have been created yet
+     */
+    private ClientResponseHandler getResponseHandler(final FilterChainContext ctx) {
+        Connection<?> connection = ctx.getConnection();
+        ClientResponseHandler handler = RESPONSE_HANDLER_ATTR.get(connection);
+        if (handler == null) {
+            LDAPReader<ASN1BufferReader> reader = GrizzlyUtils.createReader(decodeOptions,
+                    maxASN1ElementSize, connection.getTransport().getMemoryManager());
+            handler = new ClientResponseHandler(reader);
+            RESPONSE_HANDLER_ATTR.set(connection, handler);
+        }
+        handler.setFilterChainContext(ctx);
+        return handler;
+    }
+
+    /**
+     * Associate a LDAP connection to the provided Grizzly connection.
+     *
+     * @param connection
+     *            Grizzly connection
+     * @param ldapConnection
+     *            LDAP connection
+     */
     void registerConnection(final Connection<?> connection, final GrizzlyLDAPConnection ldapConnection) {
         LDAP_CONNECTION_ATTR.set(connection, ldapConnection);
     }
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPMessageHandler.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPMessageHandler.java
deleted file mode 100644
index 7104540..0000000
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPMessageHandler.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * 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 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
- *
- *
- *      Copyright 2009 Sun Microsystems, Inc.
- */
-
-package com.forgerock.opendj.grizzly;
-
-import java.io.IOException;
-
-import org.forgerock.opendj.ldap.ByteString;
-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.ExtendedRequest;
-import org.forgerock.opendj.ldap.requests.GenericBindRequest;
-import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
-import org.forgerock.opendj.ldap.requests.ModifyRequest;
-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.ExtendedResult;
-import org.forgerock.opendj.ldap.responses.IntermediateResponse;
-import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.opendj.ldap.responses.SearchResultEntry;
-import org.forgerock.opendj.ldap.responses.SearchResultReference;
-
-/**
- * LDAP message handler interface.
- *
- * @param <P>
- *            A user provided handler parameter.
- */
-interface LDAPMessageHandler<P> {
-    void abandonRequest(P param, int messageID, AbandonRequest request)
-            throws UnexpectedRequestException, IOException;
-
-    void addRequest(P param, int messageID, AddRequest request) throws UnexpectedRequestException,
-            IOException;
-
-    void addResult(P param, int messageID, Result result) throws UnexpectedResponseException,
-            IOException;
-
-    void bindRequest(P param, int messageID, int version, GenericBindRequest request)
-            throws UnexpectedRequestException, IOException;
-
-    void bindResult(P param, int messageID, BindResult result) throws UnexpectedResponseException,
-            IOException;
-
-    void compareRequest(P param, int messageID, CompareRequest request)
-            throws UnexpectedRequestException, IOException;
-
-    void compareResult(P param, int messageID, CompareResult result)
-            throws UnexpectedResponseException, IOException;
-
-    void deleteRequest(P param, int messageID, DeleteRequest request)
-            throws UnexpectedRequestException, IOException;
-
-    void deleteResult(P param, int messageID, Result result) throws UnexpectedResponseException,
-            IOException;
-
-    <R extends ExtendedResult> void extendedRequest(P param, int messageID,
-            ExtendedRequest<R> request) throws UnexpectedRequestException, IOException;
-
-    void extendedResult(P param, int messageID, ExtendedResult result)
-            throws UnexpectedResponseException, IOException;
-
-    void intermediateResponse(P param, int messageID, IntermediateResponse response)
-            throws UnexpectedResponseException, IOException;
-
-    void modifyDNRequest(P param, int messageID, ModifyDNRequest request)
-            throws UnexpectedRequestException, IOException;
-
-    void modifyDNResult(P param, int messageID, Result result) throws UnexpectedResponseException,
-            IOException;
-
-    void modifyRequest(P param, int messageID, ModifyRequest request)
-            throws UnexpectedRequestException, IOException;
-
-    void modifyResult(P param, int messageID, Result result) throws UnexpectedResponseException,
-            IOException;
-
-    void searchRequest(P param, int messageID, SearchRequest request)
-            throws UnexpectedRequestException, IOException;
-
-    void searchResult(P param, int messageID, Result result) throws UnexpectedResponseException,
-            IOException;
-
-    void searchResultEntry(P param, int messageID, SearchResultEntry entry)
-            throws UnexpectedResponseException, IOException;
-
-    void searchResultReference(P param, int messageID, SearchResultReference reference)
-            throws UnexpectedResponseException, IOException;
-
-    void unbindRequest(P param, int messageID, UnbindRequest request)
-            throws UnexpectedRequestException, IOException;
-
-    void unrecognizedMessage(P param, int messageID, byte messageTag, ByteString messageBytes)
-            throws UnsupportedMessageException, IOException;
-}
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPServerFilter.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPServerFilter.java
index f0c9f43..33c5d61 100644
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPServerFilter.java
+++ b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPServerFilter.java
@@ -38,8 +38,11 @@
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLSession;
 
+import org.forgerock.opendj.io.LDAPReader;
+import org.forgerock.opendj.io.LDAPWriter;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ConnectionSecurityLayer;
+import org.forgerock.opendj.ldap.DecodeOptions;
 import org.forgerock.opendj.ldap.ErrorResultException;
 import org.forgerock.opendj.ldap.IntermediateResponseHandler;
 import org.forgerock.opendj.ldap.LDAPClientContext;
@@ -69,6 +72,9 @@
 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.spi.AbstractLDAPMessageHandler;
+import org.forgerock.opendj.ldap.spi.UnexpectedRequestException;
+import org.forgerock.opendj.ldap.spi.UnsupportedMessageException;
 import org.glassfish.grizzly.Buffer;
 import org.glassfish.grizzly.Connection;
 import org.glassfish.grizzly.Grizzly;
@@ -89,33 +95,113 @@
  * side logic for SSL and SASL operations over LDAP.
  */
 final class LDAPServerFilter extends BaseFilter {
-    private abstract class AbstractHandler<R extends Result> implements
+
+    /**
+     * Provides an arbitrary write operation on a LDAP writer.
+     */
+    private interface LDAPWrite<T> {
+        void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, T message) throws IOException;
+    }
+
+    /**
+     * Write operation for intermediate responses.
+     */
+    private static final LDAPWrite<IntermediateResponse> INTERMEDIATE = new LDAPWrite<IntermediateResponse>() {
+        public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, IntermediateResponse resp)
+                throws IOException {
+            writer.writeIntermediateResponse(messageID, resp);
+        }
+    };
+
+    private static abstract class AbstractHandler<R extends Result> implements
             IntermediateResponseHandler, ResultHandler<R> {
         protected final ClientContextImpl context;
         protected final int messageID;
 
+
         protected AbstractHandler(final ClientContextImpl context, final int messageID) {
             this.messageID = messageID;
             this.context = context;
         }
 
         @Override
+        public void handleResult(final R result) {
+            defaultHandleResult(result);
+        }
+
+        @Override
         public final boolean handleIntermediateResponse(final IntermediateResponse response) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            writeMessage(INTERMEDIATE, response);
+            return true;
+        }
+
+        /**
+         * Default implementation of result handling, that delegate
+         * the actual write operation to {@code writeResult} method.
+         */
+        private void defaultHandleResult(final R result) {
+            writeMessage(new LDAPWrite<R>() {
+                public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, R res) throws IOException {
+                    writeResult(writer, res);
+                }
+            }, result);
+        }
+
+        /**
+         * Write a result to provided LDAP writer.
+         *
+         * @param ldapWriter
+         *            provided writer
+         * @param result
+         *            to write
+         * @throws IOException
+         *             if an error occurs during writing
+         */
+        abstract protected void writeResult(final LDAPWriter<ASN1BufferWriter> ldapWriter, final R result)
+                throws IOException;
+
+        /**
+         * Write a message on LDAP writer.
+         *
+         * @param <T>
+         *            type of message to write
+         * @param ldapWrite
+         *            the specific write operation
+         * @param message
+         *            the message to write
+         */
+        protected final <T> void writeMessage(final LDAPWrite<T> ldapWrite, final T message) {
+            final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
             try {
-                LDAP_WRITER.intermediateResponse(asn1Writer, messageID, response);
-                context.write(asn1Writer);
+                ldapWrite.perform(writer, messageID, message);
+                context.write(writer);
             } catch (final IOException ioe) {
                 context.handleError(ioe);
-                return false;
             } finally {
-                asn1Writer.recycle();
+                GrizzlyUtils.recycleWriter(writer);
             }
-            return true;
+        }
+
+        /**
+         * Copy diagnostic message, matched DN and cause to new result from the
+         * given result.
+         *
+         * @param newResult
+         *            to update
+         * @param result
+         *            contains parameters to copy
+         */
+        protected final void populateNewResultFromResult(final R newResult, final Result result) {
+            newResult.setDiagnosticMessage(result.getDiagnosticMessage());
+            newResult.setMatchedDN(result.getMatchedDN());
+            newResult.setCause(result.getCause());
+            for (final Control control : result.getControls()) {
+                newResult.addControl(control);
+            }
         }
     }
 
-    private final class AddHandler extends AbstractHandler<Result> {
+    private static final class AddHandler extends AbstractHandler<Result> {
         private AddHandler(final ClientContextImpl context, final int messageID) {
             super(context, messageID);
         }
@@ -126,20 +212,13 @@
         }
 
         @Override
-        public void handleResult(final Result result) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.addResult(asn1Writer, messageID, result);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-            } finally {
-                asn1Writer.recycle();
-            }
+        public void writeResult(LDAPWriter<ASN1BufferWriter> writer, final Result result)
+                throws IOException {
+            writer.writeAddResult(messageID, result);
         }
     }
 
-    private final class BindHandler extends AbstractHandler<BindResult> {
+    private static final class BindHandler extends AbstractHandler<BindResult> {
         private BindHandler(final ClientContextImpl context, final int messageID) {
             super(context, messageID);
         }
@@ -151,31 +230,19 @@
                 handleResult((BindResult) result);
             } else {
                 final BindResult newResult = Responses.newBindResult(result.getResultCode());
-                newResult.setDiagnosticMessage(result.getDiagnosticMessage());
-                newResult.setMatchedDN(result.getMatchedDN());
-                newResult.setCause(result.getCause());
-                for (final Control control : result.getControls()) {
-                    newResult.addControl(control);
-                }
+                populateNewResultFromResult(newResult, result);
                 handleResult(newResult);
             }
         }
 
         @Override
-        public void handleResult(final BindResult result) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.bindResult(asn1Writer, messageID, result);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-            } finally {
-                asn1Writer.recycle();
-            }
+        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, BindResult result)
+                throws IOException {
+            writer.writeBindResult(messageID, result);
         }
     }
 
-    private final class ClientContextImpl implements LDAPClientContext {
+    private static final class ClientContextImpl implements LDAPClientContext {
         private final Connection<?> connection;
         private final AtomicBoolean isClosed = new AtomicBoolean();
         private ServerConnection<Integer> serverConnection = null;
@@ -263,14 +330,14 @@
 
         @Override
         public void sendUnsolicitedNotification(final ExtendedResult notification) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
             try {
-                LDAP_WRITER.extendedResult(asn1Writer, 0, notification);
-                connection.write(asn1Writer.getBuffer(), null);
+                writer.writeExtendedResult(0, notification);
+                connection.write(writer.getASN1Writer().getBuffer(), null);
             } catch (final IOException ioe) {
                 handleError(ioe);
             } finally {
-                asn1Writer.recycle();
+                GrizzlyUtils.recycleWriter(writer);
             }
         }
 
@@ -288,8 +355,8 @@
             return builder.toString();
         }
 
-        public void write(final ASN1BufferWriter asn1Writer) {
-            connection.write(asn1Writer.getBuffer(), null);
+        public void write(final LDAPWriter<ASN1BufferWriter> writer) {
+            connection.write(writer.getASN1Writer().getBuffer(), null);
         }
 
         private void disconnect0(final ResultCode resultCode, final String message) {
@@ -385,7 +452,7 @@
         }
     }
 
-    private final class CompareHandler extends AbstractHandler<CompareResult> {
+    private static final class CompareHandler extends AbstractHandler<CompareResult> {
         private CompareHandler(final ClientContextImpl context, final int messageID) {
             super(context, messageID);
         }
@@ -397,31 +464,20 @@
                 handleResult((CompareResult) result);
             } else {
                 final CompareResult newResult = Responses.newCompareResult(result.getResultCode());
-                newResult.setDiagnosticMessage(result.getDiagnosticMessage());
-                newResult.setMatchedDN(result.getMatchedDN());
-                newResult.setCause(result.getCause());
-                for (final Control control : result.getControls()) {
-                    newResult.addControl(control);
-                }
+                populateNewResultFromResult(newResult, result);
                 handleResult(newResult);
             }
         }
 
         @Override
-        public void handleResult(final CompareResult result) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.compareResult(asn1Writer, messageID, result);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-            } finally {
-                asn1Writer.recycle();
-            }
+        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, CompareResult result)
+                throws IOException {
+            writer.writeCompareResult(messageID, result);
+
         }
     }
 
-    private final class DeleteHandler extends AbstractHandler<Result> {
+    private static final class DeleteHandler extends AbstractHandler<Result> {
         private DeleteHandler(final ClientContextImpl context, final int messageID) {
             super(context, messageID);
         }
@@ -432,20 +488,13 @@
         }
 
         @Override
-        public void handleResult(final Result result) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.deleteResult(asn1Writer, messageID, result);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-            } finally {
-                asn1Writer.recycle();
-            }
+        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result)
+                throws IOException {
+            writer.writeDeleteResult(messageID, result);
         }
     }
 
-    private final class ExtendedHandler<R extends ExtendedResult> extends AbstractHandler<R> {
+    private static final class ExtendedHandler<R extends ExtendedResult> extends AbstractHandler<R> {
         private ExtendedHandler(final ClientContextImpl context, final int messageID) {
             super(context, messageID);
         }
@@ -470,19 +519,21 @@
 
         @Override
         public void handleResult(final ExtendedResult result) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.extendedResult(asn1Writer, messageID, result);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-            } finally {
-                asn1Writer.recycle();
-            }
+            writeMessage(new LDAPWrite<ExtendedResult>() {
+                public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, ExtendedResult message)
+                        throws IOException {
+                    writer.writeExtendedResult(messageID, message);
+                }
+            }, result);
+        }
+
+        @Override
+        protected void writeResult(LDAPWriter<ASN1BufferWriter> ldapWriter, R result) throws IOException {
+            // never called because handleResult(result) method is overriden in this class
         }
     }
 
-    private final class ModifyDNHandler extends AbstractHandler<Result> {
+    private static final class ModifyDNHandler extends AbstractHandler<Result> {
         private ModifyDNHandler(final ClientContextImpl context, final int messageID) {
             super(context, messageID);
         }
@@ -493,20 +544,13 @@
         }
 
         @Override
-        public void handleResult(final Result result) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.modifyDNResult(asn1Writer, messageID, result);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-            } finally {
-                asn1Writer.recycle();
-            }
+        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result)
+                throws IOException {
+            writer.writeModifyDNResult(messageID, result);
         }
     }
 
-    private final class ModifyHandler extends AbstractHandler<Result> {
+    private static final class ModifyHandler extends AbstractHandler<Result> {
         private ModifyHandler(final ClientContextImpl context, final int messageID) {
             super(context, messageID);
         }
@@ -517,20 +561,13 @@
         }
 
         @Override
-        public void handleResult(final Result result) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.modifyResult(asn1Writer, messageID, result);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-            } finally {
-                asn1Writer.recycle();
-            }
+        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result)
+                throws IOException {
+            writer.writeModifyResult(messageID, result);
         }
     }
 
-    private final class SearchHandler extends AbstractHandler<Result> implements
+    private static final class SearchHandler extends AbstractHandler<Result> implements
             SearchResultHandler {
         private SearchHandler(final ClientContextImpl context, final int messageID) {
             super(context, messageID);
@@ -538,16 +575,12 @@
 
         @Override
         public boolean handleEntry(final SearchResultEntry entry) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.searchResultEntry(asn1Writer, messageID, entry);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-                return false;
-            } finally {
-                asn1Writer.recycle();
-            }
+            writeMessage(new LDAPWrite<SearchResultEntry>() {
+                public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, SearchResultEntry sre)
+                        throws IOException {
+                    writer.writeSearchResultEntry(messageID, sre);
+                }
+            }, entry);
             return true;
         }
 
@@ -558,30 +591,19 @@
 
         @Override
         public boolean handleReference(final SearchResultReference reference) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.searchResultReference(asn1Writer, messageID, reference);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-                return false;
-            } finally {
-                asn1Writer.recycle();
-            }
+            writeMessage(new LDAPWrite<SearchResultReference>() {
+                public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, SearchResultReference ref)
+                        throws IOException {
+                    writer.writeSearchResultReference(messageID, ref);
+                }
+            }, reference);
             return true;
         }
 
         @Override
-        public void handleResult(final Result result) {
-            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-            try {
-                LDAP_WRITER.searchResult(asn1Writer, messageID, result);
-                context.write(asn1Writer);
-            } catch (final IOException ioe) {
-                context.handleError(ioe);
-            } finally {
-                asn1Writer.recycle();
-            }
+        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result)
+                throws IOException {
+            writer.writeSearchResult(messageID, result);
         }
     }
 
@@ -613,13 +635,11 @@
     // Default maximum request size for incoming requests.
     private static final int DEFAULT_MAX_REQUEST_SIZE = 5 * 1024 * 1024;
 
-    private static final Attribute<ASN1BufferReader> LDAP_ASN1_READER_ATTR =
-            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPASN1Reader");
-
     private static final Attribute<ClientContextImpl> LDAP_CONNECTION_ATTR =
             Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPServerConnection");
 
-    private static final LDAPWriter LDAP_WRITER = new LDAPWriter();
+    private static final Attribute<ServerRequestHandler> REQUEST_HANDLER_ATTR =
+            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("ServerRequestHandler");
 
     /**
      * A dummy SSL client engine configurator as SSLFilter only needs server
@@ -638,154 +658,191 @@
         }
     }
 
-    private final LDAPReader ldapReader;
     private final GrizzlyLDAPListener listener;
     private final int maxASN1ElementSize;
+    private final DecodeOptions decodeOptions;
 
-    private final AbstractLDAPMessageHandler<FilterChainContext> serverRequestHandler =
-            new AbstractLDAPMessageHandler<FilterChainContext>() {
-                @Override
-                public void abandonRequest(final FilterChainContext ctx, final int messageID,
-                        final AbandonRequest request) throws UnexpectedRequestException {
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (clientContext != null) {
-                        final ServerConnection<Integer> conn = clientContext.getServerConnection();
-                        conn.handleAbandon(messageID, request);
-                    }
-                }
+    private static final class ServerRequestHandler extends AbstractLDAPMessageHandler {
 
-                @Override
-                public void addRequest(final FilterChainContext ctx, final int messageID,
-                        final AddRequest request) throws UnexpectedRequestException {
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (clientContext != null) {
-                        final ServerConnection<Integer> conn = clientContext.getServerConnection();
-                        final AddHandler handler = new AddHandler(clientContext, messageID);
-                        conn.handleAdd(messageID, request, handler, handler);
-                    }
-                }
+        private final Connection<?> connection;
+        private final LDAPReader<ASN1BufferReader> reader;
 
-                @Override
-                public void bindRequest(final FilterChainContext ctx, final int messageID,
-                        final int version, final GenericBindRequest request)
-                        throws UnexpectedRequestException {
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (clientContext != null) {
-                        final ServerConnection<Integer> conn = clientContext.getServerConnection();
-                        final BindHandler handler = new BindHandler(clientContext, messageID);
-                        conn.handleBind(messageID, version, request, handler, handler);
-                    }
-                }
+        /**
+         * Creates the handler with a connection.
+         *
+         * @param connection
+         *            connection this handler is associated with
+         * @param reader
+         *            LDAP reader to use for reading incoming messages
+         */
+        ServerRequestHandler(Connection<?> connection, LDAPReader<ASN1BufferReader> reader) {
+            this.connection = connection;
+            this.reader = reader;
+        }
 
-                @Override
-                public void compareRequest(final FilterChainContext ctx, final int messageID,
-                        final CompareRequest request) throws UnexpectedRequestException {
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (clientContext != null) {
-                        final ServerConnection<Integer> conn = clientContext.getServerConnection();
-                        final CompareHandler handler = new CompareHandler(clientContext, messageID);
-                        conn.handleCompare(messageID, request, handler, handler);
-                    }
-                }
+        /**
+         * Returns the LDAP reader.
+         *
+         * @return the reader to read incoming LDAP messages
+         */
+        public LDAPReader<ASN1BufferReader> getReader() {
+            return reader;
+        }
 
-                @Override
-                public void deleteRequest(final FilterChainContext ctx, final int messageID,
-                        final DeleteRequest request) throws UnexpectedRequestException {
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (clientContext != null) {
-                        final ServerConnection<Integer> conn = clientContext.getServerConnection();
-                        final DeleteHandler handler = new DeleteHandler(clientContext, messageID);
-                        conn.handleDelete(messageID, request, handler, handler);
-                    }
-                }
+        @Override
+        public void abandonRequest(final int messageID, final AbandonRequest request)
+                throws UnexpectedRequestException {
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.get(connection);
+            if (clientContext != null) {
+                final ServerConnection<Integer> conn = clientContext.getServerConnection();
+                conn.handleAbandon(messageID, request);
+            }
+        }
 
-                @Override
-                public <R extends ExtendedResult> void extendedRequest(
-                        final FilterChainContext ctx, final int messageID,
-                        final ExtendedRequest<R> request) throws UnexpectedRequestException {
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (clientContext != null) {
-                        final ServerConnection<Integer> conn = clientContext.getServerConnection();
-                        final ExtendedHandler<R> handler =
-                                new ExtendedHandler<R>(clientContext, messageID);
-                        conn.handleExtendedRequest(messageID, request, handler, handler);
-                    }
-                }
+        @Override
+        public void addRequest(final int messageID, final AddRequest request) throws UnexpectedRequestException {
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.get(connection);
+            if (clientContext != null) {
+                final ServerConnection<Integer> conn = clientContext.getServerConnection();
+                final AddHandler handler = new AddHandler(clientContext, messageID);
+                conn.handleAdd(messageID, request, handler, handler);
+            }
+        }
 
-                @Override
-                public void modifyDNRequest(final FilterChainContext ctx, final int messageID,
-                        final ModifyDNRequest request) throws UnexpectedRequestException {
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (clientContext != null) {
-                        final ServerConnection<Integer> conn = clientContext.getServerConnection();
-                        final ModifyDNHandler handler =
-                                new ModifyDNHandler(clientContext, messageID);
-                        conn.handleModifyDN(messageID, request, handler, handler);
-                    }
-                }
+        @Override
+        public void bindRequest(final int messageID, final int version,
+                final GenericBindRequest request)
+                throws UnexpectedRequestException {
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.get(connection);
+            if (clientContext != null) {
+                final ServerConnection<Integer> conn = clientContext.getServerConnection();
+                final AbstractHandler<BindResult> handler = new BindHandler(clientContext, messageID);
+                conn.handleBind(messageID, version, request, handler, handler);
+            }
+        }
 
-                @Override
-                public void modifyRequest(final FilterChainContext ctx, final int messageID,
-                        final ModifyRequest request) throws UnexpectedRequestException {
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (clientContext != null) {
-                        final ServerConnection<Integer> conn = clientContext.getServerConnection();
-                        final ModifyHandler handler = new ModifyHandler(clientContext, messageID);
-                        conn.handleModify(messageID, request, handler, handler);
-                    }
-                }
+        @Override
+        public void compareRequest(final int messageID, final CompareRequest request)
+                throws UnexpectedRequestException {
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.get(connection);
+            if (clientContext != null) {
+                final ServerConnection<Integer> conn = clientContext.getServerConnection();
+                final CompareHandler handler = new CompareHandler(clientContext, messageID);
+                conn.handleCompare(messageID, request, handler, handler);
+            }
+        }
 
-                @Override
-                public void searchRequest(final FilterChainContext ctx, final int messageID,
-                        final SearchRequest request) throws UnexpectedRequestException {
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.get(ctx.getConnection());
-                    if (clientContext != null) {
-                        final ServerConnection<Integer> conn = clientContext.getServerConnection();
-                        final SearchHandler handler = new SearchHandler(clientContext, messageID);
-                        conn.handleSearch(messageID, request, handler, handler);
-                    }
-                }
+        @Override
+        public void deleteRequest(final int messageID, final DeleteRequest request)
+                throws UnexpectedRequestException {
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.get(connection);
+            if (clientContext != null) {
+                final ServerConnection<Integer> conn = clientContext.getServerConnection();
+                final DeleteHandler handler = new DeleteHandler(clientContext, messageID);
+                conn.handleDelete(messageID, request, handler, handler);
+            }
+        }
 
-                @Override
-                public void unbindRequest(final FilterChainContext ctx, final int messageID,
-                        final UnbindRequest request) {
-                    // Remove the client context causing any subsequent LDAP
-                    // traffic to be ignored.
-                    final ClientContextImpl clientContext =
-                            LDAP_CONNECTION_ATTR.remove(ctx.getConnection());
-                    if (clientContext != null) {
-                        clientContext.handleClose(messageID, request);
-                    }
-                }
+        @Override
+        public <R extends ExtendedResult> void extendedRequest(
+                final int messageID,
+                final ExtendedRequest<R> request) throws UnexpectedRequestException {
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.get(connection);
+            if (clientContext != null) {
+                final ServerConnection<Integer> conn = clientContext.getServerConnection();
+                final ExtendedHandler<R> handler = new ExtendedHandler<R>(clientContext, messageID);
+                conn.handleExtendedRequest(messageID, request, handler, handler);
+            }
+        }
 
-                @Override
-                public void unrecognizedMessage(final FilterChainContext ctx, final int messageID,
-                        final byte messageTag, final ByteString messageBytes) {
-                    exceptionOccurred(ctx, new UnsupportedMessageException(messageID, messageTag,
-                            messageBytes));
-                }
-            };
+        @Override
+        public void modifyDNRequest(final int messageID, final ModifyDNRequest request)
+                throws UnexpectedRequestException {
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.get(connection);
+            if (clientContext != null) {
+                final ServerConnection<Integer> conn = clientContext.getServerConnection();
+                final ModifyDNHandler handler = new ModifyDNHandler(clientContext, messageID);
+                conn.handleModifyDN(messageID, request, handler, handler);
+            }
+        }
 
-    LDAPServerFilter(final GrizzlyLDAPListener listener, final LDAPReader ldapReader,
+        @Override
+        public void modifyRequest(final int messageID, final ModifyRequest request)
+                throws UnexpectedRequestException {
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.get(connection);
+            if (clientContext != null) {
+                final ServerConnection<Integer> conn = clientContext.getServerConnection();
+                final ModifyHandler handler = new ModifyHandler(clientContext, messageID);
+                conn.handleModify(messageID, request, handler, handler);
+            }
+        }
+
+        @Override
+        public void searchRequest(final int messageID, final SearchRequest request)
+                throws UnexpectedRequestException {
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.get(connection);
+            if (clientContext != null) {
+                final ServerConnection<Integer> conn = clientContext.getServerConnection();
+                final SearchHandler handler = new SearchHandler(clientContext, messageID);
+                conn.handleSearch(messageID, request, handler, handler);
+            }
+        }
+
+        @Override
+        public void unbindRequest(final int messageID, final UnbindRequest request) {
+            // Remove the client context causing any subsequent LDAP
+            // traffic to be ignored.
+            final ClientContextImpl clientContext =
+                    LDAP_CONNECTION_ATTR.remove(connection);
+            if (clientContext != null) {
+                clientContext.handleClose(messageID, request);
+            }
+        }
+
+        @Override
+        public void unrecognizedMessage(final int messageID, final byte messageTag,
+                final ByteString messageBytes) {
+            exceptionOccurred(connection, new UnsupportedMessageException(messageID, messageTag,
+                    messageBytes));
+        }
+    }
+
+    /**
+     * Creates a server filter with provided listener, options and max size of
+     * ASN1 element.
+     *
+     * @param listener
+     *            listen for incoming connections
+     * @param options
+     *            control how to decode requests and responses
+     * @param maxASN1ElementSize
+     *            The maximum BER element size, or <code>0</code> to indicate
+     *            that there is no limit.
+     */
+    LDAPServerFilter(final GrizzlyLDAPListener listener, final DecodeOptions options,
             final int maxASN1ElementSize) {
         this.listener = listener;
-        this.ldapReader = ldapReader;
+        this.decodeOptions = options;
         this.maxASN1ElementSize =
                 maxASN1ElementSize <= 0 ? DEFAULT_MAX_REQUEST_SIZE : maxASN1ElementSize;
     }
 
     @Override
     public void exceptionOccurred(final FilterChainContext ctx, final Throwable error) {
-        final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.remove(ctx.getConnection());
+        exceptionOccurred(ctx.getConnection(), error);
+    }
+
+    private static void exceptionOccurred(final Connection<?> connection, final Throwable error) {
+        final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.remove(connection);
         if (clientContext != null) {
             clientContext.handleError(error);
         }
@@ -819,19 +876,15 @@
 
     @Override
     public NextAction handleRead(final FilterChainContext ctx) throws IOException {
+        final ServerRequestHandler requestHandler = getRequestHandler(ctx.getConnection());
+        final LDAPReader<ASN1BufferReader> reader = requestHandler.getReader();
+        final ASN1BufferReader asn1Reader = reader.getASN1Reader();
         final Buffer buffer = (Buffer) ctx.getMessage();
-        ASN1BufferReader asn1Reader = LDAP_ASN1_READER_ATTR.get(ctx.getConnection());
-        if (asn1Reader == null) {
-            asn1Reader =
-                    new ASN1BufferReader(maxASN1ElementSize, ctx.getConnection().getTransport()
-                            .getMemoryManager());
-            LDAP_ASN1_READER_ATTR.set(ctx.getConnection(), asn1Reader);
-        }
-        asn1Reader.appendBytesRead(buffer);
 
+        asn1Reader.appendBytesRead(buffer);
         try {
-            while (asn1Reader.elementAvailable()) {
-                ldapReader.decode(asn1Reader, serverRequestHandler, ctx);
+            while (reader.hasMessageAvailable()) {
+                reader.readMessage(requestHandler);
             }
         } catch (IOException e) {
             exceptionOccurred(ctx, e);
@@ -842,4 +895,26 @@
 
         return ctx.getStopAction();
     }
+
+    /**
+     * Returns the request handler associated to a connection.
+     * <p>
+     * If no handler exists yet for this context, a new one is created and
+     * recorded for the context.
+     *
+     * @param ctx
+     *            Context
+     * @return the response handler associated to the context, which can be a
+     *         new one if no handler have been created yet
+     */
+    private ServerRequestHandler getRequestHandler(final Connection<?> connection) {
+        ServerRequestHandler handler = REQUEST_HANDLER_ATTR.get(connection);
+        if (handler == null) {
+            LDAPReader<ASN1BufferReader> reader = GrizzlyUtils.createReader(decodeOptions,
+                    maxASN1ElementSize, connection.getTransport().getMemoryManager());
+            handler = new ServerRequestHandler(connection, reader);
+            REQUEST_HANDLER_ATTR.set(connection, handler);
+        }
+        return handler;
+    }
 }
diff --git a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPWriter.java b/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPWriter.java
deleted file mode 100644
index b3e2fa9..0000000
--- a/opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPWriter.java
+++ /dev/null
@@ -1,481 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * 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 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
- *
- *
- *      Copyright 2009-2010 Sun Microsystems, Inc.
- *      Portions copyright 2011-2013 ForgeRock AS
- */
-
-package com.forgerock.opendj.grizzly;
-
-import static com.forgerock.opendj.ldap.LDAPConstants.*;
-import static com.forgerock.opendj.util.StaticUtils.IO_LOG;
-import static com.forgerock.opendj.util.StaticUtils.byteToHex;
-
-import java.io.IOException;
-import java.util.List;
-
-import org.forgerock.opendj.asn1.ASN1Writer;
-import org.forgerock.opendj.ldap.Attribute;
-import org.forgerock.opendj.ldap.ByteString;
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.Modification;
-import org.forgerock.opendj.ldap.controls.Control;
-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.ExtendedRequest;
-import org.forgerock.opendj.ldap.requests.GenericBindRequest;
-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.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.ExtendedResult;
-import org.forgerock.opendj.ldap.responses.IntermediateResponse;
-import org.forgerock.opendj.ldap.responses.Response;
-import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.opendj.ldap.responses.SearchResultEntry;
-import org.forgerock.opendj.ldap.responses.SearchResultReference;
-
-import com.forgerock.opendj.ldap.LDAPUtils;
-import com.forgerock.opendj.util.StaticUtils;
-
-/**
- * Static methods for encoding LDAP messages.
- */
-final class LDAPWriter implements LDAPMessageHandler<ASN1Writer> {
-    public static void encodeControl(final ASN1Writer writer, final Control control)
-            throws IOException {
-        writer.writeStartSequence();
-        writer.writeOctetString(control.getOID());
-        if (control.isCritical()) {
-            writer.writeBoolean(control.isCritical());
-        }
-        if (control.getValue() != null) {
-            writer.writeOctetString(control.getValue());
-        }
-        writer.writeEndSequence();
-    }
-
-    public static void encodeEntry(final ASN1Writer writer,
-            final SearchResultEntry searchResultEntry) throws IOException {
-        writer.writeStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
-        writer.writeOctetString(searchResultEntry.getName().toString());
-
-        writer.writeStartSequence();
-        for (final Attribute attr : searchResultEntry.getAllAttributes()) {
-            encodeAttribute(writer, attr);
-        }
-        writer.writeEndSequence();
-        writer.writeEndSequence();
-    }
-
-    private static void encodeAttribute(final ASN1Writer writer, final Attribute attribute)
-            throws IOException {
-        writer.writeStartSequence();
-        writer.writeOctetString(attribute.getAttributeDescriptionAsString());
-
-        writer.writeStartSet();
-        for (final ByteString value : attribute) {
-            writer.writeOctetString(value);
-        }
-        writer.writeEndSequence();
-
-        writer.writeEndSequence();
-    }
-
-    private static void encodeChange(final ASN1Writer writer, final Modification change)
-            throws IOException {
-        writer.writeStartSequence();
-        writer.writeEnumerated(change.getModificationType().intValue());
-        encodeAttribute(writer, change.getAttribute());
-        writer.writeEndSequence();
-    }
-
-    private static void encodeMessageFooter(final ASN1Writer writer, final Request request)
-            throws IOException {
-        final List<Control> controls = request.getControls();
-        if (!controls.isEmpty()) {
-            writer.writeStartSequence(TYPE_CONTROL_SEQUENCE);
-            for (final Control control : controls) {
-                encodeControl(writer, control);
-            }
-            writer.writeEndSequence();
-        }
-
-        writer.writeEndSequence();
-    }
-
-    private static void encodeMessageFooter(final ASN1Writer writer, final Response response)
-            throws IOException {
-        final List<Control> controls = response.getControls();
-        if (!controls.isEmpty()) {
-            writer.writeStartSequence(TYPE_CONTROL_SEQUENCE);
-            for (final Control control : controls) {
-                encodeControl(writer, control);
-            }
-            writer.writeEndSequence();
-        }
-
-        writer.writeEndSequence();
-    }
-
-    private static void encodeMessageHeader(final ASN1Writer writer, final int messageID)
-            throws IOException {
-        writer.writeStartSequence();
-        writer.writeInteger(messageID);
-    }
-
-    private static void encodeResultFooter(final ASN1Writer writer) throws IOException {
-        writer.writeEndSequence();
-    }
-
-    private static void encodeResultHeader(final ASN1Writer writer, final byte typeTag,
-            final Result rawMessage) throws IOException {
-        writer.writeStartSequence(typeTag);
-        writer.writeEnumerated(rawMessage.getResultCode().intValue());
-        writer.writeOctetString(rawMessage.getMatchedDN());
-        writer.writeOctetString(rawMessage.getDiagnosticMessage());
-
-        final List<String> referralURIs = rawMessage.getReferralURIs();
-        if (!referralURIs.isEmpty()) {
-            writer.writeStartSequence(TYPE_REFERRAL_SEQUENCE);
-            for (final String s : referralURIs) {
-                writer.writeOctetString(s);
-            }
-            writer.writeEndSequence();
-        }
-    }
-
-    @Override
-    public void abandonRequest(final ASN1Writer writer, final int messageID,
-            final AbandonRequest request) throws IOException {
-        IO_LOG.trace("ENCODE LDAP ABANDON REQUEST(messageID={}, request={})", messageID, request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeInteger(OP_TYPE_ABANDON_REQUEST, request.getRequestID());
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void addRequest(final ASN1Writer writer, final int messageID, final AddRequest request)
-            throws IOException {
-        IO_LOG.trace("ENCODE LDAP ADD REQUEST(messageID={}, request={})", messageID, request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeStartSequence(OP_TYPE_ADD_REQUEST);
-        writer.writeOctetString(request.getName().toString());
-
-        // Write the attributes
-        writer.writeStartSequence();
-        for (final Attribute attr : request.getAllAttributes()) {
-            encodeAttribute(writer, attr);
-        }
-        writer.writeEndSequence();
-
-        writer.writeEndSequence();
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void addResult(final ASN1Writer writer, final int messageID, final Result result)
-            throws IOException {
-        IO_LOG.trace("ENCODE LDAP ADD RESULT(messageID={}, result={})", messageID, result);
-        encodeMessageHeader(writer, messageID);
-        encodeResultHeader(writer, OP_TYPE_ADD_RESPONSE, result);
-        encodeResultFooter(writer);
-        encodeMessageFooter(writer, result);
-    }
-
-    @Override
-    public void bindRequest(final ASN1Writer writer, final int messageID, final int version,
-            final GenericBindRequest request) throws IOException {
-        IO_LOG.trace("ENCODE LDAP BIND REQUEST(messageID={}, auth=0x{}, request={})",
-                messageID, byteToHex(request.getAuthenticationType()), request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeStartSequence(OP_TYPE_BIND_REQUEST);
-
-        writer.writeInteger(version);
-        writer.writeOctetString(request.getName());
-        writer.writeOctetString(request.getAuthenticationType(), request.getAuthenticationValue());
-
-        writer.writeEndSequence();
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void bindResult(final ASN1Writer writer, final int messageID, final BindResult result)
-            throws IOException {
-        IO_LOG.trace("ENCODE LDAP BIND RESULT(messageID={}, result={})", messageID, result);
-        encodeMessageHeader(writer, messageID);
-        encodeResultHeader(writer, OP_TYPE_BIND_RESPONSE, result);
-
-        final ByteString saslCredentials = result.getServerSASLCredentials();
-        if (saslCredentials != null && saslCredentials.length() > 0) {
-            writer.writeOctetString(TYPE_SERVER_SASL_CREDENTIALS, result.getServerSASLCredentials());
-        }
-
-        encodeResultFooter(writer);
-        encodeMessageFooter(writer, result);
-    }
-
-    @Override
-    public void compareRequest(final ASN1Writer writer, final int messageID,
-            final CompareRequest request) throws IOException {
-        IO_LOG.trace("ENCODE LDAP COMPARE REQUEST(messageID={}, request={})", messageID, request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeStartSequence(OP_TYPE_COMPARE_REQUEST);
-        writer.writeOctetString(request.getName().toString());
-
-        writer.writeStartSequence();
-        writer.writeOctetString(request.getAttributeDescription().toString());
-        writer.writeOctetString(request.getAssertionValue());
-        writer.writeEndSequence();
-
-        writer.writeEndSequence();
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void compareResult(final ASN1Writer writer, final int messageID,
-            final CompareResult result) throws IOException {
-        IO_LOG.trace("ENCODE LDAP COMPARE RESULT(messageID={}, result={})", messageID, result);
-        encodeMessageHeader(writer, messageID);
-        encodeResultHeader(writer, OP_TYPE_COMPARE_RESPONSE, result);
-        encodeResultFooter(writer);
-        encodeMessageFooter(writer, result);
-    }
-
-    @Override
-    public void deleteRequest(final ASN1Writer writer, final int messageID,
-            final DeleteRequest request) throws IOException {
-        IO_LOG.trace("ENCODE LDAP DELETE REQUEST(messageID={}, request={})", messageID, request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeOctetString(OP_TYPE_DELETE_REQUEST, request.getName().toString());
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void deleteResult(final ASN1Writer writer, final int messageID, final Result result)
-            throws IOException {
-        IO_LOG.trace("ENCODE LDAP DELETE RESULT(messageID={}, result={})", messageID, result);
-        encodeMessageHeader(writer, messageID);
-        encodeResultHeader(writer, OP_TYPE_DELETE_RESPONSE, result);
-        encodeResultFooter(writer);
-        encodeMessageFooter(writer, result);
-    }
-
-    @Override
-    public <R extends ExtendedResult> void extendedRequest(final ASN1Writer writer,
-            final int messageID, final ExtendedRequest<R> request) throws IOException {
-        IO_LOG.trace("ENCODE LDAP EXTENDED REQUEST(messageID={}, request={})", messageID, request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeStartSequence(OP_TYPE_EXTENDED_REQUEST);
-        writer.writeOctetString(TYPE_EXTENDED_REQUEST_OID, request.getOID());
-
-        final ByteString requestValue = request.getValue();
-        if (requestValue != null) {
-            writer.writeOctetString(TYPE_EXTENDED_REQUEST_VALUE, requestValue);
-        }
-
-        writer.writeEndSequence();
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void extendedResult(final ASN1Writer writer, final int messageID,
-            final ExtendedResult result) throws IOException {
-        IO_LOG.trace("ENCODE LDAP EXTENDED RESULT(messageID={}, result={})", messageID, result);
-        encodeMessageHeader(writer, messageID);
-        encodeResultHeader(writer, OP_TYPE_EXTENDED_RESPONSE, result);
-
-        final String responseName = result.getOID();
-        final ByteString responseValue = result.getValue();
-
-        if (responseName != null) {
-            writer.writeOctetString(TYPE_EXTENDED_RESPONSE_OID, responseName);
-        }
-
-        if (responseValue != null) {
-            writer.writeOctetString(TYPE_EXTENDED_RESPONSE_VALUE, responseValue);
-        }
-
-        encodeResultFooter(writer);
-        encodeMessageFooter(writer, result);
-    }
-
-    @Override
-    public void intermediateResponse(final ASN1Writer writer, final int messageID,
-            final IntermediateResponse response) throws IOException {
-        IO_LOG.trace("ENCODE LDAP INTERMEDIATE RESPONSE(messageID={}, response={})", messageID, response);
-        encodeMessageHeader(writer, messageID);
-        writer.writeStartSequence(OP_TYPE_INTERMEDIATE_RESPONSE);
-
-        final String responseName = response.getOID();
-        final ByteString responseValue = response.getValue();
-
-        if (responseName != null) {
-            writer.writeOctetString(TYPE_INTERMEDIATE_RESPONSE_OID, response.getOID());
-        }
-
-        if (responseValue != null) {
-            writer.writeOctetString(TYPE_INTERMEDIATE_RESPONSE_VALUE, response.getValue());
-        }
-
-        writer.writeEndSequence();
-        encodeMessageFooter(writer, response);
-    }
-
-    @Override
-    public void modifyDNRequest(final ASN1Writer writer, final int messageID,
-            final ModifyDNRequest request) throws IOException {
-        IO_LOG.trace("ENCODE LDAP MODIFY DN REQUEST(messageID={}, request={})", messageID, request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeStartSequence(OP_TYPE_MODIFY_DN_REQUEST);
-        writer.writeOctetString(request.getName().toString());
-        writer.writeOctetString(request.getNewRDN().toString());
-        writer.writeBoolean(request.isDeleteOldRDN());
-
-        final DN newSuperior = request.getNewSuperior();
-        if (newSuperior != null) {
-            writer.writeOctetString(TYPE_MODIFY_DN_NEW_SUPERIOR, newSuperior.toString());
-        }
-
-        writer.writeEndSequence();
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void modifyDNResult(final ASN1Writer writer, final int messageID, final Result result)
-            throws IOException {
-        IO_LOG.trace("ENCODE LDAP MODIFY DN RESULT(messageID={}, result={})", messageID, result);
-        encodeMessageHeader(writer, messageID);
-        encodeResultHeader(writer, OP_TYPE_MODIFY_DN_RESPONSE, result);
-        encodeResultFooter(writer);
-        encodeMessageFooter(writer, result);
-    }
-
-    @Override
-    public void modifyRequest(final ASN1Writer writer, final int messageID,
-            final ModifyRequest request) throws IOException {
-        IO_LOG.trace("ENCODE LDAP MODIFY REQUEST(messageID={}, request={})", messageID, request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeStartSequence(OP_TYPE_MODIFY_REQUEST);
-        writer.writeOctetString(request.getName().toString());
-
-        writer.writeStartSequence();
-        for (final Modification change : request.getModifications()) {
-            encodeChange(writer, change);
-        }
-        writer.writeEndSequence();
-
-        writer.writeEndSequence();
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void modifyResult(final ASN1Writer writer, final int messageID, final Result result)
-            throws IOException {
-        IO_LOG.trace("ENCODE LDAP MODIFY RESULT(messageID={}, result={})", messageID, result);
-        encodeMessageHeader(writer, messageID);
-        encodeResultHeader(writer, OP_TYPE_MODIFY_RESPONSE, result);
-        encodeResultFooter(writer);
-        encodeMessageFooter(writer, result);
-    }
-
-    @Override
-    public void searchRequest(final ASN1Writer writer, final int messageID,
-            final SearchRequest request) throws IOException {
-        IO_LOG.trace("ENCODE LDAP SEARCH REQUEST(messageID={}, request={})", messageID, request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeStartSequence(OP_TYPE_SEARCH_REQUEST);
-        writer.writeOctetString(request.getName().toString());
-        writer.writeEnumerated(request.getScope().intValue());
-        writer.writeEnumerated(request.getDereferenceAliasesPolicy().intValue());
-        writer.writeInteger(request.getSizeLimit());
-        writer.writeInteger(request.getTimeLimit());
-        writer.writeBoolean(request.isTypesOnly());
-        LDAPUtils.encodeFilter(writer, request.getFilter());
-
-        writer.writeStartSequence();
-        for (final String attribute : request.getAttributes()) {
-            writer.writeOctetString(attribute);
-        }
-        writer.writeEndSequence();
-
-        writer.writeEndSequence();
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void searchResult(final ASN1Writer writer, final int messageID, final Result result)
-            throws IOException {
-        IO_LOG.trace("ENCODE LDAP SEARCH RESULT(messageID={}, result={})", messageID, result);
-        encodeMessageHeader(writer, messageID);
-        encodeResultHeader(writer, OP_TYPE_SEARCH_RESULT_DONE, result);
-        encodeResultFooter(writer);
-        encodeMessageFooter(writer, result);
-    }
-
-    @Override
-    public void searchResultEntry(final ASN1Writer writer, final int messageID,
-            final SearchResultEntry entry) throws IOException {
-        IO_LOG.trace("ENCODE LDAP SEARCH RESULT ENTRY(messageID={}, entry={})", messageID, entry);
-        encodeMessageHeader(writer, messageID);
-        encodeEntry(writer, entry);
-        encodeMessageFooter(writer, entry);
-    }
-
-    @Override
-    public void searchResultReference(final ASN1Writer writer, final int messageID,
-            final SearchResultReference reference) throws IOException {
-        IO_LOG.trace("ENCODE LDAP SEARCH RESULT REFERENCE(messageID={}, reference={})", messageID, reference);
-        encodeMessageHeader(writer, messageID);
-        writer.writeStartSequence(OP_TYPE_SEARCH_RESULT_REFERENCE);
-        for (final String url : reference.getURIs()) {
-            writer.writeOctetString(url);
-        }
-        writer.writeEndSequence();
-        encodeMessageFooter(writer, reference);
-    }
-
-    @Override
-    public void unbindRequest(final ASN1Writer writer, final int messageID,
-            final UnbindRequest request) throws IOException {
-        IO_LOG.trace("ENCODE LDAP UNBIND REQUEST(messageID={}, request={})", messageID, request);
-        encodeMessageHeader(writer, messageID);
-        writer.writeNull(OP_TYPE_UNBIND_REQUEST);
-        encodeMessageFooter(writer, request);
-    }
-
-    @Override
-    public void unrecognizedMessage(final ASN1Writer writer, final int messageID,
-            final byte messageTag, final ByteString messageBytes) throws IOException {
-        IO_LOG.trace("ENCODE LDAP UNKNOWN MESSAGE(messageID={}, messageTag={}, messageBytes={})",
-                messageID, StaticUtils.byteToHex(messageTag), messageBytes);
-        encodeMessageHeader(writer, messageID);
-        writer.writeOctetString(messageTag, messageBytes);
-        writer.writeEndSequence();
-    }
-}
diff --git a/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java b/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java
index dcb54bb..3c21b45 100644
--- a/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java
+++ b/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java
@@ -22,7 +22,7 @@
  *
  *
  *      Copyright 2010 Sun Microsystems, Inc.
- *      Portions copyright 2011 ForgeRock AS
+ *      Portions copyright 2011-2013 ForgeRock AS
  */
 
 package com.forgerock.opendj.grizzly;
@@ -43,7 +43,7 @@
  */
 public class ASN1BufferWriterTestCase extends ASN1WriterTestCase {
 
-    private final ASN1BufferWriter writer = ASN1BufferWriter.getWriter();
+    private final ASN1BufferWriter writer = new ASN1BufferWriter();
 
     @Override
     protected byte[] getEncodedBytes() throws IOException, DecodeException {
diff --git a/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java b/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
index a77e2bd..a6b1d6a 100644
--- a/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
+++ b/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
@@ -102,7 +102,7 @@
             connection.searchAsync(request, null, handler);
 
             // Pass in a time which is guaranteed to trigger expiration.
-            connection.cancelExpiredRequests(System.currentTimeMillis() + 1000000);
+            connection.handleTimeout(System.currentTimeMillis() + 1000000);
             if (isPersistentSearch) {
                 verifyZeroInteractions(handler);
             } else {
diff --git a/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java b/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java
new file mode 100644
index 0000000..029c2ac
--- /dev/null
+++ b/opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java
@@ -0,0 +1,61 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * 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 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
+ *
+ *
+ *      Copyright 2013 ForgeRock AS.
+ */
+package com.forgerock.opendj.grizzly;
+
+import org.forgerock.opendj.asn1.ASN1Reader;
+import org.forgerock.opendj.asn1.ASN1Writer;
+import org.forgerock.opendj.io.LDAPReader;
+import org.forgerock.opendj.io.LDAPReaderWriterTestCase;
+import org.forgerock.opendj.io.LDAPWriter;
+import org.forgerock.opendj.ldap.LDAPOptions;
+import org.glassfish.grizzly.memory.HeapMemoryManager;
+
+/**
+ * Tests for LDAPWriter / LDAPReader classes using specific implementations of
+ * ASN1 writer and ASN1 reader with Grizzly.
+ */
+public class GrizzlyLDAPReaderWriterTestCase extends LDAPReaderWriterTestCase {
+
+    @Override
+    protected LDAPWriter<? extends ASN1Writer> getLDAPWriter() {
+        return GrizzlyUtils.getWriter();
+    }
+
+    @Override
+    protected LDAPReader<? extends ASN1Reader> getLDAPReader() {
+        return GrizzlyUtils.createReader(new LDAPOptions().getDecodeOptions(),
+                0, new HeapMemoryManager());
+    }
+
+    @Override
+    protected void transferFromWriterToReader(LDAPWriter<? extends ASN1Writer> writer,
+            LDAPReader<? extends ASN1Reader> reader) {
+        ASN1BufferReader asn1Reader = (ASN1BufferReader) reader.getASN1Reader();
+        ASN1BufferWriter asn1Writer = (ASN1BufferWriter) writer.getASN1Writer();
+        asn1Reader.appendBytesRead(asn1Writer.getBuffer());
+    }
+
+}

--
Gitblit v1.10.0