From 8999024cbef72f4371afcbc033894696a2885cc3 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Thu, 20 Jun 2024 13:43:23 -0700 Subject: [PATCH 1/3] SDKS-3229 DefaultStorageClient Stress Test --- .../auth/DefaultStorageClientStressTest.java | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/DefaultStorageClientStressTest.java diff --git a/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/DefaultStorageClientStressTest.java b/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/DefaultStorageClientStressTest.java new file mode 100644 index 00000000..371facbd --- /dev/null +++ b/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/DefaultStorageClientStressTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024 ForgeRock. All rights reserved. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +package org.forgerock.android.auth; + +import static org.junit.Assert.assertNotNull; + +import android.content.Context; +import android.util.Log; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.forgerock.android.auth.exception.AccountLockException; +import org.forgerock.android.auth.exception.MechanismCreationException; +import org.forgerock.android.auth.exception.OathMechanismException; +import org.forgerock.android.auth.util.Base32String; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.Random; +import java.util.UUID; + +@RunWith(AndroidJUnit4.class) +public class DefaultStorageClientStressTest { + + private final Context context = ApplicationProvider.getApplicationContext(); + private final Random rand = new Random(); + + private static final String[] ALGORITHMS = {"SHA1", "SHA256", "SHA512"}; + private static final String ISSUER = "ISSUER_"; + private static final String ACCOUNT_NAME = "ACCOUNT_NAME_"; + private static final String TAG = DefaultStorageClientStressTest.class.getSimpleName(); + + private DefaultStorageClient defaultStorage; + + @Before + public void setUp() { + defaultStorage = new DefaultStorageClient(context); + OathCodeGenerator.getInstance(defaultStorage); + } + + @After + public void cleanUp() { + defaultStorage.removeAll(); + } + + @Test + public void testStoreOneHundredAccounts() + throws OathMechanismException, AccountLockException, MechanismCreationException { + long startTime = System.currentTimeMillis(); + int numberOfAccounts = 100; + + // Create accounts + for (int i = 0; i < numberOfAccounts; i++) { + String issuer = ISSUER + i; + String accountName = ACCOUNT_NAME + i; + OathMechanism.TokenType tokenType = getRandomTokenType(); + int period = getRandomPeriod(); + int digits = getRandomDigits(); + int counter = getRandomCounter(); + String algorithm = getRandomAlgorithm(); + String mechanismUid = getRandomMechanismUid(); + String secret = getRandomSharedSecret(); + + Account account = MockModelBuilder.createAccount(issuer, accountName); + Mechanism mechanism = MockModelBuilder.createOath(mechanismUid, issuer, accountName, + tokenType, algorithm, secret, digits, counter, period); + defaultStorage.setAccount(account); + defaultStorage.setMechanism(mechanism); + } + + // Retrieve and verify accounts + for (int i = 0; i < numberOfAccounts; i++) { + String issuer = ISSUER + i; + String accountName = ACCOUNT_NAME + i; + // Verify account + Account account = defaultStorage.getAccount(issuer + "-" + accountName); + assertNotNull(account); + // Verify mechanism + List mechanismList = defaultStorage.getMechanismsForAccount(account); + OathMechanism mechanism = (OathMechanism) mechanismList.get(0); + assertNotNull(mechanism); + // Verify token code + OathTokenCode tokenCode = mechanism.getOathTokenCode(); + assertNotNull(tokenCode); + } + + Log.d(TAG, "Stored and retrieved " + numberOfAccounts + + " accounts in " + (System.currentTimeMillis() - startTime)/1000 + " seconds"); + } + + private String getRandomSharedSecret() { + int sharedSecretByteLength = Math.max(8, (int) Math.ceil(32 / 2d)); + byte[] sharedSecret = new byte[sharedSecretByteLength]; + rand.nextBytes(sharedSecret); + return Base32String.encode(sharedSecret); + } + + private int getRandomDigits() { + return rand.nextBoolean() ? 6 : 8; + } + + private int getRandomCounter() { + return rand.nextInt(200); + } + + private int getRandomPeriod() { + return rand.nextBoolean() ? 30 : 60; + } + + private String getRandomAlgorithm() { + return ALGORITHMS[rand.nextInt(ALGORITHMS.length)]; + } + + private String getRandomMechanismUid() { + return UUID.randomUUID().toString(); + } + + private OathMechanism.TokenType getRandomTokenType() { + return rand.nextBoolean() ? OathMechanism.TokenType.HOTP : OathMechanism.TokenType.TOTP; + } + +} \ No newline at end of file From 7650ca438b23904f56c88e19eca92e96f9641fd6 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Thu, 20 Jun 2024 14:00:55 -0700 Subject: [PATCH 2/3] Fixed tests in ServerConfigTest... --- .../java/org/forgerock/android/auth/ServerConfigTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forgerock-auth/src/test/java/org/forgerock/android/auth/ServerConfigTest.java b/forgerock-auth/src/test/java/org/forgerock/android/auth/ServerConfigTest.java index d38acf9e..5fef818e 100644 --- a/forgerock-auth/src/test/java/org/forgerock/android/auth/ServerConfigTest.java +++ b/forgerock-auth/src/test/java/org/forgerock/android/auth/ServerConfigTest.java @@ -80,7 +80,7 @@ public void testSha256Pinning() throws InterruptedException { ServerConfig serverConfig = ServerConfig.builder() .context(context) .url("https://api.ipify.org") - .pin("Pf/hyG+ywUC8g3d1abF9kQn83lY6NwJUTUnqe03UMIY=") + .pin("2lFvaIHpsTcbb5uqa08S2k6wzLKscXXx1k1hKoX9R1Q=") .build(); OkHttpClient client = OkHttpClientProvider.getInstance().lookup(serverConfig); @@ -122,7 +122,7 @@ public void testMultiplePinning() throws InterruptedException { ServerConfig serverConfig = ServerConfig.builder() .context(context) .url("https://api.ipify.org") - .pin("Pf/hyG+ywUC8g3d1abF9kQn83lY6NwJUTUnqe03UMIY=") + .pin("2lFvaIHpsTcbb5uqa08S2k6wzLKscXXx1k1hKoX9R1Q=") .pin("invalid") .build(); @@ -214,7 +214,7 @@ public void testBuildStepWithCustomPin() throws InterruptedException { .context(context) .url("https://api.ipify.org") .buildStep(builder -> builder.certificatePinner( - new CertificatePinner.Builder().add("api.ipify.org", "sha1/KmdlCymwI4zbla1kcWu2fBEwKA8=" ).build())) + new CertificatePinner.Builder().add("api.ipify.org", "sha1/FAx66BsuUMrmrBnZ8F0GKxBZxLs=" ).build())) .build(); OkHttpClient client = OkHttpClientProvider.getInstance().lookup(serverConfig); From 12d082f762a7a73f339f9ef9ce938319baa9cfa2 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Mon, 24 Jun 2024 12:56:40 -0700 Subject: [PATCH 3/3] Make the test multi-threaded --- .../auth/DefaultStorageClientStressTest.java | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/DefaultStorageClientStressTest.java b/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/DefaultStorageClientStressTest.java index 371facbd..653379b8 100644 --- a/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/DefaultStorageClientStressTest.java +++ b/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/DefaultStorageClientStressTest.java @@ -27,6 +27,9 @@ import java.util.List; import java.util.Random; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @RunWith(AndroidJUnit4.class) public class DefaultStorageClientStressTest { @@ -53,15 +56,36 @@ public void cleanUp() { } @Test - public void testStoreOneHundredAccounts() - throws OathMechanismException, AccountLockException, MechanismCreationException { + public void testStoreOneHundredAccounts() throws InterruptedException { + int numberOfAccounts = 50; + int numberOfThreads = 4; + ExecutorService service = Executors.newFixedThreadPool(numberOfThreads); + CountDownLatch latch = new CountDownLatch(numberOfThreads); + + for (int i = 0; i < numberOfThreads; i++) { + int threadIndex = i; + service.execute(() -> { + try { + storeAndRetrieveAccounts(threadIndex, numberOfAccounts); + } catch (Exception e) { + throw new RuntimeException(e); + } + latch.countDown(); + }); + } + + latch.await(); + service.shutdown(); + } + + private void storeAndRetrieveAccounts(int threadIndex,int numberOfAccounts) + throws MechanismCreationException, OathMechanismException, AccountLockException { long startTime = System.currentTimeMillis(); - int numberOfAccounts = 100; // Create accounts for (int i = 0; i < numberOfAccounts; i++) { - String issuer = ISSUER + i; - String accountName = ACCOUNT_NAME + i; + String issuer = ISSUER + threadIndex + "_" + i; + String accountName = ACCOUNT_NAME + threadIndex + "_" + i; OathMechanism.TokenType tokenType = getRandomTokenType(); int period = getRandomPeriod(); int digits = getRandomDigits(); @@ -79,8 +103,8 @@ public void testStoreOneHundredAccounts() // Retrieve and verify accounts for (int i = 0; i < numberOfAccounts; i++) { - String issuer = ISSUER + i; - String accountName = ACCOUNT_NAME + i; + String issuer = ISSUER + threadIndex + "_" + i; + String accountName = ACCOUNT_NAME + threadIndex + "_" + i; // Verify account Account account = defaultStorage.getAccount(issuer + "-" + accountName); assertNotNull(account); @@ -128,4 +152,4 @@ private OathMechanism.TokenType getRandomTokenType() { return rand.nextBoolean() ? OathMechanism.TokenType.HOTP : OathMechanism.TokenType.TOTP; } -} \ No newline at end of file +}