From 88f1a8b11a45b44bedb89fb80ff97004e45f4bf3 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 06 Aug 2026 08:00:53 +0000
Subject: [PATCH] Reduce per-bind allocations on the bind hot path (#672)

---
 opendj-server-legacy/src/main/java/org/opends/server/core/PasswordPolicyState.java |   74 ++++++++++++++++++++++++++++++++-----
 opendj-server-legacy/src/main/java/org/opends/server/core/BindOperationBasis.java  |   13 +++++-
 2 files changed, 74 insertions(+), 13 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/BindOperationBasis.java b/opendj-server-legacy/src/main/java/org/opends/server/core/BindOperationBasis.java
index 095c997..e467221 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/BindOperationBasis.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/BindOperationBasis.java
@@ -53,6 +53,15 @@
 {
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
+  /**
+   * The cancel request sent to the other operations in progress when a bind
+   * starts. CancelRequest and LocalizableMessage are both immutable (the
+   * message is rendered per locale when used), so a single shared instance
+   * avoids building both objects on every bind.
+   */
+  private static final CancelRequest CANCEL_ALL_BY_BIND_REQUEST =
+      new CancelRequest(true, INFO_CANCELED_BY_BIND_REQUEST.get());
+
   /** The credentials used for SASL authentication. */
   private ByteString saslCredentials;
 
@@ -484,9 +493,7 @@
     clientConnection.setUnauthenticated();
 
     // Abandon any operations that may be in progress for the client.
-    LocalizableMessage cancelReason = INFO_CANCELED_BY_BIND_REQUEST.get();
-    CancelRequest cancelRequest = new CancelRequest(true, cancelReason);
-    clientConnection.cancelAllOperationsExcept(cancelRequest, getMessageID());
+    clientConnection.cancelAllOperationsExcept(CANCEL_ALL_BY_BIND_REQUEST, getMessageID());
 
     // This flag is set to true as soon as a workflow has been executed.
     boolean workflowExecuted = false;
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/PasswordPolicyState.java b/opendj-server-legacy/src/main/java/org/opends/server/core/PasswordPolicyState.java
index cdad80d..443b1ff 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/PasswordPolicyState.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/PasswordPolicyState.java
@@ -1963,27 +1963,50 @@
       return false;
     }
 
+    boolean isAuthPassword = passwordPolicy.isAuthPasswordSyntax();
     for (Attribute a : attrList)
     {
       for (ByteString v : a)
       {
         try
         {
-          String[] pwComponents = getPwComponents(v);
-          String schemeName = pwComponents[0];
-          PasswordStorageScheme<?> scheme = getPasswordStorageScheme(schemeName);
-          if (scheme == null)
+          String schemeName;
+          boolean matches;
+          if (isAuthPassword)
           {
-            if (logger.isTraceEnabled())
+            String[] pwComponents = getPwComponents(v);
+            schemeName = pwComponents[0];
+            PasswordStorageScheme<?> scheme = getPasswordStorageScheme(schemeName);
+            if (scheme == null)
             {
-              logger.trace("User entry %s contains a password with scheme %s that is not defined in the server.",
-                                  userDNString, schemeName);
+              traceUndefinedScheme(schemeName);
+              continue;
             }
-
-            continue;
+            matches = passwordMatches(password, pwComponents, scheme);
+          }
+          else
+          {
+            // This method runs for every bind: parse the {scheme} prefix at
+            // the byte level instead of materializing the whole stored value
+            // as a String only to convert its payload back to bytes.
+            int closePos = userPasswordSchemeEnd(v);
+            if (closePos < 0)
+            {
+              // Malformed value: raise the same errors as the String decoder.
+              getPwComponents(v);
+              continue;
+            }
+            schemeName = toLowerCase(v.subSequence(1, closePos).toString());
+            PasswordStorageScheme<?> scheme = DirectoryServer.getPasswordStorageScheme(schemeName);
+            if (scheme == null)
+            {
+              traceUndefinedScheme(schemeName);
+              continue;
+            }
+            matches = scheme.passwordMatches(password, v.subSequence(closePos + 1, v.length()));
           }
 
-          if (passwordMatches(password, pwComponents, scheme))
+          if (matches)
           {
             if (logger.isTraceEnabled())
             {
@@ -2024,6 +2047,37 @@
   }
 
   /**
+   * Returns the index of the closing brace of the {scheme} prefix of the
+   * provided user password value, or -1 if the value is not well-formed
+   * (also when the scheme name is empty, mirroring
+   * {@link UserPasswordSyntax#decodeUserPassword(String)}).
+   */
+  private static int userPasswordSchemeEnd(ByteString v)
+  {
+    if (v.length() == 0 || v.byteAt(0) != '{')
+    {
+      return -1;
+    }
+    for (int i = 1; i < v.length(); i++)
+    {
+      if (v.byteAt(i) == '}')
+      {
+        return i > 1 ? i : -1;
+      }
+    }
+    return -1;
+  }
+
+  private void traceUndefinedScheme(String schemeName)
+  {
+    if (logger.isTraceEnabled())
+    {
+      logger.trace("User entry %s contains a password with scheme %s that is not defined in the server.",
+          userDNString, schemeName);
+    }
+  }
+
+  /**
    * Indicates whether the provided password value is pre-encoded.
    *
    * @param  passwordValue  The value for which to make the determination.

--
Gitblit v1.10.0