diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/UsernameGenerator.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/UsernameGenerator.java index a941bff25..222d00954 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/UsernameGenerator.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/UsernameGenerator.java @@ -70,7 +70,7 @@ public class UsernameGenerator { // check discriminators of the current width up to attemptsPerWidth times for (int i = 0; i < attemptsPerWidth; i++) { int discriminator = ThreadLocalRandom.current().nextInt(rangeMin, rangeMax); - String username = UsernameGenerator.fromParts(nickname, discriminator); + String username = fromParts(nickname, discriminator); attempts++; if (usernameAvailableFun.test(username)) { DISCRIMINATOR_ATTEMPT_COUNTER.record(attempts); @@ -101,11 +101,12 @@ public class UsernameGenerator { /** * Generate a username from a nickname and discriminator */ - public static String fromParts(final String nickname, final int discriminator) throws IllegalArgumentException { + public String fromParts(final String nickname, final int discriminator) throws IllegalArgumentException { if (!isValidNickname(nickname)) { throw new IllegalArgumentException("Invalid nickname " + nickname); } - return nickname + SEPARATOR + discriminator; + // zero pad discriminators less than the discriminator initial width + return String.format("%s#%0" + initialWidth + "d", nickname, discriminator); } public static boolean isValidNickname(final String nickname) { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java index 8bbf72725..136bc16b5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java @@ -64,6 +64,7 @@ class AccountsManagerUsernameIntegrationTest { private AccountsManager accountsManager; private Accounts accounts; + private UsernameGenerator usernameGenerator; @BeforeEach void setup() throws InterruptedException { @@ -141,6 +142,7 @@ class AccountsManagerUsernameIntegrationTest { when(experimentEnrollmentManager.isEnrolled(any(UUID.class), eq(AccountsManager.USERNAME_EXPERIMENT_NAME))) .thenReturn(true); + usernameGenerator = new UsernameGenerator(1, 2, 10); accountsManager = new AccountsManager( accounts, phoneNumberIdentifiers, @@ -155,7 +157,7 @@ class AccountsManagerUsernameIntegrationTest { mock(SecureStorageClient.class), mock(SecureBackupClient.class), mock(ClientPresenceManager.class), - new UsernameGenerator(1, 2, 10), + usernameGenerator, experimentEnrollmentManager, mock(Clock.class)); } @@ -197,7 +199,7 @@ class AccountsManagerUsernameIntegrationTest { .tableName(USERNAMES_TABLE_NAME) .item(Map.of( Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(UUID.randomUUID()), - Accounts.ATTR_USERNAME, AttributeValues.fromString(UsernameGenerator.fromParts("n00bkiller", i)))) + Accounts.ATTR_USERNAME, AttributeValues.fromString(usernameGenerator.fromParts("n00bkiller", i)))) .build()); } assertThrows(UsernameNotAvailableException.class, () -> accountsManager.setUsername(account, "n00bkiller", null)); @@ -213,7 +215,7 @@ class AccountsManagerUsernameIntegrationTest { .tableName(USERNAMES_TABLE_NAME) .item(Map.of( Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(UUID.randomUUID()), - Accounts.ATTR_USERNAME, AttributeValues.fromString(UsernameGenerator.fromParts("n00bkiller", i)))) + Accounts.ATTR_USERNAME, AttributeValues.fromString(usernameGenerator.fromParts("n00bkiller", i)))) .build()); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/UsernameGeneratorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/UsernameGeneratorTest.java index c19d40b4d..8ad69d348 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/UsernameGeneratorTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/UsernameGeneratorTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; import org.whispersystems.textsecuregcm.storage.UsernameNotAvailableException; import org.whispersystems.textsecuregcm.util.UsernameGenerator; @@ -63,6 +62,15 @@ public class UsernameGeneratorTest { ); } + @Test + public void zeroPadDiscriminators() { + final UsernameGenerator generator = new UsernameGenerator(4, 5, 1); + assertThat(generator.fromParts("test", 1)).isEqualTo("test#0001"); + assertThat(generator.fromParts("test", 123)).isEqualTo("test#0123"); + assertThat(generator.fromParts("test", 9999)).isEqualTo("test#9999"); + assertThat(generator.fromParts("test", 99999)).isEqualTo("test#99999"); + } + @Test public void expectedWidth() throws UsernameNotAvailableException { String username = new UsernameGenerator(1, 6, 1).generateAvailableUsername("test", t -> true);