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

neil_a_wilson
20.33.2007 0e12bdbd95545009fc5840beec7d2b4daf88a1fd
Add a new internal LDAP socket implementation that provides a mechanism which
allows third-party LDAP SDKs to be used to perform internal operations within
the server. Rather than performing network communication, the custom socket
decodes the request written to it, converts it to an internal operation,
processes the request, and encodes the response so that it can be read from the
socket by the LDAP SDK.

This has been tested with both the Mozilla LDAP SDK for Java (which requires a
trivial custom LDAPSocketFactory implementation) and JNDI (which requires a
custom property to be set).
6 files added
1 files modified
3293 ■■■■■ changed files
opendj-sdk/opends/src/messages/messages/protocol.properties 7 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPInputStream.java 497 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPOutputStream.java 1029 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocket.java 860 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocketFactory.java 180 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/NullLDAPMessage.java 58 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalLDAPSocketTestCase.java 662 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/messages/messages/protocol.properties
@@ -1400,3 +1400,10 @@
 privileges to establish the connection through JMX. At least JMX_READ \
 privilege is required
MILD_ERR_INTERNALCONN_NO_SUCH_USER_440=User %s does not exist in the directory
MILD_ERR_INTERNALOS_CLOSED_441=This output stream has been closed
MILD_ERR_INTERNALOS_INVALID_REQUEST_442=The provided LDAP message had an \
 invalid operation type (%s) for a request
MILD_ERR_INTERNALOS_SASL_BIND_NOT_SUPPORTED_443=SASL bind operations are not \
 supported over internal LDAP sockets
MILD_ERR_INTERNALOS_STARTTLS_NOT_SUPPORTED_444=StartTLS operations are not \
 supported over internal LDAP sockets
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPInputStream.java
New file
@@ -0,0 +1,497 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.internal;
import java.io.InputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
import org.opends.server.protocols.ldap.LDAPMessage;
/**
 * This class provides an implementation of a
 * {@code java.io.InputStream} that can be used to facilitate internal
 * communication with the Directory Server.  On the backend, this
 * input stream will be populated by ASN.1 elements encoded from LDAP
 * messages created from internal operation responses.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
     mayInstantiate=false,
     mayExtend=false,
     mayInvoke=true)
public final class InternalLDAPInputStream
       extends InputStream
{
  // The queue of LDAP messages providing the data to be made
  // available to the client.
  private ArrayBlockingQueue<LDAPMessage> messageQueue;
  // Indicates whether this stream has been closed.
  private boolean closed;
  // The byte buffer with partial data to be written to the client.
  private ByteBuffer partialMessageBuffer;
  // The internal LDAP socket serviced by this input stream.
  private InternalLDAPSocket socket;
  /**
   * Creates a new internal LDAP input stream that will service the
   * provided internal LDAP socket.
   *
   * @param  socket  The internal LDAP socket serviced by this
   *                 internal LDAP input stream.
   */
  public InternalLDAPInputStream(InternalLDAPSocket socket)
  {
    this.socket = socket;
    messageQueue = new ArrayBlockingQueue<LDAPMessage>(10);
    partialMessageBuffer = null;
    closed = false;
  }
  /**
   * Adds the provided LDAP message to the set of messages to be
   * returned to the client.  Note that this may block if there is
   * already a significant backlog of messages to be returned.
   *
   * @param  message  The message to add to the set of messages to be
   *                  returned to the client.
   */
  @org.opends.server.types.PublicAPI(
       stability=org.opends.server.types.StabilityLevel.PRIVATE,
       mayInstantiate=false,
       mayExtend=false,
       mayInvoke=false)
  void addLDAPMessage(LDAPMessage message)
  {
    // If the stream is closed, then simply drop the message.
    if (closed)
    {
      return;
    }
    try
    {
      messageQueue.put(message);
      return;
    }
    catch (Exception e)
    {
      // This shouldn't happen, but if it does then try three more
      // times before giving up and dropping the message.
      for (int i=0; i < 3; i++)
      {
        try
        {
          messageQueue.put(message);
          break;
        } catch (Exception e2) {}
      }
      return;
    }
  }
  /**
   * Retrieves the number of bytes that can be read (or skipped over)
   * from this input stream without blocking.
   *
   * @return  The number of bytes that can be read (or skipped over)
   *          from this input stream wihtout blocking.
   */
  @Override()
  public synchronized int available()
  {
    if (partialMessageBuffer == null)
    {
      LDAPMessage message = messageQueue.poll();
      if ((message == null) || (message instanceof NullLDAPMessage))
      {
        if (message instanceof NullLDAPMessage)
        {
          closed = true;
        }
        return 0;
      }
      else
      {
        partialMessageBuffer =
             ByteBuffer.wrap(message.encode().encode());
        return partialMessageBuffer.remaining();
      }
    }
    else
    {
      return partialMessageBuffer.remaining();
    }
  }
  /**
   * Closes this input stream.  This will add a special marker
   * element to the message queue indicating that the end of the
   * stream has been reached.  If the queue is full, thenit will be
   * cleared before adding the marker element.
   */
  @Override()
  public void close()
  {
    socket.close();
  }
  /**
   * Closes this input stream through an internal mechanism that will
   * not cause an infinite recursion loop by trying to also close the
   * input stream.
   */
  @org.opends.server.types.PublicAPI(
       stability=org.opends.server.types.StabilityLevel.PRIVATE,
       mayInstantiate=false,
       mayExtend=false,
       mayInvoke=false)
  void closeInternal()
  {
    if (closed)
    {
      return;
    }
    closed = true;
    NullLDAPMessage nullMessage = new NullLDAPMessage();
    while (! messageQueue.offer(nullMessage))
    {
      messageQueue.clear();
    }
  }
  /**
   * Marks the current position in the input stream.  This will not
   * have any effect, as this input stream inplementation does not
   * support marking.
   *
   * @param  readLimit  The maximum limit of bytes that can be read
   *                    before the mark position becomes invalid.
   */
  @Override()
  public void mark(int readLimit)
  {
    // No implementation is required.
  }
  /**
   * Indicates whether this input stream inplementation supports the
   * use of the {@code mark} and {@code reset} methods.  This
   * implementation does not support that functionality.
   *
   * @return  {@code false} because this implementation does not
   *          support the use of the {@code mark} and {@code reset}
   *          methods.
   */
  @Override()
  public boolean markSupported()
  {
    return false;
  }
  /**
   * Reads the next byte of data from the input stream, blocking if
   * necessary until there is data available.
   *
   * @return  The next byte of data read from the input stream, or -1
   *          if the end of the input stream has been reached.
   *
   * @throws  IOException  If a problem occurs while trying to read
   *                       data from the stream.
   */
  @Override()
  public synchronized int read()
         throws IOException
  {
    if (partialMessageBuffer != null)
    {
      if (partialMessageBuffer.remaining() > 0)
      {
        int i = (0xFF & partialMessageBuffer.get());
        if (partialMessageBuffer.remaining() == 0)
        {
          partialMessageBuffer = null;
        }
        return i;
      }
      else
      {
        partialMessageBuffer = null;
      }
    }
    if (closed)
    {
      return -1;
    }
    try
    {
      LDAPMessage message = messageQueue.take();
      if (message instanceof NullLDAPMessage)
      {
        messageQueue.clear();
        closed = true;
        return -1;
      }
      partialMessageBuffer =
           ByteBuffer.wrap(message.encode().encode());
      return (0xFF & partialMessageBuffer.get());
    }
    catch (Exception e)
    {
      throw new IOException(e.getMessage(), e);
    }
  }
  /**
   * Reads some number of bytes from the input stream, blocking if
   * necessary until there is data available, and adds them to the
   * provided array starting at position 0.
   *
   * @param  b  The array to which the data is to be written.
   *
   * @return  The number of bytes actually written into the
   *          provided array, or -1 if the end of the stream has been
   *          reached.
   *
   * @throws  IOException  If a problem occurs while trying to read
   *                       data from the stream.
   */
  @Override()
  public int read(byte[] b)
         throws IOException
  {
    return read(b, 0, b.length);
  }
  /**
   * Reads some number of bytes from the input stream, blocking if
   * necessary until there is data available, and adds them to the
   * provided array starting at the specified position.
   *
   * @param  b    The array to which the data is to be written.
   * @param  off  The offset in the array at which to start writing
   *              data.
   * @param  len  The maximum number of bytes that may be added to the
   *              array.
   *
   * @return  The number of bytes actually written into the
   *          provided array, or -1 if the end of the stream has been
   *          reached.
   *
   * @throws  IOException  If a problem occurs while trying to read
   *                       data from the stream.
   */
  @Override()
  public synchronized int read(byte[] b, int off, int len)
         throws IOException
  {
    if (partialMessageBuffer != null)
    {
      int remaining = partialMessageBuffer.remaining();
      if (remaining > 0)
      {
        if (remaining <= len)
        {
          // We can fit all the remaining data in the provided array,
          // so that's all we'll try to put in it.
          partialMessageBuffer.get(b, off, remaining);
          partialMessageBuffer = null;
          return remaining;
        }
        else
        {
          // The array is too small to hold the rest of the data, so
          // only take as much as we can.
          partialMessageBuffer.get(b, off, len);
          return len;
        }
      }
      else
      {
        partialMessageBuffer = null;
      }
    }
    if (closed)
    {
      return -1;
    }
    try
    {
      LDAPMessage message = messageQueue.take();
      if (message instanceof NullLDAPMessage)
      {
        messageQueue.clear();
        closed = true;
        return -1;
      }
      byte[] encodedMessage = message.encode().encode();
      if (encodedMessage.length <= len)
      {
        // We can fit the entire message in the array.
        System.arraycopy(encodedMessage, 0, b, off,
                         encodedMessage.length);
        return encodedMessage.length;
      }
      else
      {
        // We can only fit part of the message in the array,
        // so we need to save the rest for later.
        System.arraycopy(encodedMessage, 0, b, off, len);
        partialMessageBuffer = ByteBuffer.wrap(encodedMessage);
        partialMessageBuffer.position(len);
        return len;
      }
    }
    catch (Exception e)
    {
      throw new IOException(e.getMessage(), e);
    }
  }
  /**
   * Repositions this stream to the position at the time that the
   * {@code mark} method was called on this stream.  This will not
   * have any effect, as this input stream inplementation does not
   * support marking.
   */
  @Override()
  public void reset()
  {
    // No implementation is required.
  }
  /**
   * Skips over and discards up to the specified number of bytes of
   * data from this input stream.  This implementation will always
   * skip the requested number of bytes unless the end of the stream
   * is reached.
   *
   * @param  n  The maximum number of bytes to skip.
   *
   * @return  The number of bytes actually skipped.
   *
   * @throws  IOException  If a problem occurs while trying to read
   *                       data from the input stream.
   */
  @Override()
  public synchronized long skip(long n)
         throws IOException
  {
    byte[] b;
    if (n > 8192)
    {
      b = new byte[8192];
    }
    else
    {
      b = new byte[(int) n];
    }
    long totalBytesRead = 0L;
    while (totalBytesRead < n)
    {
      int maxLen = (int) Math.min((n - totalBytesRead), b.length);
      int bytesRead = read(b, 0, maxLen);
      if (bytesRead < 0)
      {
        if (totalBytesRead > 0)
        {
          return totalBytesRead;
        }
        else
        {
          return bytesRead;
        }
      }
      else
      {
        totalBytesRead += bytesRead;
      }
    }
    return totalBytesRead;
  }
  /**
   * Retrieves a string representation of this internal LDAP socket.
   *
   * @return  A string representation of this internal LDAP socket.
   */
  @Override()
  public String toString()
  {
    return "InternalLDAPInputStream";
  }
}
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPOutputStream.java
New file
@@ -0,0 +1,1029 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.internal;
import java.io.OutputStream;
import java.io.IOException;
import java.util.ArrayList;
import org.opends.messages.Message;
import org.opends.server.core.*;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.ldap.*;
import org.opends.server.types.AuthenticationType;
import org.opends.server.types.Control;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * This class provides an implementation of a
 * {@code java.io.OutputStream} that can be used to facilitate
 * internal communication with the Directory Server.  On the backend,
 * data written to this output stream will be first decoded as an
 * ASN.1 element and then as an LDAP message.  That LDAP message will
 * be converted to an internal operation which will then be processed
 * and the result returned to the client via the input stream on the
 * other side of the associated internal LDAP socket.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
     mayInstantiate=false,
     mayExtend=false,
     mayInvoke=true)
