diff --git a/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/EvaluatorBucketing.java b/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/EvaluatorBucketing.java index 2a96663..5b55e24 100644 --- a/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/EvaluatorBucketing.java +++ b/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/EvaluatorBucketing.java @@ -47,30 +47,42 @@ static float computeBucketValue( } } - String idHash = getBucketableStringValue(contextValue); - if (idHash == null) { + StringBuilder keyBuilder = new StringBuilder(); + if (seed != null) { + keyBuilder.append(seed.intValue()); + } else { + keyBuilder.append(flagOrSegmentKey).append('.').append(salt); + } + keyBuilder.append('.'); + if (!getBucketableStringValue(keyBuilder, contextValue)) { return 0; } - String prefix; - if (seed != null) { - prefix = seed.toString(); - } else { - prefix = flagOrSegmentKey + "." + salt; + // turn the first 15 hex digits of this into a long + byte[] hash = DigestUtils.sha1(keyBuilder.toString()); + long longVal = 0; + for (int i = 0; i < 7; i++) { + longVal <<= 8; + longVal |= (hash[i] & 0xff); } - String hash = DigestUtils.sha1Hex(prefix + "." + idHash).substring(0, 15); - long longVal = Long.parseLong(hash, 16); + longVal <<= 4; + longVal |= ((hash[7] >> 4) & 0xf); return (float) longVal / LONG_SCALE; } - private static String getBucketableStringValue(LDValue userValue) { + private static boolean getBucketableStringValue(StringBuilder keyBuilder, LDValue userValue) { switch (userValue.getType()) { case STRING: - return userValue.stringValue(); + keyBuilder.append(userValue.stringValue()); + return true; case NUMBER: - return userValue.isInt() ? String.valueOf(userValue.intValue()) : null; + if (userValue.isInt()) { + keyBuilder.append(userValue.intValue()); + return true; + } + return false; default: - return null; + return false; } } } diff --git a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/EvaluatorBucketingTest.java b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/EvaluatorBucketingTest.java index 6e0a164..72e82c4 100644 --- a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/EvaluatorBucketingTest.java +++ b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/EvaluatorBucketingTest.java @@ -8,6 +8,8 @@ import com.launchdarkly.sdk.server.DataModel.RolloutKind; import com.launchdarkly.sdk.server.DataModel.WeightedVariation; +import org.apache.commons.codec.digest.DigestUtils; + import org.junit.Test; import java.util.Arrays; @@ -132,6 +134,18 @@ public void cannotBucketByBooleanAttribute() { assertEquals(0f, result, Float.MIN_VALUE); } + @Test + public void optimizedHashText() { + LDContext context = LDContext.builder("key") + .set("stringattr", "33333") + .build(); + float result = computeBucketValue(false, noSeed, context, null, "key", AttributeRef.fromLiteral("stringattr"), "salt"); + String hash = DigestUtils.sha1Hex("key.salt.33333").substring(0, 15); + long longVal = Long.parseLong(hash, 16); + float expectedResult = longVal / (float) 0xFFFFFFFFFFFFFFFL; + assertEquals(expectedResult, result, Float.MIN_VALUE); + } + private static void assertVariationIndexFromRollout( int expectedVariation, Rollout rollout,