/** {@inheritDoc} */
      @Override
      public ByteString normalizeValue(ByteSequence value) throws DirectoryException {
        StringBuilder buffer = new StringBuilder();
        prepareUnicode(buffer, value, TRIM, CASE_FOLD);

        int bufferLength = buffer.length();
        if (bufferLength == 0) {
          if (value.length() > 0) {
            // This should only happen if the value is composed entirely
            // of spaces. In that case, the normalized value is a single space.
            return SINGLE_SPACE_VALUE;
          } else {
            // The value is empty, so it is already normalized.
            return ByteString.empty();
          }
        }

        // Replace any consecutive spaces with a single space.
        for (int pos = bufferLength - 1; pos > 0; pos--) {
          if (buffer.charAt(pos) == ' ') {
            if (buffer.charAt(pos - 1) == ' ') {
              buffer.delete(pos, pos + 1);
            }
          }
        }

        return ByteString.valueOf(buffer.toString());
      }
  /**
   * Tests whether the Who Am I? extended operation with an internal authenticated connection
   * succeeds with default setting of "ds-cfg-reject-unauthenticated-requests".
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testAuthWAIDefCfg() throws Exception {
    DirectoryServer.setRejectUnauthenticatedRequests(false);

    Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
    LDAPReader reader = new LDAPReader(s);
    LDAPWriter writer = new LDAPWriter(s);

    AtomicInteger nextMessageID = new AtomicInteger(1);
    LDAPAuthenticationHandler authHandler =
        new LDAPAuthenticationHandler(reader, writer, "localhost", nextMessageID);
    authHandler.doSimpleBind(
        3,
        ByteString.valueOf("cn=Directory Manager"),
        ByteString.valueOf("password"),
        new ArrayList<Control>(),
        new ArrayList<Control>());
    ByteString authzID = authHandler.requestAuthorizationIdentity();
    assertNotNull(authzID);

    LDAPMessage unbindMessage =
        new LDAPMessage(nextMessageID.getAndIncrement(), new UnbindRequestProtocolOp());
    writer.writeMessage(unbindMessage);
    s.close();
  }
  /**
   * Reads an LDAP message from the associated input stream.
   *
   * @return The LDAP message read from the associated input stream, or <CODE>null</CODE> if the end
   *     of the stream has been reached.
   * @throws IOException If a problem occurs while attempting to read from the input stream.
   * @throws ASN1Exception If a problem occurs while attempting to decode the data read as an ASN.1
   *     sequence.
   * @throws LDAPException If a problem occurs while attempting to decode the LDAP message.
   */
  public LDAPMessage readMessage() throws IOException, ASN1Exception, LDAPException {
    debugInputStream.setRecordingEnabled(debugEnabled());

    if (!asn1Reader.hasNextElement()) {
      // EOF was reached...
      return null;
    }

    LDAPMessage message = org.opends.server.protocols.ldap.LDAPReader.readMessage(asn1Reader);

    if (debugInputStream.isRecordingEnabled()) {
      ByteString bytesRead = debugInputStream.getRecordedBytes();
      debugInputStream.clearRecordedBytes();

      StringBuilder builder = new StringBuilder();
      builder.append("bytes read from wire(len=");
      builder.append(bytesRead.length());
      builder.append("):");
      builder.append(ServerConstants.EOL);
      bytesRead.toHexPlusAscii(builder, 4);

      TRACER.debugProtocolElement(DebugLogLevel.VERBOSE, builder.toString());
      TRACER.debugProtocolElement(DebugLogLevel.VERBOSE, message.toString());
    }

    return message;
  }
  /**
   * Tests the {@code getRawAuthorizationDN} and {@code setRawAuthorizationDN} methods.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testGetAndSetRawAuthorizationDN() throws Exception {
    ProxiedAuthV1Control proxyControl = new ProxiedAuthV1Control(ByteString.valueOf(""));
    assertEquals(proxyControl.getRawAuthorizationDN(), ByteString.valueOf(""));

    proxyControl = new ProxiedAuthV1Control(ByteString.valueOf("uid=test,o=test"));
    assertEquals(proxyControl.getRawAuthorizationDN(), ByteString.valueOf("uid=test,o=test"));
  }
  /**
   * Tests whether an authenticated BIND request will be allowed with the default configuration
   * settings for "ds-cfg-reject-unauthenticated-requests" .
   */
  @Test()
  public void testAuthBindDefCfg() {
    DirectoryServer.setRejectUnauthenticatedRequests(false);

    InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
    ByteString user = ByteString.valueOf("cn=Directory Manager");
    ByteString password = ByteString.valueOf("password");
    BindOperation bindOperation = conn.processSimpleBind(user, password);
    assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the fourth constructor for the request control with a non-null context ID.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor4NonNullContextID() throws Exception {
    VLVRequestControl vlvRequest =
        new VLVRequestControl(true, 0, 9, ByteString.valueOf("a"), ByteString.valueOf("foo"));

    assertEquals(vlvRequest.isCritical(), true);
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getGreaterThanOrEqualAssertion().toString(), "a");
    assertNotNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(), VLVRequestControl.TYPE_TARGET_GREATERTHANOREQUAL);
    assertNotNull(vlvRequest.toString());
  }
 /**
  * Verifies that the server will reject a CRAM-MD5 bind in which the first message contains SASL
  * credentials (which isn't allowed).
  *
  * @throws Exception If an unexpected problem occurs.
  */
 @Test()
 public void testOutOfSequenceBind() throws Exception {
   InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
   BindOperation bindOperation =
       conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, ByteString.valueOf("invalid"));
   assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
 }
  /**
   * Tests the {@code decodeControl} method when the control value is not a sequence.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test(expectedExceptions = {DirectoryException.class})
  public void testDecodeControlValueNotSequence() throws Exception {
    LDAPControl c =
        new LDAPControl(OID_PROXIED_AUTH_V1, true, ByteString.valueOf("uid=test,o=test"));

    ProxiedAuthV1Control.DECODER.decode(c.isCritical(), c.getValue());
  }
  /**
   * Tests the ASN.1 encoding for the response control.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testASN1ValueEncoding() throws Exception {
    ByteStringBuilder builder = new ByteStringBuilder();
    ASN1Writer writer = ASN1.getWriter(builder);
    VLVResponseControl vlvResponse =
        new VLVResponseControl(true, 0, 15, 0, ByteString.valueOf("foo"));
    vlvResponse.writeValue(writer);

    ASN1Reader reader = ASN1.getReader(builder.toByteString());
    // Should start as an octet string with a nested sequence
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE);
    reader.readStartSequence();
    // Should be an sequence start
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_SEQUENCE_TYPE);
    reader.readStartSequence();
    // Should be an integer with targetPosition
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_INTEGER_TYPE);
    assertEquals(reader.readInteger(), 0);
    // Should be an integer with contentCount
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_INTEGER_TYPE);
    assertEquals(reader.readInteger(), 15);
    // Should be an enumerated with virtualListViewResult
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_ENUMERATED_TYPE);
    assertEquals(reader.readEnumerated(), 0);
    // Should be an octet string with contextID
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE);
    assertEquals(reader.readOctetStringAsString(), "foo");
  }
  /**
   * Tests whether authenticated and unauthenticated BIND requests will be allowed with the new
   * configuration settings for "ds-cfg-reject-unauthenticated-requests" .
   */
  @Test
  public void testBindNewCfg() {
    try {
      DirectoryServer.setRejectUnauthenticatedRequests(true);

      InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
      ByteString user = ByteString.valueOf("cn=Directory Manager");
      ByteString password = ByteString.valueOf("password");
      // Unauthenticated BIND request.
      BindOperation bindOperation = conn.processSimpleBind(DN.nullDN(), null);
      assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
      // Authenticated BIND request.
      bindOperation = conn.processSimpleBind(user, password);
      assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
    } finally {
      DirectoryServer.setRejectUnauthenticatedRequests(false);
    }
  }
  private Object[] generateValues(String password) throws Exception {
    ByteString bytePassword = ByteString.valueOf(password);
    SaltedMD5PasswordStorageScheme scheme = new SaltedMD5PasswordStorageScheme();

    ConfigEntry configEntry =
        DirectoryServer.getConfigEntry(
            DN.decode("cn=Salted MD5,cn=Password Storage Schemes,cn=config"));

    SaltedMD5PasswordStorageSchemeCfg configuration =
        AdminTestCaseUtils.getConfiguration(
            SaltedMD5PasswordStorageSchemeCfgDefn.getInstance(), configEntry.getEntry());

    scheme.initializePasswordStorageScheme(configuration);

    ByteString encodedAuthPassword = scheme.encodePasswordWithScheme(bytePassword);

    return new Object[] {encodedAuthPassword.toString(), password, true};
  }
  /**
   * Verifies that the server will reject a CRAM-MD5 bind with credentials containing a malformed
   * digest.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testMalformedDigest() throws Exception {
    InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
    BindOperation bindOperation = conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, null);
    assertEquals(bindOperation.getResultCode(), ResultCode.SASL_BIND_IN_PROGRESS);

    ByteString creds = ByteString.valueOf("dn:cn=Directory Manager malformeddigest");
    bindOperation = conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, creds);
    assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
  }
  /**
   * Perform the LDAP EXTENDED operation and send the result back to the client.
   *
   * @param objFactory The object factory for this operation.
   * @param extendedRequest The extended request for this operation.
   * @param controls Any required controls (e.g. for proxy authz).
   * @return The result of the extended operation.
   * @throws IOException If an I/O problem occurs.
   * @throws LDAPException If an error occurs while interacting with an LDAP element.
   * @throws ASN1Exception If an error occurs while interacting with an ASN.1 element.
   */
  public ExtendedResponse doOperation(
      ObjectFactory objFactory,
      ExtendedRequest extendedRequest,
      List<org.opends.server.types.Control> controls)
      throws IOException, LDAPException, ASN1Exception {
    ExtendedResponse extendedResponse = objFactory.createExtendedResponse();
    extendedResponse.setRequestID(extendedRequest.getRequestID());

    String requestName = extendedRequest.getRequestName();
    Object value = extendedRequest.getRequestValue();
    ByteString asnValue = ByteStringUtility.convertValue(value);

    // Create and send the LDAP request to the server.
    ProtocolOp op = new ExtendedRequestProtocolOp(requestName, asnValue);
    LDAPMessage msg = new LDAPMessage(DSMLServlet.nextMessageID(), op, controls);
    connection.getLDAPWriter().writeMessage(msg);

    // Read and decode the LDAP response from the server.
    LDAPMessage responseMessage = connection.getLDAPReader().readMessage();

    ExtendedResponseProtocolOp extendedOp = responseMessage.getExtendedResponseProtocolOp();
    int resultCode = extendedOp.getResultCode();
    Message errorMessage = extendedOp.getErrorMessage();

    // Set the result code and error message for the DSML response.
    extendedResponse.setResponseName(extendedOp.getOID());

    ByteString rawValue = extendedOp.getValue();
    value = null;
    if (rawValue != null) {
      if (responseIsString(requestName)) {
        value = rawValue.toString();
      } else {
        value = rawValue.toByteArray();
      }
    }
    extendedResponse.setResponse(value);
    extendedResponse.setErrorMessage(errorMessage != null ? errorMessage.toString() : null);
    ResultCode code = ResultCodeFactory.create(objFactory, resultCode);
    extendedResponse.setResultCode(code);

    return extendedResponse;
  }
  /**
   * Tests the {@code toString} methods.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testToString() throws Exception {
    // The default toString() calls the version that takes a string builder
    // argument, so we only need to use the default version to cover both cases.
    ProxiedAuthV1Control proxyControl =
        new ProxiedAuthV1Control(ByteString.valueOf("uid=test,o=test"));
    proxyControl.toString();

    proxyControl = new ProxiedAuthV1Control(DN.decode("uid=test,o=test"));
    proxyControl.toString();
  }
  /** {@inheritDoc} */
  @Override()
  public Set<AttributeValue> getValues(Entry entry, VirtualAttributeRule rule) {
    Backend backend = DirectoryServer.getBackend(entry.getDN());

    try {
      ConditionResult ret = backend.hasSubordinates(entry.getDN());
      if (ret != null && ret != ConditionResult.UNDEFINED) {
        AttributeValue value =
            AttributeValues.create(
                ByteString.valueOf(ret.toString()), ByteString.valueOf(ret.toString()));
        return Collections.singleton(value);
      }
    } catch (DirectoryException de) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
    }

    return Collections.emptySet();
  }
  /**
   * Tests the second constructor for the response control with a non-null context ID.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testResponseConstructor2NonNullContextID() throws Exception {
    VLVResponseControl vlvResponse =
        new VLVResponseControl(true, 0, 15, 0, ByteString.valueOf("foo"));

    assertEquals(vlvResponse.isCritical(), true);
    assertEquals(vlvResponse.getTargetPosition(), 0);
    assertEquals(vlvResponse.getContentCount(), 15);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertNotNull(vlvResponse.getContextID());
    assertNotNull(vlvResponse.toString());
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a subset of the entries
   * using an assertion value that is after all values in the list.
   *
   * @throws Exception If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueAfterAll() throws Exception {
    populateDB();

    InternalClientConnection conn = InternalClientConnection.getRootConnection();

    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("sn"));
    requestControls.add(new VLVRequestControl(0, 3, ByteString.valueOf("zz")));

    InternalSearchOperation internalSearch =
        new InternalSearchOperation(
            conn,
            InternalClientConnection.nextOperationID(),
            InternalClientConnection.nextMessageID(),
            requestControls,
            DN.decode("dc=example,dc=com"),
            SearchScope.WHOLE_SUBTREE,
            DereferencePolicy.NEVER_DEREF_ALIASES,
            0,
            0,
            false,
            SearchFilter.createFilterFromString("(objectClass=person)"),
            null,
            null);

    internalSearch.run();

    // It will be successful because the control isn't critical.
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);

    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);

    VLVResponseControl vlvResponse = null;
    for (Control c : responseControls) {
      if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          vlvResponse =
              VLVResponseControl.DECODER.decode(c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          vlvResponse = (VLVResponseControl) c;
        }
      }
    }

    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), LDAPResultCode.SUCCESS);
    assertEquals(vlvResponse.getTargetPosition(), 10);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests the first constructor, which creates an instance of the control using a raw, unprocessed
   * DN.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testConstructor1() throws Exception {
    // Try a DN of "null", which is not valid and will fail on the attempt to
    // create the control
    ProxiedAuthV1Control proxyControl;
    try {
      proxyControl = new ProxiedAuthV1Control((ByteString) null);
      throw new AssertionError(
          "Expected a failure when creating a proxied "
              + "auth V1 control with a null octet string.");
    } catch (Throwable t) {
    }

    // Try an empty DN, which is acceptable.
    proxyControl = new ProxiedAuthV1Control(ByteString.valueOf(""));
    assertTrue(proxyControl.getOID().equals(OID_PROXIED_AUTH_V1));
    assertTrue(proxyControl.isCritical());
    assertTrue(proxyControl.getAuthorizationDN().isNullDN());

    // Try a valid DN, which is acceptable.
    proxyControl = new ProxiedAuthV1Control(ByteString.valueOf("uid=test,o=test"));
    assertTrue(proxyControl.getOID().equals(OID_PROXIED_AUTH_V1));
    assertTrue(proxyControl.isCritical());
    assertEquals(proxyControl.getAuthorizationDN(), DN.decode("uid=test,o=test"));

    // Try an invalid DN, which will be initally accepted but will fail when
    // attempting to get the authorization DN.
    proxyControl = new ProxiedAuthV1Control(ByteString.valueOf("invalid"));
    assertTrue(proxyControl.getOID().equals(OID_PROXIED_AUTH_V1));
    assertTrue(proxyControl.isCritical());
    try {
      proxyControl.getAuthorizationDN();
      throw new AssertionError(
          "Expected a failure when creating a proxied "
              + "auth V1 control with an invalid DN string.");
    } catch (Exception e) {
    }
  }
  /**
   * Tests the {@code decodeControl} method when the control value is a sequence with multiple
   * elements.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test
  public void testDecodeControlValueMultiElementSequence() throws Exception {
    ByteStringBuilder bsb = new ByteStringBuilder();
    ASN1Writer writer = ASN1.getWriter(bsb);
    writer.writeStartSequence();
    writer.writeOctetString("uid=element1,o=test");
    writer.writeOctetString("uid=element2,o=test");
    writer.writeEndSequence();
    LDAPControl c = new LDAPControl(OID_PROXIED_AUTH_V1, true, bsb.toByteString());

    assertEquals(
        ByteString.valueOf("uid=element1,o=test"),
        ProxiedAuthV1Control.DECODER.decode(c.isCritical(), c.getValue()).getRawAuthorizationDN());
  }
  /**
   * Tests the second constructor for the request control with a non-null context ID.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor2NonNullContextID() throws Exception {
    VLVRequestControl vlvRequest =
        new VLVRequestControl(true, 0, 9, 1, 0, ByteString.valueOf("foo"));

    assertEquals(vlvRequest.isCritical(), true);
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getOffset(), 1);
    assertEquals(vlvRequest.getContentCount(), 0);
    assertNotNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(), VLVRequestControl.TYPE_TARGET_BYOFFSET);
    assertNull(vlvRequest.getGreaterThanOrEqualAssertion());
    assertNotNull(vlvRequest.toString());
  }
  /** {@inheritDoc} */
  @Override()
  public boolean passwordIsAcceptable(
      ByteString newPassword,
      Set<ByteString> currentPasswords,
      Operation operation,
      Entry userEntry,
      MessageBuilder invalidReason) {
    // Get a handle to the current configuration and see if we need to count
    // the number of repeated characters in the password.
    RepeatedCharactersPasswordValidatorCfg config = currentConfig;
    int maxRepeats = config.getMaxConsecutiveLength();
    if (maxRepeats <= 0) {
      // We don't need to check anything, so the password will be acceptable.
      return true;
    }

    // Get the password as a string.  If we should use case-insensitive
    // validation, then convert it to use all lowercase characters.
    String passwordString = newPassword.toString();
    if (!config.isCaseSensitiveValidation()) {
      passwordString = passwordString.toLowerCase();
    }

    // Create variables to keep track of the last character we've seen and how
    // many times we have seen it.
    char lastCharacter = '\u0000';
    int consecutiveCount = 0;

    // Iterate through the characters in the password.  If the consecutive
    // count ever gets too high, then fail.
    for (int i = 0; i < passwordString.length(); i++) {
      char currentCharacter = passwordString.charAt(i);
      if (currentCharacter == lastCharacter) {
        consecutiveCount++;
        if (consecutiveCount > maxRepeats) {
          Message message = ERR_REPEATEDCHARS_VALIDATOR_TOO_MANY_CONSECUTIVE.get(maxRepeats);
          invalidReason.append(message);
          return false;
        }
      } else {
        lastCharacter = currentCharacter;
        consecutiveCount = 1;
      }
    }

    return true;
  }
  /**
   * Decodes the contents of the provided byte sequence as an ldap syntax definition according to
   * the rules of this syntax. Note that the provided byte sequence value does not need to be
   * normalized (and in fact, it should not be in order to allow the desired capitalization to be
   * preserved).
   *
   * @param value The byte sequence containing the value to decode (it does not need to be
   *     normalized).
   * @param schema The schema to use to resolve references to other schema elements.
   * @param allowUnknownElements Indicates whether to allow values that are not defined in the
   *     server schema. This should only be true when called by {@code valueIsAcceptable}. Not used
   *     for LDAP Syntaxes
   * @return The decoded ldapsyntax definition.
   * @throws DirectoryException If the provided value cannot be decoded as an ldapsyntax definition.
   */
  public static LDAPSyntaxDescription decodeLDAPSyntax(
      ByteSequence value, Schema schema, boolean allowUnknownElements) throws DirectoryException {
    // Get string representations of the provided value using the provided form.
    String valueStr = value.toString();

    // We'll do this a character at a time.  First, skip over any leading
    // whitespace.
    int pos = 0;
    int length = valueStr.length();
    while ((pos < length) && (valueStr.charAt(pos) == ' ')) {
      pos++;
    }

    if (pos >= length) {
      // This means that the value was empty or contained only whitespace.  That
      // is illegal.

      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_EMPTY_VALUE.get();
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    // The next character must be an open parenthesis.  If it is not, then that
    // is an error.
    char c = valueStr.charAt(pos++);
    if (c != '(') {

      Message message =
          ERR_ATTR_SYNTAX_LDAPSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(
              valueStr, (pos - 1), String.valueOf(c));
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    // Skip over any spaces immediately following the opening parenthesis.
    while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) {
      pos++;
    }

    if (pos >= length) {
      // This means that the end of the value was reached before we could find
      // the OID.  Ths is illegal.
      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    int oidStartPos = pos;
    if (isDigit(c)) {
      // This must be a numeric OID.  In that case, we will accept only digits
      // and periods, but not consecutive periods.
      boolean lastWasPeriod = false;
      while ((pos < length)
          && ((c = valueStr.charAt(pos)) != ' ')
          && (c = valueStr.charAt(pos)) != ')') {
        if (c == '.') {
          if (lastWasPeriod) {
            Message message =
                ERR_ATTR_SYNTAX_LDAPSYNTAX_DOUBLE_PERIOD_IN_NUMERIC_OID.get(valueStr, (pos - 1));
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
          } else {
            lastWasPeriod = true;
          }
        } else if (!isDigit(c)) {
          // This must have been an illegal character.
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_NUMERIC_OID.get(
                  valueStr, String.valueOf(c), (pos - 1));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        } else {
          lastWasPeriod = false;
        }
        pos++;
      }
    } else {
      // This must be a "fake" OID.  In this case, we will only accept
      // alphabetic characters, numeric digits, and the hyphen.
      while ((pos < length)
          && ((c = valueStr.charAt(pos)) != ' ')
          && (c = valueStr.charAt(pos)) != ')') {
        if (isAlpha(c)
            || isDigit(c)
            || (c == '-')
            || ((c == '_') && DirectoryServer.allowAttributeNameExceptions())) {
          // This is fine.  It is an acceptable character.
          pos++;
        } else {
          // This must have been an illegal character.
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(
                  valueStr, String.valueOf(c), (pos - 1));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
      }
    }

    // If we're at the end of the value, then it isn't a valid attribute type
    // description.  Otherwise, parse out the OID.
    String oid;
    if (pos >= length) {
      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    } else {
      oid = toLowerCase(valueStr.substring(oidStartPos, pos));
    }

    // Skip over the space(s) after the OID.
    while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) {
      pos++;
    }

    if (pos >= length) {
      // This means that the end of the value was reached before we could find
      // the OID.  Ths is illegal.
      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    // At this point, we should have a pretty specific syntax that describes
    // what may come next, but some of the components are optional and it would
    // be pretty easy to put something in the wrong order, so we will be very
    // flexible about what we can accept.  Just look at the next token, figure
    // out what it is and how to treat what comes after it, then repeat until
    // we get to the end of the value.  But before we start, set default values
    // for everything else we might need to know.
    String description = null;
    LDAPSyntaxDescriptionSyntax syntax = null;
    HashMap<String, List<String>> extraProperties = new LinkedHashMap<String, List<String>>();
    boolean hasXSyntaxToken = false;

    while (true) {
      StringBuilder tokenNameBuffer = new StringBuilder();
      pos = readTokenName(valueStr, tokenNameBuffer, pos);
      String tokenName = tokenNameBuffer.toString();
      String lowerTokenName = toLowerCase(tokenName);
      if (tokenName.equals(")")) {
        // We must be at the end of the value.  If not, then that's a problem.
        if (pos < length) {
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_UNEXPECTED_CLOSE_PARENTHESIS.get(valueStr, (pos - 1));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }

        break;
      } else if (lowerTokenName.equals("desc")) {
        // This specifies the description for the attribute type.  It is an
        // arbitrary string of characters enclosed in single quotes.
        StringBuilder descriptionBuffer = new StringBuilder();
        pos = readQuotedString(valueStr, descriptionBuffer, pos);
        description = descriptionBuffer.toString();
      } else if (lowerTokenName.equals("x-subst")) {
        if (hasXSyntaxToken) {
          // We've already seen syntax extension. More than 1 is not allowed
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        hasXSyntaxToken = true;
        StringBuilder woidBuffer = new StringBuilder();
        pos = readQuotedString(valueStr, woidBuffer, pos);
        String syntaxOID = toLowerCase(woidBuffer.toString());
        AttributeSyntax<?> subSyntax = schema.getSyntax(syntaxOID);
        if (subSyntax == null) {
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_SYNTAX.get(String.valueOf(oid), syntaxOID);
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
        }
        syntax = new SubstitutionSyntax(subSyntax, valueStr, description, oid);
      } else if (lowerTokenName.equals("x-pattern")) {
        if (hasXSyntaxToken) {
          // We've already seen syntax extension. More than 1 is not allowed
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        hasXSyntaxToken = true;
        StringBuilder regexBuffer = new StringBuilder();
        pos = readQuotedString(valueStr, regexBuffer, pos);
        String regex = regexBuffer.toString().trim();
        if (regex.length() == 0) {
          Message message = WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_NO_PATTERN.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }

        try {
          Pattern pattern = Pattern.compile(regex);
          syntax = new RegexSyntax(pattern, valueStr, description, oid);
        } catch (Exception e) {
          Message message = WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN.get(valueStr, regex);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
      } else if (lowerTokenName.equals("x-enum")) {
        if (hasXSyntaxToken) {
          // We've already seen syntax extension. More than 1 is not allowed
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        hasXSyntaxToken = true;
        LinkedList<String> values = new LinkedList<String>();
        pos = readExtraParameterValues(valueStr, values, pos);

        if (values.isEmpty()) {
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_ENUM_NO_VALUES.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        // Parse all enum values, check for uniqueness
        LinkedList<ByteSequence> entries = new LinkedList<ByteSequence>();
        for (String v : values) {
          ByteString entry = ByteString.valueOf(v);
          if (entries.contains(entry)) {
            Message message =
                WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_DUPLICATE_VALUE.get(
                    valueStr, entry.toString(), pos);
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
          }
          entries.add(entry);
        }
        syntax = new EnumSyntax(entries, valueStr, description, oid);
      } else if (tokenName.matches("X\\-[_\\p{Alpha}-]+")) {
        // This must be a non-standard property and it must be followed by
        // either a single value in single quotes or an open parenthesis
        // followed by one or more values in single quotes separated by spaces
        // followed by a close parenthesis.
        List<String> valueList = new ArrayList<String>();
        pos = readExtraParameterValues(valueStr, valueList, pos);
        extraProperties.put(tokenName, valueList);
      } else {
        // Unknown Token
        Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_EXT.get(valueStr, tokenName, pos);
        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
      }
    }
    if (syntax == null) {
      // Create a plain Syntax. That seems to be required by export/import
      // Schema backend.
      syntax = new LDAPSyntaxDescriptionSyntax();
    }

    CommonSchemaElements.checkSafeProperties(extraProperties);

    // Since we reached here it means everything is OK.
    return new LDAPSyntaxDescription(
        valueStr, syntax,
        description, extraProperties);
  }
  /**
   * Generates an entry for a backup based on the provided DN. The DN must have an RDN component
   * that specifies the backup ID, and the parent DN must have an RDN component that specifies the
   * backup directory.
   *
   * @param entryDN The DN of the backup entry to retrieve.
   * @return The requested backup entry.
   * @throws DirectoryException If the specified backup does not exist or is invalid.
   */
  private Entry getBackupEntry(DN entryDN) throws DirectoryException {
    // First, get the backup ID from the entry DN.
    AttributeType idType = DirectoryServer.getAttributeType(ATTR_BACKUP_ID, true);
    AttributeValue idValue = entryDN.getRDN().getAttributeValue(idType);
    if (idValue == null) {
      Message message = ERR_BACKUP_NO_BACKUP_ID_IN_DN.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }
    String backupID = idValue.getValue().toString();

    // Next, get the backup directory from the parent DN.
    DN parentDN = entryDN.getParentDNInSuffix();
    if (parentDN == null) {
      Message message = ERR_BACKUP_NO_BACKUP_PARENT_DN.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }

    AttributeType t = DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
    AttributeValue v = parentDN.getRDN().getAttributeValue(t);
    if (v == null) {
      Message message = ERR_BACKUP_NO_BACKUP_DIR_IN_DN.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }

    BackupDirectory backupDirectory;
    try {
      backupDirectory = BackupDirectory.readBackupDirectoryDescriptor(v.getValue().toString());
    } catch (ConfigException ce) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, ce);
      }

      Message message =
          ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(String.valueOf(entryDN), ce.getMessageObject());
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      Message message = ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY.get(getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
    }

    BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
    if (backupInfo == null) {
      Message message = ERR_BACKUP_NO_SUCH_BACKUP.get(backupID, backupDirectory.getPath());
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, parentDN, null);
    }

    // Construct the backup entry to return.
    LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<ObjectClass, String>(3);
    ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);

    ObjectClass oc = DirectoryServer.getObjectClass(OC_BACKUP_INFO, true);
    ocMap.put(oc, OC_BACKUP_INFO);

    oc = DirectoryServer.getObjectClass(OC_EXTENSIBLE_OBJECT_LC, true);
    ocMap.put(oc, OC_EXTENSIBLE_OBJECT);

    LinkedHashMap<AttributeType, List<Attribute>> opAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>(0);
    LinkedHashMap<AttributeType, List<Attribute>> userAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>();

    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(idType, idValue));
    userAttrs.put(idType, attrList);

    backupInfo.getBackupDirectory();
    attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, v));
    userAttrs.put(t, attrList);

    Date backupDate = backupInfo.getBackupDate();
    if (backupDate != null) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_DATE, true);
      attrList = new ArrayList<Attribute>(1);
      attrList.add(
          Attributes.create(
              t, AttributeValues.create(t, GeneralizedTimeSyntax.format(backupDate))));
      userAttrs.put(t, attrList);
    }

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_COMPRESSED, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, BooleanSyntax.createBooleanValue(backupInfo.isCompressed())));
    userAttrs.put(t, attrList);

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_ENCRYPTED, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, BooleanSyntax.createBooleanValue(backupInfo.isEncrypted())));
    userAttrs.put(t, attrList);

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_INCREMENTAL, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(
        Attributes.create(t, BooleanSyntax.createBooleanValue(backupInfo.isIncremental())));
    userAttrs.put(t, attrList);

    HashSet<String> dependencies = backupInfo.getDependencies();
    if (dependencies != null && !dependencies.isEmpty()) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_DEPENDENCY, true);
      AttributeBuilder builder = new AttributeBuilder(t);
      for (String s : dependencies) {
        builder.add(AttributeValues.create(t, s));
      }
      attrList = new ArrayList<Attribute>(1);
      attrList.add(builder.toAttribute());
      userAttrs.put(t, attrList);
    }

    byte[] signedHash = backupInfo.getSignedHash();
    if (signedHash != null) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_SIGNED_HASH, true);
      attrList = new ArrayList<Attribute>(1);
      attrList.add(Attributes.create(t, AttributeValues.create(t, ByteString.wrap(signedHash))));
      userAttrs.put(t, attrList);
    }

    byte[] unsignedHash = backupInfo.getUnsignedHash();
    if (unsignedHash != null) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_UNSIGNED_HASH, true);
      attrList = new ArrayList<Attribute>(1);
      attrList.add(Attributes.create(t, AttributeValues.create(t, ByteString.wrap(unsignedHash))));
      userAttrs.put(t, attrList);
    }

    HashMap<String, String> properties = backupInfo.getBackupProperties();
    if (properties != null && !properties.isEmpty()) {
      for (Map.Entry<String, String> e : properties.entrySet()) {
        t = DirectoryServer.getAttributeType(toLowerCase(e.getKey()), true);
        attrList = new ArrayList<Attribute>(1);
        attrList.add(Attributes.create(t, AttributeValues.create(t, e.getValue())));
        userAttrs.put(t, attrList);
      }
    }

    Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
    e.processVirtualAttributes();
    return e;
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a subset of the entries
   * using an assertion value before any actual value in the list.
   *
   * @throws Exception If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueBeforeAll() throws Exception {
    populateDB();

    InternalClientConnection conn = InternalClientConnection.getRootConnection();

    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3, ByteString.valueOf("a")));

    InternalSearchOperation internalSearch =
        new InternalSearchOperation(
            conn,
            InternalClientConnection.nextOperationID(),
            InternalClientConnection.nextMessageID(),
            requestControls,
            DN.decode("dc=example,dc=com"),
            SearchScope.WHOLE_SUBTREE,
            DereferencePolicy.NEVER_DEREF_ALIASES,
            0,
            0,
            false,
            SearchFilter.createFilterFromString("(objectClass=person)"),
            null,
            null);

    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);

    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN); // Aaccf
    expectedDNOrder.add(aaronZimmermanDN); // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN); // Albert, higher entry ID

    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries()) {
      returnedDNOrder.add(e.getDN());
    }

    assertEquals(returnedDNOrder, expectedDNOrder);

    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);

    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl vlvResponse = null;
    for (Control c : responseControls) {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          sortResponse =
              ServerSideSortResponseControl.DECODER.decode(
                  c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          sortResponse = (ServerSideSortResponseControl) c;
        }
      } else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          vlvResponse =
              VLVResponseControl.DECODER.decode(c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          vlvResponse = (VLVResponseControl) c;
        }
      } else {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }

    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);

    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 1);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
 /** {@inheritDoc} */
 public int compare(byte[] arg0, byte[] arg1) {
   return compareValues(ByteString.wrap(arg0), ByteString.wrap(arg1));
 }