public final class InternalLDAPOutputStream
       extends OutputStream
       implements InternalSearchListener
{
  // Indicates whether this stream has been closed.
  private boolean closed;
  // Indicates whether the type of the ASN.1 element is needed.
  private boolean needType;
  // The BER type for the ASN.1 element being read.
  private byte elementType;
  // The data for the ASN.1 element being read.
  private byte[] elementBytes;
  // The length bytes for the ASN.1 element being read.
  private byte[] lengthBytes;
  // The offset in the appropriate array at which we should begin
  // writing data.  This could either refer to the length or data
  // array, depending on the stage of the encoding process.
  private int arrayOffset;
  // The internal LDAP socket with which this output stream is
  // associated.
  private InternalLDAPSocket socket;
  /**
   * Creates a new instance of an internal LDAP output stream that is
   * associated with the provided internal LDAP socket.
   *
   * @param  socket  The internal LDAP socket that will be serviced by
   *                 this internal LDAP output stream.
   */
  public InternalLDAPOutputStream(InternalLDAPSocket socket)
  {
    this.socket = socket;
    closed = false;
    needType = true;
    elementType = 0x00;
    elementBytes = null;
    lengthBytes = null;
    arrayOffset = 0;
  }
  /**
   * Closes this output stream, its associated socket, and the
   * socket's associated input stream.
   */
  @Override()
  public void close()
  {
    socket.close();
  }
  /**
   * Closes this output stream through an internal mechanism that will
   * not cause an infinite recursion loop by trying to also close the
   * output stream.
   */
  @org.opends.server.types.PublicAPI(
       stability=org.opends.server.types.StabilityLevel.PRIVATE,
       mayInstantiate=false,
       mayExtend=false,
       mayInvoke=false)
  void closeInternal()
  {
    closed = true;
  }
  /**
   * Flushes this output stream and forces any buffered data to be
   * written out.  This will have no effect, since this output
   * stream implementation does not use buffering.
   */
  @Override()
  public void flush()
  {
    // No implementation is required.
  }
  /**
   * Writes the contents of the provided byte array to this output
   * stream.
   *
   * @param  b  The byte array to be written.
   *
   * @throws  IOException  If the output stream is closed, or if there
   *                       is a problem with the data being written.
   */
  @Override()
  public void write(byte[] b)
         throws IOException
  {
    write(b, 0, b.length);
  }
  /**
   * Writes the specified portion of the data in the provided byte
   * array to this output stream.  Any data written will be
   * accumulated until a complete ASN.1 element is available, at which
   * point it will be decoded as an LDAP message and converted to an
   * internal operation that will be processed.
   *
   * @param  b    The byte array containing the data to be read.
   * @param  off  The position in the array at which to start reading
   *              data.
   * @param  len  The number of bytes to read from the array.
   *
   * @throws  IOException  If the output stream is closed, or if there
   *                       is a problem with the data being written.
   */
  @Override()
  public synchronized void write(byte[] b, int off, int len)
         throws IOException
  {
    if (closed)
    {
      Message m = ERR_INTERNALOS_CLOSED.get();
      throw new IOException(m.toString());
    }
    if (len == 0)
    {
      return;
    }
    // See if we need to read the BER type.
    int position  = off;
    int remaining = len;
    if (needType)
    {
      elementType = b[position++];
      needType = false;
      if (--remaining <= 0)
      {
        return;
      }
    }
    // See if we need to read the first length byte.
    if ((lengthBytes == null) && (elementBytes == null))
    {
      int length = b[position++];
      if (length == (length & 0x7F))
      {
        // It's a single-byte length, so we can create the value
        // array.
        elementBytes = new byte[length];
      }
      else
      {
        // It's a multi-byte length, so we can create the length
        // array.
        lengthBytes = new byte[length & 0x7F];
      }
      arrayOffset = 0;
      if (--remaining <= 0)
      {
        return;
      }
    }
    // See if we need to continue reading part of a multi-byte length.
    if (lengthBytes != null)
    {
      // See if we have enough to read the full length.  If so, then
      // do it.  Otherwise, read what we can and return.
      int needed = lengthBytes.length - arrayOffset;
      if (remaining >= needed)
      {
        System.arraycopy(b, position, lengthBytes, arrayOffset,
                         needed);
        position += needed;
        remaining -= needed;
        int length = 0;
        for (byte lb : lengthBytes)
        {
          length <<= 8;
          length |= (lb & 0xFF);
        }
        elementBytes = new byte[length];
        lengthBytes = null;
        arrayOffset = 0;
        if (remaining <= 0)
        {
          return;
        }
      }
      else
      {
        System.arraycopy(b, position, lengthBytes, arrayOffset,
                         remaining);
        arrayOffset += remaining;
        return;
      }
    }
    // See if we need to read data for the element value.
    if (elementBytes != null)
    {
      // See if we have enough to read the full value.  If so, then
      // do it, create the element, and process it.  Otherwise, read
      // what we can and return.
      int needed = elementBytes.length - arrayOffset;
      if (remaining >= needed)
      {
        System.arraycopy(b, position, elementBytes, arrayOffset,
                         needed);
        position += needed;
        remaining -= needed;
        processElement(new ASN1Element(elementType, elementBytes));
        needType     = true;
        arrayOffset  = 0;
        lengthBytes  = null;
        elementBytes = null;
      }
      else
      {
        System.arraycopy(b, position, lengthBytes, arrayOffset,
                         remaining);
        arrayOffset += remaining;
        return;
      }
    }
    // If there is still more data available, then call this method
    // again to process it.
    if (remaining > 0)
    {
      write(b, position, remaining);
    }
  }
  /**
   * Writes a single byte of data to this output stream.  If the byte
   * written completes an ASN.1 element that was in progress, then it
   * will be decoded as an LDAP message and converted to an internal
   * operation that will be processed.  Otherwise, the data will be
   * accumulated until a complete element can be formed.
   *
   * @param  b The byte to be written.
   *
   * @throws  IOException  If the output stream is closed, or if there
   *                       is a problem with the data being written.
   */
  @Override()
  public synchronized void write(int b)
         throws IOException
  {
    if (closed)
    {
      Message m = ERR_INTERNALOS_CLOSED.get();
      throw new IOException(m.toString());
    }
    if (needType)
    {
      elementType = (byte) (b & 0xFF);
      needType = false;
      return;
    }
    else if (elementBytes != null)
    {
      // The byte should be part of the element value.
      elementBytes[arrayOffset++] = (byte) (b & 0xFF);
      if (arrayOffset == elementBytes.length)
      {
        // The element has been completed, so process it.
        processElement(new ASN1Element(elementType, elementBytes));
      }
      lengthBytes  = null;
      elementBytes = null;
      arrayOffset  = 0;
      needType     = true;
      return;
    }
    else if (lengthBytes != null)
    {
      // The byte should be part of a multi-byte length.
      lengthBytes[arrayOffset++] = (byte) (b & 0xFF);
      if (arrayOffset == lengthBytes.length)
      {
        int length = 0;
        for (int i=0; i < lengthBytes.length; i++)
        {
          length <<= 8;
          length |= (lengthBytes[i] & 0xFF);
        }
        elementBytes = new byte[length];
        lengthBytes  = null;
        arrayOffset   = 0;
      }
      return;
    }
    else
    {
      if ((b & 0x7F) == b)
      {
        // It's the complete length.
        elementBytes = new byte[b];
        lengthBytes  = null;
        arrayOffset = 0;
      }
      else
      {
        lengthBytes  = new byte[b & 0x7F];
        elementBytes = null;
        arrayOffset  = 0;
      }
      return;
    }
  }
  /**
   * Processes the provided ASN.1 element by decoding it as an LDAP
   * message, converting that to an internal operation, and sending
   * the appropriate response message(s) to the client through the
   * corresponding internal LDAP input stream.
   *
   * @param  element  The ASN.1 element to be processed.
   *
   * @throws  IOException  If a problem occurs while attempting to
   *                       decode the provided ASN.1 element as an
   *                       LDAP message.
   */
  private void processElement(ASN1Element element)
          throws IOException
  {
    LDAPMessage message;
    try
    {
      message = LDAPMessage.decode(element.decodeAsSequence());
    }
    catch (Exception e)
    {
      throw new IOException(e.getMessage(), e);
    }
    switch (message.getProtocolOpType())
    {
      case OP_TYPE_ABANDON_REQUEST:
        // No action is required.
        return;
      case OP_TYPE_ADD_REQUEST:
        processAddOperation(message);
        break;
      case OP_TYPE_BIND_REQUEST:
        processBindOperation(message);
        break;
      case OP_TYPE_COMPARE_REQUEST:
        processCompareOperation(message);
        break;
      case OP_TYPE_DELETE_REQUEST:
        processDeleteOperation(message);
        break;
      case OP_TYPE_EXTENDED_REQUEST:
        processExtendedOperation(message);
        break;
      case OP_TYPE_MODIFY_REQUEST:
        processModifyOperation(message);
        break;
      case OP_TYPE_MODIFY_DN_REQUEST:
        processModifyDNOperation(message);
        break;
      case OP_TYPE_SEARCH_REQUEST:
        processSearchOperation(message);
        break;
      case OP_TYPE_UNBIND_REQUEST:
        socket.close();
        break;
      default:
        Message m = ERR_INTERNALOS_INVALID_REQUEST.get(
                         message.getProtocolElementName());
        throw new IOException(m.toString());
    }
  }
  /**
   * Processes the content of the provided LDAP message as an add
   * operation and returns the appropriate result to the client.
   *
   * @param  message  The LDAP message containing the request to
   *                  process.
   *
   * @throws  IOException  If a problem occurs while attempting to
   *                       process the operation.
   */
  private void processAddOperation(LDAPMessage message)
          throws IOException
  {
    int messageID = message.getMessageID();
    AddRequestProtocolOp request = message.getAddRequestProtocolOp();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    if (message.getControls() != null)
    {
      for (LDAPControl c : message.getControls())
      {
        requestControls.add(c.getControl());
      }
    }
    InternalClientConnection conn = socket.getConnection();
    AddOperationBasis op =
         new AddOperationBasis(conn, conn.nextOperationID(),
                               messageID, requestControls,
                               request.getDN(),
                               request.getAttributes());
    op.run();
    AddResponseProtocolOp addResponse =
         new AddResponseProtocolOp(op.getResultCode().getIntValue(),
                                   op.getErrorMessage().toMessage(),
                                   op.getMatchedDN(),
                                   op.getReferralURLs());
    ArrayList<LDAPControl> responseControls =
         new ArrayList<LDAPControl>();
    for (Control c : op.getResponseControls())
    {
      responseControls.add(new LDAPControl(c));
    }
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(messageID, addResponse, responseControls));
  }
  /**
   * Processes the content of the provided LDAP message as a bind
   * operation and returns the appropriate result to the client.
   *
   * @param  message  The LDAP message containing the request to
   *                  process.
   *
   * @throws  IOException  If a problem occurs while attempting to
   *                       process the operation.
   */
  private void processBindOperation(LDAPMessage message)
          throws IOException
  {
    int messageID = message.getMessageID();
    BindRequestProtocolOp request =
         message.getBindRequestProtocolOp();
    if (request.getAuthenticationType() == AuthenticationType.SASL)
    {
      Message m = ERR_INTERNALOS_SASL_BIND_NOT_SUPPORTED.get();
      BindResponseProtocolOp bindResponse =
           new BindResponseProtocolOp(
                    LDAPResultCode.UNWILLING_TO_PERFORM, m);
      socket.getInputStream().addLDAPMessage(
           new LDAPMessage(messageID, bindResponse));
      return;
    }
    ArrayList<Control> requestControls = new ArrayList<Control>();
    if (message.getControls() != null)
    {
      for (LDAPControl c : message.getControls())
      {
        requestControls.add(c.getControl());
      }
    }
    InternalClientConnection conn = socket.getConnection();
    BindOperationBasis op =
         new BindOperationBasis(conn, conn.nextOperationID(),
                  messageID, requestControls,
                  String.valueOf(request.getProtocolVersion()),
                  request.getDN(), request.getSimplePassword());
    op.run();
    BindResponseProtocolOp bindResponse =
         new BindResponseProtocolOp(op.getResultCode().getIntValue(),
                                    op.getErrorMessage().toMessage(),
                                    op.getMatchedDN(),
                                    op.getReferralURLs());
    ArrayList<LDAPControl> responseControls =
         new ArrayList<LDAPControl>();
    for (Control c : op.getResponseControls())
    {
      responseControls.add(new LDAPControl(c));
    }
    if (bindResponse.getResultCode() == LDAPResultCode.SUCCESS)
    {
      socket.setConnection(new InternalClientConnection(
           op.getAuthenticationInfo()));
    }
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(messageID, bindResponse, responseControls));
  }
  /**
   * Processes the content of the provided LDAP message as a compare
   * operation and returns the appropriate result to the client.
   *
   * @param  message  The LDAP message containing the request to
   *                  process.
   *
   * @throws  IOException  If a problem occurs while attempting to
   *                       process the operation.
   */
  private void processCompareOperation(LDAPMessage message)
          throws IOException
  {
    int messageID = message.getMessageID();
    CompareRequestProtocolOp request =
         message.getCompareRequestProtocolOp();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    if (message.getControls() != null)
    {
      for (LDAPControl c : message.getControls())
      {
        requestControls.add(c.getControl());
      }
    }
    InternalClientConnection conn = socket.getConnection();
    CompareOperationBasis op =
         new CompareOperationBasis(conn, conn.nextOperationID(),
                  messageID, requestControls, request.getDN(),
                  request.getAttributeType(),
                  request.getAssertionValue());
    op.run();
    CompareResponseProtocolOp compareResponse =
         new CompareResponseProtocolOp(
                  op.getResultCode().getIntValue(),
                  op.getErrorMessage().toMessage(),
                  op.getMatchedDN(),
                  op.getReferralURLs());
    ArrayList<LDAPControl> responseControls =
         new ArrayList<LDAPControl>();
    for (Control c : op.getResponseControls())
    {
      responseControls.add(new LDAPControl(c));
    }
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(messageID, compareResponse,
                         responseControls));
  }
  /**
   * Processes the content of the provided LDAP message as a delete
   * operation and returns the appropriate result to the client.
   *
   * @param  message  The LDAP message containing the request to
   *                  process.
   *
   * @throws  IOException  If a problem occurs while attempting to
   *                       process the operation.
   */
  private void processDeleteOperation(LDAPMessage message)
          throws IOException
  {
    int messageID = message.getMessageID();
    DeleteRequestProtocolOp request =
         message.getDeleteRequestProtocolOp();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    if (message.getControls() != null)
    {
      for (LDAPControl c : message.getControls())
      {
        requestControls.add(c.getControl());
      }
    }
    InternalClientConnection conn = socket.getConnection();
    DeleteOperationBasis op =
         new DeleteOperationBasis(conn, conn.nextOperationID(),
                  messageID, requestControls, request.getDN());
    op.run();
    DeleteResponseProtocolOp deleteResponse =
         new DeleteResponseProtocolOp(
                  op.getResultCode().getIntValue(),
                  op.getErrorMessage().toMessage(),
                  op.getMatchedDN(),
                  op.getReferralURLs());
    ArrayList<LDAPControl> responseControls =
         new ArrayList<LDAPControl>();
    for (Control c : op.getResponseControls())
    {
      responseControls.add(new LDAPControl(c));
    }
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(messageID, deleteResponse,
                         responseControls));
  }
  /**
   * Processes the content of the provided LDAP message as an extended
   * operation and returns the appropriate result to the client.
   *
   * @param  message  The LDAP message containing the request to
   *                  process.
   *
   * @throws  IOException  If a problem occurs while attempting to
   *                       process the operation.
   */
  private void processExtendedOperation(LDAPMessage message)
          throws IOException
  {
    int messageID = message.getMessageID();
    ExtendedRequestProtocolOp request =
         message.getExtendedRequestProtocolOp();
    if (request.getOID().equals(OID_START_TLS_REQUEST))
    {
      Message m = ERR_INTERNALOS_STARTTLS_NOT_SUPPORTED.get();
      ExtendedResponseProtocolOp extendedResponse =
           new ExtendedResponseProtocolOp(
                    LDAPResultCode.UNWILLING_TO_PERFORM, m);
      socket.getInputStream().addLDAPMessage(
           new LDAPMessage(messageID, extendedResponse));
      return;
    }
    ArrayList<Control> requestControls = new ArrayList<Control>();
    if (message.getControls() != null)
    {
      for (LDAPControl c : message.getControls())
      {
        requestControls.add(c.getControl());
      }
    }
    InternalClientConnection conn = socket.getConnection();
    ExtendedOperationBasis op =
         new ExtendedOperationBasis(conn, conn.nextOperationID(),
                  messageID, requestControls, request.getOID(),
                  request.getValue());
    op.run();
    ExtendedResponseProtocolOp extendedResponse =
         new ExtendedResponseProtocolOp(
                  op.getResultCode().getIntValue(),
                  op.getErrorMessage().toMessage(),
                  op.getMatchedDN(),
                  op.getReferralURLs(), op.getResponseOID(),
                  op.getResponseValue());
    ArrayList<LDAPControl> responseControls =
         new ArrayList<LDAPControl>();
    for (Control c : op.getResponseControls())
    {
      responseControls.add(new LDAPControl(c));
    }
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(messageID, extendedResponse,
                         responseControls));
  }
  /**
   * Processes the content of the provided LDAP message as a modify
   * operation and returns the appropriate result to the client.
   *
   * @param  message  The LDAP message containing the request to
   *                  process.
   *
   * @throws  IOException  If a problem occurs while attempting to
   *                       process the operation.
   */
  private void processModifyOperation(LDAPMessage message)
          throws IOException
  {
    int messageID = message.getMessageID();
    ModifyRequestProtocolOp request =
         message.getModifyRequestProtocolOp();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    if (message.getControls() != null)
    {
      for (LDAPControl c : message.getControls())
      {
        requestControls.add(c.getControl());
      }
    }
    InternalClientConnection conn = socket.getConnection();
    ModifyOperationBasis op =
         new ModifyOperationBasis(conn, conn.nextOperationID(),
                  messageID, requestControls, request.getDN(),
                  request.getModifications());
    op.run();
    ModifyResponseProtocolOp modifyResponse =
         new ModifyResponseProtocolOp(
                  op.getResultCode().getIntValue(),
                  op.getErrorMessage().toMessage(),
                  op.getMatchedDN(),
                  op.getReferralURLs());
    ArrayList<LDAPControl> responseControls =
         new ArrayList<LDAPControl>();
    for (Control c : op.getResponseControls())
    {
      responseControls.add(new LDAPControl(c));
    }
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(messageID, modifyResponse,
                         responseControls));
  }
  /**
   * Processes the content of the provided LDAP message as a modify DN
   * operation and returns the appropriate result to the client.
   *
   * @param  message  The LDAP message containing the request to
   *                  process.
   *
   * @throws  IOException  If a problem occurs while attempting to
   *                       process the operation.
   */
  private void processModifyDNOperation(LDAPMessage message)
          throws IOException
  {
    int messageID = message.getMessageID();
    ModifyDNRequestProtocolOp request =
         message.getModifyDNRequestProtocolOp();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    if (message.getControls() != null)
    {
      for (LDAPControl c : message.getControls())
      {
        requestControls.add(c.getControl());
      }
    }
    InternalClientConnection conn = socket.getConnection();
    ModifyDNOperationBasis op =
         new ModifyDNOperationBasis(conn, conn.nextOperationID(),
                  messageID, requestControls, request.getEntryDN(),
                  request.getNewRDN(), request.deleteOldRDN(),
                  request.getNewSuperior());
    op.run();
    ModifyDNResponseProtocolOp modifyDNResponse =
         new ModifyDNResponseProtocolOp(
                  op.getResultCode().getIntValue(),
                  op.getErrorMessage().toMessage(),
                  op.getMatchedDN(),
                  op.getReferralURLs());
    ArrayList<LDAPControl> responseControls =
         new ArrayList<LDAPControl>();
    for (Control c : op.getResponseControls())
    {
      responseControls.add(new LDAPControl(c));
    }
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(messageID, modifyDNResponse,
                         responseControls));
  }
  /**
   * Processes the content of the provided LDAP message as a search
   * operation and returns the appropriate result to the client.
   *
   * @param  message  The LDAP message containing the request to
   *                  process.
   *
   * @throws  IOException  If a problem occurs while attempting to
   *                       process the operation.
   */
  private void processSearchOperation(LDAPMessage message)
          throws IOException
  {
    int messageID = message.getMessageID();
    SearchRequestProtocolOp request =
         message.getSearchRequestProtocolOp();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    if (message.getControls() != null)
    {
      for (LDAPControl c : message.getControls())
      {
        requestControls.add(c.getControl());
      }
    }
    InternalClientConnection conn = socket.getConnection();
    InternalSearchOperation op =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  messageID, requestControls, request.getBaseDN(),
                  request.getScope(), request.getDereferencePolicy(),
                  request.getSizeLimit(), request.getTimeLimit(),
                  request.getTypesOnly(), request.getFilter(),
                  request.getAttributes(), this);
    op.run();
    SearchResultDoneProtocolOp searchDone =
         new SearchResultDoneProtocolOp(
                  op.getResultCode().getIntValue(),
                  op.getErrorMessage().toMessage(),
                  op.getMatchedDN(),
                  op.getReferralURLs());
    ArrayList<LDAPControl> responseControls =
         new ArrayList<LDAPControl>();
    for (Control c : op.getResponseControls())
    {
      responseControls.add(new LDAPControl(c));
    }
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(messageID, searchDone, responseControls));
  }
  /**
   * Performs any processing necessary for the provided search result
   * entry.
   *
   * @param  searchOperation  The internal search operation being
   *                          processed.
   * @param  searchEntry      The matching search result entry to be
   *                          processed.
   */
  @org.opends.server.types.PublicAPI(
       stability=org.opends.server.types.StabilityLevel.PRIVATE,
       mayInstantiate=false,
       mayExtend=false,
       mayInvoke=false)
  public void handleInternalSearchEntry(
                   InternalSearchOperation searchOperation,
                   SearchResultEntry searchEntry)
  {
    ArrayList<LDAPControl> entryControls =
         new ArrayList<LDAPControl>();
    for (Control c : searchEntry.getControls())
    {
      entryControls.add(new LDAPControl(c));
    }
    SearchResultEntryProtocolOp entry =
         new SearchResultEntryProtocolOp(searchEntry);
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(searchOperation.getMessageID(), entry,
                         entryControls));
  }
  /**
   * Performs any processing necessary for the provided search result
   * reference.
   *
   * @param  searchOperation  The internal search operation being
   *                          processed.
   * @param  searchReference  The search result reference to be
   *                          processed.
   */
  @org.opends.server.types.PublicAPI(
       stability=org.opends.server.types.StabilityLevel.PRIVATE,
       mayInstantiate=false,
       mayExtend=false,
       mayInvoke=false)
  public void handleInternalSearchReference(
                   InternalSearchOperation searchOperation,
                   SearchResultReference searchReference)
  {
    ArrayList<LDAPControl> entryControls =
         new ArrayList<LDAPControl>();
    for (Control c : searchReference.getControls())
    {
      entryControls.add(new LDAPControl(c));
    }
    SearchResultReferenceProtocolOp reference =
         new SearchResultReferenceProtocolOp(searchReference);
    socket.getInputStream().addLDAPMessage(
         new LDAPMessage(searchOperation.getMessageID(), reference,
                         entryControls));
  }
  /**
   * Retrieves a string representation of this internal LDAP socket.
   *
   * @return  A string representation of this internal LDAP socket.
   */
  @Override()
  public String toString()
  {
    return "InternalLDAPOutputStream";
  }
}
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocket.java
New file
@@ -0,0 +1,860 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.internal;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import org.opends.server.types.DN;
/**
 * This class provides an implementation of a {@code java.net.Socket}
 * object that can be used to facilitate internal communication with
 * the Directory Server through third-party LDAP APIs that provide the
 * ability to use a custom socket factory when creating connections.
 * Whenever data is written over the socket, it is decoded as LDAP
 * communication and converted to an appropriate internal operation,
 * which the server then processes and converts the response back to
 * an LDAP encoding.
 * <BR><BR>
 * Note that this implementation only supports those operations which
 * can be performed in the Directory Server via internal operations.
 * This includes add, compare, delete, modify, modify DN, and search
 * operations, and some types of extended operations.  Special support
 * has been added for simple bind operations to function properly, but
 * SASL binds are not supported.  Abandon and unbind operations are
 * not supported, nor are the cancel or StartTLS extended operations.
 * Only clear-text LDAP communication may be used.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
     mayInstantiate=true,
     mayExtend=false,
     mayInvoke=true)
public final class InternalLDAPSocket
       extends Socket
{
  // Indicates whether this socket is closed.
  private boolean closed;
  // The value that the client has requested for SO_KEEPALIVE.
  private boolean keepAlive;
  // The value that the client has requested for OOBINLINE.
  private boolean oobInline;
  // The value that the client has requested for SO_REUSEADDR.
  private boolean reuseAddress;
  // The value that the client has requested for TCP_NODELAY.
  private boolean tcpNoDelay;
  // The value that the client has requested for SO_LINGER.
  private int lingerDuration;
  // The value that the client has requested for SO_RCVBUF.
  private int receiveBufferSize;
  // The value that the client has requested for SO_SNDBUF.
  private int sendBufferSize;
  // The value that the client has requested for SO_TIMEOUT.
  private int timeout;
  // The value that the client has requested for the traffic class.
  private int trafficClass;
  // The internal client connection used to perform the internal
  // operations.  It will be null until it is first used.
  private InternalClientConnection conn;
  // The input stream associated with this internal LDAP socket.
  private InternalLDAPInputStream inputStream;
  // The output stream associated with this internal LDAP socket.
  private InternalLDAPOutputStream outputStream;
  /**
   * Creates a new internal LDAP socket.
   */
  public InternalLDAPSocket()
  {
    closed            = false;
    keepAlive         = true;
    oobInline         = true;
    reuseAddress      = true;
    tcpNoDelay        = true;
    lingerDuration    = 0;
    receiveBufferSize = 1024;
    sendBufferSize    = 1024;
    timeout           = 0;
    trafficClass      = 0;
    conn              = null;
    inputStream       = new InternalLDAPInputStream(this);
    outputStream      = new InternalLDAPOutputStream(this);
  }
  /**
   * Retrieves the internal client connection used to back this
   * internal LDAP socket.
   *
   * @return  The internal client connection used to back this
   *          internal LDAP socket.
   *
   * @throws  IOException  If there is a problem obtaining the
   *                       connection.
   */
  @org.opends.server.types.PublicAPI(
       stability=org.opends.server.types.StabilityLevel.PRIVATE,
       mayInstantiate=false,
       mayExtend=false,
       mayInvoke=false)
  synchronized InternalClientConnection getConnection()
               throws IOException
  {
    if (conn == null)
    {
      try
      {
        conn = new InternalClientConnection(DN.nullDN());
      }
      catch (Exception e)
      {
        // This should never happen.
        throw new IOException(e.getMessage(), e);
      }
    }
    return conn;
  }
  /**
   * Sets the internal client connection used to back this internal
   * LDAP socket.
   *
   * @param  conn  The internal client connection used to back this
   *               internal LDAP socket.
   */
  @org.opends.server.types.PublicAPI(
       stability=org.opends.server.types.StabilityLevel.PRIVATE,
       mayInstantiate=false,
       mayExtend=false,
       mayInvoke=false)
  synchronized void setConnection(InternalClientConnection conn)
  {
    this.conn = conn;
  }
  /**
   * Binds the socket to a local address.  This does nothing, since
   * there is no actual network communication performed by this
   * socket implementation.
   *
   * @param  bindpoint  The socket address to which to bind.
   */
  @Override()
  public void bind(SocketAddress bindpoint)
  {
    // No implementation is required.
  }
  /**
   * Closes this socket.  This will make it unavailable for use.
   */
  @Override()
  public synchronized void close()
  {
    try
    {
      inputStream.closeInternal();
    } catch (Exception e) {}
    try
    {
      outputStream.closeInternal();
    } catch (Exception e) {}
    closed       = true;
    inputStream  = null;
    outputStream = null;
  }
  /**
   * Connects this socket to the specified remote endpoint.  This will
   * make the connection available again if it has been previously
   * closed.  The provided address is irrelevant, as it will always be
   * an internal connection.
   *
   * @param  endpoint  The address of the remote endpoint.
   */
  @Override()
  public synchronized void connect(SocketAddress endpoint)
  {
    closed       = false;
    inputStream  = new InternalLDAPInputStream(this);
    outputStream = new InternalLDAPOutputStream(this);
  }
  /**
   * Connects this socket to the specified remote endpoint.  This does
   * nothing, since there is no actual network communication performed
   * by this socket implementation.
   *
   * @param  endpoint  The address of the remote endpoint.
   * @param  timeout   The maximum length of time in milliseconds to
   *                   wait for the connection to be established.
   */
  @Override()
  public void connect(SocketAddress endpoint, int timeout)
  {
    closed       = false;
    inputStream  = new InternalLDAPInputStream(this);
    outputStream = new InternalLDAPOutputStream(this);
  }
  /**
   * Retrieves the socket channel associated with this socket.  This
   * method always returns {@code null} since this implementation does
   * not support use with NIO channels.
   *
   * @return  {@code null} because this implementation does not
   *          support use with NIO channels.
   */
  @Override()
  public SocketChannel getChannel()
  {
    // This implementation does not support use with NIO channels.
    return null;
  }
  /**
   * Retrieves the address to which this socket is connected.  The
   * address returned is meaningless, since there is no actual network
   * communication performed by this socket implementation.
   *
   * @return The address to which this socket is connected.
   */
  @Override()
  public InetAddress getInetAddress()
  {
    try
    {
      return InetAddress.getLocalHost();
    }
    catch (Exception e)
    {
      // This should not happen.
      return null;
    }
  }
  /**
   * Retrieves the input stream for this socket.
   *
   * @return  The input stream for this socket.
   */
  @Override()
  public InternalLDAPInputStream getInputStream()
  {
    return inputStream;
  }
  /**
   * Indicates whether SO_KEEPALIVE is enabled.  This implementation
   * will return {@code true} by default, but if its value is changed
   * using {@code setKeepalive} then that value will be returned.
   * This setting has no effect in this socket implementation.
   *
   * @return  {@code true} if SO_KEEPALIVE is enabled, or
   *          {@code false} if not.
   */
  @Override()
  public boolean getKeepAlive()
  {
    return keepAlive;
  }
  /**
   * Retrieves the local address to which this socket is bound.  The
   * address returned is meaningless, since there is no actual network
   * communication performed by this socket implementation.
   *
   * @return The local address to which this socket is bound.
   */
  @Override()
  public InetAddress getLocalAddress()
  {
    try
    {
      return InetAddress.getLocalHost();
    }
    catch (Exception e)
    {
      // This should not happen.
      return null;
    }
  }
  /**
   * Retrieves the local port to which this socket is bound.  The
   * value returned is meaningless, since there is no actual network
   * communication performed by this socket implementation.
   *
   * @return  The local port to which this socket is bound.
   */
  @Override()
  public int getLocalPort()
  {
    return 389;
  }
  /**
   * Retrieves the local socket address to which this socket is bound.
   * The value returned is meaningless, since there is no actual
   * network communication performed by this socket implementation.
   *
   * @return  The local socket address to which this socket is bound.
   */
  @Override()
  public SocketAddress getLocalSocketAddress()
  {
    try
    {
      return new InetSocketAddress(getLocalAddress(), getLocalPort());
    }
    catch (Exception e)
    {
      // This should not happen.
      return null;
    }
  }
  /**
   * Indicates whether OOBINLINE is enabled.  This implementation will
   * return {@code true} by default, but if its value is changed
   * using {@code setOOBInline} then that value will be returned.
   * This setting has no effect in this socket implementation.
   *
   * @return  {@code true} if OOBINLINE is enabled, or {@code false}
   *          if it is not.
   */
  @Override()
  public boolean getOOBInline()
  {
    return oobInline;
  }
  /**
   * Retrieves the output stream for this socket.
   *
   * @return  The output stream for this socket.
   */
  @Override()
  public InternalLDAPOutputStream getOutputStream()
  {
    return outputStream;
  }
  /**
   * Retrieves the remote port to which this socket is connected.  The
   * value returned is meaningless, since there is no actual network
   * communication performed by this socket implementation.
   *
   * @return  The remote port to which this socket is connected.
   */
  @Override()
  public int getPort()
  {
    return 389;
  }
  /**
   * Retrieves the value of the SO_RCVBUF option for this socket.  The
   * value returned is meaningless, since there is no actual network
   * communication performed by this socket implementation.
   *
   * @return  The value of the SO_RCVBUF option for this socket.
   */
  @Override()
  public int getReceiveBufferSize()
  {
    return receiveBufferSize;
  }
  /**
   * Retrieves the remote socket address to which this socket is
   * connected.  The value returned is meaningless, since there is no
   * actual network communication performed by this socket
   * implementation.
   *
   * @return  The remote socket address to which this socket is
   *          connected.
   */
  @Override()
  public SocketAddress getRemoteSocketAddress()
  {
    try
    {
      return new InetSocketAddress(getInetAddress(), getPort());
    }
    catch (Exception e)
    {
      // This should not happen.
      return null;
    }
  }
  /**
   * Indicates whether SO_REUSEADDR is enabled.  This implementation
   * will return {@code true} by default, but if its value is changed
   * using {@code setReuseAddress} then that value will be returned.
   * This setting has no effect in this socket implementation.
   *
   * @return  {@code true} if SO_REUSEADDR is enabled, or
   *          {@code false} if it is not.
   */
  @Override()
  public boolean getReuseAddress()
  {
    return reuseAddress;
  }
  /**
   * Retrieves the value of the SO_SNDBUF option for this socket.  The
   * value returned is meaningless, since there is no actual network
   * communication performed by this socket implementation.
   *
   * @return  The value of the SO_SNDBUF option for this socket.
   */
  @Override()
  public int getSendBufferSize()
  {
    return sendBufferSize;
  }
  /**
   * Retrieves the value of the SO_LINGER option for this socket.  The
   * value returned is meaningless, since there is no actual network
   * communication performed by this socket implementation.
   *
   * @return  The value of the SO_LINGER option for this socket.
   */
  @Override()
  public int getSoLinger()
  {
    return lingerDuration;
  }
  /**
   * Retrieves the value of the SO_TIMEOUT option for this socket.
   * The value returned is meaningless, since there is no actual
   * network communication performed by this socket implementation.
   *
   * @return  The value of the SO_TIMEOUT option for this socket.
   */
  @Override()
  public int getSoTimeout()
  {
    return timeout;
  }
  /**
   * Indicates whether TCP_NODELAY is enabled.  This implementation
   * will return {@code true} by default, but if its value is changed
   * using {@code setTcpNoDelay} then that value will be returned.
   * This setting has no effect in this socket implementation.
   *
   * @return  {@code true} if TCP_NODELAY is enabled, or {@code false}
   *          if it is not.
   */
  @Override()
  public boolean getTcpNoDelay()
  {
    return tcpNoDelay;
  }
  /**
   * Retrieves the traffic class for this socket.  The value returned
   * will be meaningless, since there is no actual network
   * communication performed by this socket.
   *
   * @return  The traffic class for this socket.
   */
  @Override()
  public int getTrafficClass()
  {
    return trafficClass;
  }
  /**
   * Indicates whether this socket is bound to a local address.  This
   * method will always return {@code true} to indicate that it is
   * bound.
   *
   * @return  {@code true} to indicate that the socket is bound to a
   *          local address.
   */
  @Override()
  public boolean isBound()
  {
    return true;
  }
  /**
   * Indicates whether this socket is closed.  This method will always
   * return {@code false} to indicate that it is not closed.
   *
   * @return  {@code false} to indicate that the socket is not closed.
   */
  @Override()
  public boolean isClosed()
  {
    return closed;
  }
  /**
   * Indicates whether this socket is connected to both local and
   * remote endpoints.  This method will always return {@code true} to
   * indicate that it is connected.
   *
   * @return  {@code true} to indicate that the socket is connected.
   */
  @Override()
  public boolean isConnected()
  {
    return (! closed);
  }
  /**
   * Indicates whether the input side of this socket has been closed.
   * This method will always return {@code false} to indicate that it
   * is not closed.
   *
   * @return  {@code false} to indicate that the input side of this
   *          socket is not closed.
   */
  @Override()
  public boolean isInputShutdown()
  {
    return closed;
  }
  /**
   * Indicates whether the output side of this socket has been closed.
   * This method will always return {@code false} to indicate that it
   * is not closed.
   *
   * @return  {@code false} to indicate that the output side of this
   *          socket is not closed.
   */
  @Override()
  public boolean isOutputShutdown()
  {
    return closed;
  }
  /**
   * Sends a single byte of urgent data over this socket.
   *
   * @param  data  The data to be sent.
   *
   * @throws  IOException  If a problem occurs while trying to write
   *                       the provided data over this socket.
   */
  @Override()
  public void sendUrgentData(int data)
         throws IOException
  {
    getOutputStream().write(data);
  }
  /**
   * Sets the value of SO_KEEPALIVE for this socket.  This will not
   * affect anything, since there is no actual network communication
   * performed by this socket.
   *
   * @param  on  The value to use for the SO_KEEPALIVE option.
   */
  @Override()
  public void setKeepAlive(boolean on)
  {
    keepAlive = on;
  }
  /**
   * Sets the value of OOBINLINE for this socket.  This will not
   * affect anything, since there is no actual network communication
   * performed by this socket.
   *
   * @param  on  The value to use for the OOBINLINE option.
   */
  @Override()
  public void setOOBInline(boolean on)
  {
    oobInline = on;
  }
  /**
   * Sets the provided performance preferences for this socket.  This
   * will not affect anything, since there is no actual network
   * communication performed by this socket.
   *
   * @param  connectionTime  An {@code int} expressing the relative
   *                         importance of a short connection time.
   * @param  latency         An {@code int} expressing the relative
   *                         importance of low latency.
   * @param  bandwidth       An {@code int} expressing the relative
   *                         importance of high bandwidth.
   */
  @Override()
  public void setPerformancePreferences(int connectionTime,
                                        int latency, int bandwidth)
  {
    // No implementation is required.
  }
  /**
   * Sets the value of SO_RCVBUF for this socket.  This will not
   * affect anything, since there is no actual network communication
   * performed by this socket.
   *
   * @param  size  The value to use for the SO_RCVBUF option.
   */
  @Override()
  public void setReceiveBufferSize(int size)
  {
    receiveBufferSize = size;
  }
  /**
   * Sets the value of SO_REUSEADDR for this socket.  This will not
   * affect anything, since there is no actual network communication
   * performed by this socket.
   *
   * @param  on  The value to use for the SO_REUSEADDR option.
   */
  @Override()
  public void setReuseAddress(boolean on)
  {
    reuseAddress = on;
  }
  /**
   * Sets the value of SO_SNDBUF for this socket.  This will not
   * affect anything, since there is no actual network communication
   * performed by this socket.
   *
   * @param  size  The value to use for the SO_SNDBUF option.
   */
  @Override()
  public void setSendBufferSize(int size)
  {
    sendBufferSize = size;
  }
  /**
   * Sets the value of SO_LINGER for this socket.  This will not
   * affect anything, since there is no actual network communication
   * performed by this socket.
   *
   * @param  on      Indicates whether to enable the linger option.
   * @param  linger  The length of time in milliseconds to allow the
   *                 connection to linger.
   */
  @Override()
  public void setSoLinger(boolean on, int linger)
  {
    lingerDuration = linger;
  }
  /**
   * Sets the value of SO_TIMEOUT for this socket.  This will not
   * affect anything, since there is no actual network communication
   * performed by this socket.
   *
   * @param  timeout  The value to use for the SO_TIMEOUT option.
   */
  @Override()
  public void setSoTimeout(int timeout)
  {
    this.timeout = timeout;
  }
  /**
   * Sets the value of TCP_NODELAY for this socket.  This will not
   * affect anything, since there is no actual network communication
   * performed by this socket.
   *
   * @param  on  The value to use for the TCP_NODELAY option.
   */
  @Override()
  public void setTcpNoDelay(boolean on)
  {
    tcpNoDelay = on;
  }
  /**
   * Sets the traffic class for this socket.  This will not affect
   * anything, since there is no actual network communication
   * performed by this socket.
   *
   * @param  tc  The value to use for the traffic class.
   */
  @Override()
  public void setTrafficClass(int tc)
  {
    trafficClass = tc;
  }
  /**
   * Shuts down the input side of this socket.  This will have the
   * effect of closing the entire socket.
   */
  @Override()
  public void shutdownInput()
  {
    close();
  }
  /**
   * Shuts down the output side of this socket.  This will have the
   * effect of closing the entire socket.
   */
  @Override()
  public void shutdownOutput()
  {
    close();
  }
  /**
   * Retrieves a string representation of this internal LDAP socket.
   *
   * @return  A string representation of this internal LDAP socket.
   */
  @Override()
  public String toString()
  {
    return "InternalLDAPSocket";
  }
}
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocketFactory.java
New file
@@ -0,0 +1,180 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.internal;
import java.net.InetAddress;
import java.net.Socket;
import javax.net.SocketFactory;
/**
 * This class provides an implementation of a
 * {@code javax.net.SocketFactory} object that can be used to create
 * internal LDAP sockets.  This socket factory can be used with some
 * common LDAP SDKs (e.g., JNDI) in order to allow that SDK to be used
 * to perform internal operations within OpenDS with minimal changes
 * needed from what is required to perform external LDAP
 * communication.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
     mayInstantiate=true,
     mayExtend=false,
     mayInvoke=true)
public final class InternalLDAPSocketFactory
       extends SocketFactory
{
  /**
   * Creates a new instance of this internal LDAP socket factory.
   */
  public InternalLDAPSocketFactory()
  {
    // No implementation is required.
  }
  /**
   * Retrieves the default socket factory that should be used.  Note
   * that this method must be present for the implementation to work
   * properly.  Even though the superclass declares the same static
   * method and static methods are not generally overridden, that is
   * not the case here because the method is invoked through
   * reflection, and the superclass returns a bogus socket factory.
   *
   * @return  The default socket factory that should be used.
   */
  public static SocketFactory getDefault()
  {
    return new InternalLDAPSocketFactory();
  }
  /**
   * Creates a new internal LDAP socket.  The provided arguments will
   * be ignored, as they are not needed by this implementation.
   *
   * @param  host  The remote address to which the socket should be
   *               connected.
   * @param  port  The remote port to which the socket should be
   *               connected.
   *
   * @return  The created internal LDAP socket.
   */
  @Override()
  public Socket createSocket(InetAddress host, int port)
  {
    return new InternalLDAPSocket();
  }
  /**
   * Creates a new internal LDAP socket.  The provided arguments will
   * be ignored, as they are not needed by this implementation.
   *
   * @param  host  The remote address to which the socket should be
   *               connected.
   * @param  port  The remote port to which the socket should be
   *               connected.
   *
   * @return  The created internal LDAP socket.
   */
  @Override()
  public Socket createSocket(String host, int port)
  {
    return new InternalLDAPSocket();
  }
  /**
   * Creates a new internal LDAP socket.  The provided arguments will
   * be ignored, as they are not needed by this implementation.
   *
   * @param  host        The remote address to which the socket should
   *                     be connected.
   * @param  port        The remote port to which the socket should be
   *                     connected.
   * @param  clientHost  The local address to which the socket should
   *                     be bound.
   * @param  clientPort  The local port to which the socket should be
   *                     bound.
   *
   * @return  The created internal LDAP socket.
   */
  @Override()
  public Socket createSocket(InetAddress host, int port,
                             InetAddress clientHost, int clientPort)
  {
    return new InternalLDAPSocket();
  }
  /**
   * Creates a new internal LDAP socket.  The provided arguments will
   * be ignored, as they are not needed by this implementation.
   *
   * @param  host        The remote address to which the socket should
   *                     be connected.
   * @param  port        The remote port to which the socket should be
   *                     connected.
   * @param  clientHost  The local address to which the socket should
   *                     be bound.
   * @param  clientPort  The local port to which the socket should be
   *                     bound.
   *
   * @return  The created internal LDAP socket.
   */
  @Override()
  public Socket createSocket(String host, int port,
                             InetAddress clientHost, int clientPort)
  {
    return new InternalLDAPSocket();
  }
  /**
   * Retrieves a string representation of this internal LDAP socket
   * factory.
   *
   * @return  A string representation of this internal LDAP socket
   *          factory.
   */
  @Override()
  public String toString()
  {
    return "InternalLDAPSocketFactory";
  }
}
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/NullLDAPMessage.java
New file
@@ -0,0 +1,58 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.internal;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.UnbindRequestProtocolOp;
/**
 * This class provides a special implementation of an LDAP message
 * that will serve as a marker in the internal LDAP input stream
 * message queue that the end of the stream has been reached.  It
 * should not be used for any other purpose.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.PRIVATE,
     mayInstantiate=false,
     mayExtend=false,
     mayInvoke=false)
final class NullLDAPMessage
       extends LDAPMessage
{
  /**
   * Creates a new null LDAP message.
   */
  NullLDAPMessage()
  {
    super(1, new UnbindRequestProtocolOp());
  }
}
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalLDAPSocketTestCase.java
New file
@@ -0,0 +1,662 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.internal;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedHashSet;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.ldap.*;
import org.opends.server.tools.LDAPReader;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.*;
import static org.testng.Assert.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * This class provides a number of tests to cover the internal LDAP socket
 * implementation.
 */
public class InternalLDAPSocketTestCase
       extends InternalTestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Tests the ability to perform an add operation over the internal LDAP
   * socket.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddOperation()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(false);
    assertFalse(DirectoryServer.entryExists(DN.decode("o=test")));
    InternalLDAPSocket socket = new InternalLDAPSocket();
    LDAPReader reader = new LDAPReader(socket);
    LDAPWriter writer = new LDAPWriter(socket);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
                                   3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    writer.writeMessage(message);
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
    ArrayList<RawAttribute> attrList = new ArrayList<RawAttribute>();
    attrList.add(RawAttribute.create("objectClass", "organization"));
    attrList.add(RawAttribute.create("o", "test"));
    AddRequestProtocolOp addRequest =
         new AddRequestProtocolOp(new ASN1OctetString("o=test"), attrList);
    writer.writeMessage(new LDAPMessage(2, addRequest));
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getAddResponseProtocolOp().getResultCode(),
                 LDAPResultCode.SUCCESS);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    reader.close();
    writer.close();
    socket.close();
  }
  /**
   * Tests the ability to perform an add operation over the internal LDAP
   * socket via JNDI.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddOperationThroughJNDI()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(false);
    assertFalse(DirectoryServer.entryExists(DN.decode("o=test")));
    Hashtable<String,String> env = new Hashtable<String,String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put("java.naming.ldap.factory.socket",
            InternalLDAPSocketFactory.class.getName());
    env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
    env.put(Context.SECURITY_CREDENTIALS, "password");
    DirContext context = new InitialDirContext(env);
    Attributes attributes = new BasicAttributes(true);
    Attribute objectClass = new BasicAttribute("objectClass");
    objectClass.add("top");
    objectClass.add("organization");
    attributes.put(objectClass);
    Attribute o = new BasicAttribute("o");
    o.add("test");
    attributes.put(o);
    context.createSubcontext("o=test", attributes);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    context.close();
  }
  /**
   * Tests the ability to perform a compare operation over the internal LDAP
   * socket.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testCompareOperation()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    InternalLDAPSocket socket = new InternalLDAPSocket();
    LDAPReader reader = new LDAPReader(socket);
    LDAPWriter writer = new LDAPWriter(socket);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
                                   3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    writer.writeMessage(message);
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
    CompareRequestProtocolOp compareRequest =
         new CompareRequestProtocolOp(new ASN1OctetString("o=test"), "o",
                                      new ASN1OctetString("test"));
    writer.writeMessage(new LDAPMessage(2, compareRequest));
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getCompareResponseProtocolOp().getResultCode(),
                 LDAPResultCode.COMPARE_TRUE);
    reader.close();
    writer.close();
    socket.close();
  }
  /**
   * Tests the ability to perform a compare operation over the internal LDAP
   * socket via JNDI.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testCompareOperationThroughJNDI()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    Hashtable<String,String> env = new Hashtable<String,String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put("java.naming.ldap.factory.socket",
            InternalLDAPSocketFactory.class.getName());
    env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
    env.put(Context.SECURITY_CREDENTIALS, "password");
    DirContext context = new InitialDirContext(env);
    SearchControls poorlyNamedSearchControls = new SearchControls();
    poorlyNamedSearchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
    poorlyNamedSearchControls.setReturningAttributes(new String[0]);
    NamingEnumeration results = context.search("o=test", "(o=test)",
                                               poorlyNamedSearchControls);
    assertTrue(results.hasMoreElements());
    assertNotNull(results.nextElement());
    assertFalse(results.hasMoreElements());
    context.close();
  }
  /**
   * Tests the ability to perform a delete operation over the internal LDAP
   * socket.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testDeleteOperation()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    InternalLDAPSocket socket = new InternalLDAPSocket();
    LDAPReader reader = new LDAPReader(socket);
    LDAPWriter writer = new LDAPWriter(socket);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
                                   3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    writer.writeMessage(message);
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
    DeleteRequestProtocolOp deleteRequest =
         new DeleteRequestProtocolOp(new ASN1OctetString("o=test"));
    writer.writeMessage(new LDAPMessage(2, deleteRequest));
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getDeleteResponseProtocolOp().getResultCode(),
                 LDAPResultCode.SUCCESS);
    assertFalse(DirectoryServer.entryExists(DN.decode("o=test")));
    reader.close();
    writer.close();
    socket.close();
  }
  /**
   * Tests the ability to perform a delete operation over the internal LDAP
   * socket via JNDI.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testDeleteOperationThroughJNDI()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    Hashtable<String,String> env = new Hashtable<String,String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put("java.naming.ldap.factory.socket",
            InternalLDAPSocketFactory.class.getName());
    env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
    env.put(Context.SECURITY_CREDENTIALS, "password");
    DirContext context = new InitialDirContext(env);
    context.destroySubcontext("o=test");
    assertFalse(DirectoryServer.entryExists(DN.decode("o=test")));
    context.close();
  }
  /**
   * Tests the ability to perform an extended operation over the internal LDAP
   * socket using the "Who Am I?" request.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testExtendedOperation()
         throws Exception
  {
    InternalLDAPSocket socket = new InternalLDAPSocket();
    LDAPReader reader = new LDAPReader(socket);
    LDAPWriter writer = new LDAPWriter(socket);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
                                   3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    writer.writeMessage(message);
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
    ExtendedRequestProtocolOp extendedRequest =
         new ExtendedRequestProtocolOp(OID_WHO_AM_I_REQUEST);
    writer.writeMessage(new LDAPMessage(2, extendedRequest));
    message = reader.readMessage();
    assertNotNull(message);
    ExtendedResponseProtocolOp extendedResponse =
         message.getExtendedResponseProtocolOp();
    assertEquals(extendedResponse.getResultCode(), LDAPResultCode.SUCCESS);
    assertTrue(extendedResponse.getValue().stringValue().equalsIgnoreCase(
                    "dn:cn=Directory Manager,cn=Root DNs,cn=config"));
    reader.close();
    writer.close();
    socket.close();
  }
  /**
   * Tests the ability to perform a modify operation over the internal LDAP
   * socket.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyOperation()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    InternalLDAPSocket socket = new InternalLDAPSocket();
    LDAPReader reader = new LDAPReader(socket);
    LDAPWriter writer = new LDAPWriter(socket);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
                                   3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    writer.writeMessage(message);
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
    ArrayList<RawModification> mods = new ArrayList<RawModification>();
    mods.add(RawModification.create(ModificationType.REPLACE, "description",
                                    "foo"));
    ModifyRequestProtocolOp modifyRequest =
         new ModifyRequestProtocolOp(new ASN1OctetString("o=test"), mods);
    writer.writeMessage(new LDAPMessage(2, modifyRequest));
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getModifyResponseProtocolOp().getResultCode(),
                 LDAPResultCode.SUCCESS);
    reader.close();
    writer.close();
    socket.close();
  }
  /**
   * Tests the ability to perform a modify operation over the internal LDAP
   * socket via JNDI.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyOperationThroughJNDI()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    Hashtable<String,String> env = new Hashtable<String,String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put("java.naming.ldap.factory.socket",
            InternalLDAPSocketFactory.class.getName());
    env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
    env.put(Context.SECURITY_CREDENTIALS, "password");
    DirContext context = new InitialDirContext(env);
    ModificationItem[] mods =
    {
      new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                           new BasicAttribute("description", "foo"))
    };
    context.modifyAttributes("o=test", mods);
    context.close();
  }
  /**
   * Tests the ability to perform a modify DN operation over the internal LDAP
   * socket.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyDNOperation()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People");
    assertTrue(DirectoryServer.entryExists(DN.decode("ou=People,o=test")));
    assertFalse(DirectoryServer.entryExists(DN.decode("ou=Users,o=test")));
    InternalLDAPSocket socket = new InternalLDAPSocket();
    LDAPReader reader = new LDAPReader(socket);
    LDAPWriter writer = new LDAPWriter(socket);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
                                   3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    writer.writeMessage(message);
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
    ModifyDNRequestProtocolOp modifyDNRequest =
         new ModifyDNRequestProtocolOp(new ASN1OctetString("ou=People,o=test"),
                                       new ASN1OctetString("ou=Users"), true);
    writer.writeMessage(new LDAPMessage(2, modifyDNRequest));
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getModifyDNResponseProtocolOp().getResultCode(),
                 LDAPResultCode.SUCCESS);
    assertFalse(DirectoryServer.entryExists(DN.decode("ou=People,o=test")));
    assertTrue(DirectoryServer.entryExists(DN.decode("ou=Users,o=test")));
    reader.close();
    writer.close();
    socket.close();
  }
  /**
   * Tests the ability to perform a modify DN operation over the internal LDAP
   * socket via JNDI.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyDNOperationThroughJNDI()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People");
    assertTrue(DirectoryServer.entryExists(DN.decode("ou=People,o=test")));
    assertFalse(DirectoryServer.entryExists(DN.decode("ou=Users,o=test")));
    Hashtable<String,String> env = new Hashtable<String,String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put("java.naming.ldap.factory.socket",
            InternalLDAPSocketFactory.class.getName());
    env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
    env.put(Context.SECURITY_CREDENTIALS, "password");
    DirContext context = new InitialDirContext(env);
    context.rename("ou=People,o=test", "ou=Users,o=test");
    assertFalse(DirectoryServer.entryExists(DN.decode("ou=People,o=test")));
    assertTrue(DirectoryServer.entryExists(DN.decode("ou=Users,o=test")));
    context.close();
  }
  /**
   * Tests the ability to perform a search operation over the internal LDAP
   * socket.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSearchOperation()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    InternalLDAPSocket socket = new InternalLDAPSocket();
    LDAPReader reader = new LDAPReader(socket);
    LDAPWriter writer = new LDAPWriter(socket);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
                                   3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    writer.writeMessage(message);
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
    SearchRequestProtocolOp searchRequest =
         new SearchRequestProtocolOp(new ASN1OctetString("o=test"),
                                     SearchScope.BASE_OBJECT,
                                     DereferencePolicy.NEVER_DEREF_ALIASES,
                                     0, 0, false,
                                     LDAPFilter.decode("(objectClass=*)"),
                                     new LinkedHashSet<String>());
    writer.writeMessage(new LDAPMessage(2, searchRequest));
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getSearchResultEntryProtocolOp().getDN(),
                 DN.decode("o=test"));
    message = reader.readMessage();
    assertNotNull(message);
    assertEquals(message.getSearchResultDoneProtocolOp().getResultCode(),
                 LDAPResultCode.SUCCESS);
    reader.close();
    writer.close();
    socket.close();
  }
  /**
   * Tests the ability to perform a searcj operation over the internal LDAP
   * socket via JNDI.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSearchOperationThroughJNDI()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
    Hashtable<String,String> env = new Hashtable<String,String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put("java.naming.ldap.factory.socket",
            InternalLDAPSocketFactory.class.getName());
    env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
    env.put(Context.SECURITY_CREDENTIALS, "password");
    DirContext context = new InitialDirContext(env);
    SearchControls poorlyNamedSearchControls = new SearchControls();
    poorlyNamedSearchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
    NamingEnumeration results = context.search("o=test", "(objectClass=*)",
                                               poorlyNamedSearchControls);
    assertTrue(results.hasMoreElements());
    assertNotNull(results.nextElement());
    assertFalse(results.hasMoreElements());
    context.close();
  }
}