From 74f569b99e62949d2fd17abf36c03b9eba292d01 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray <57103426+arifBurakDemiray@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:52:49 +0000 Subject: [PATCH] [Java] safe id generator (#213) * feat: id generator * feat: validate test case * feat: better validation * feat: scenario utils test --- .../count/sdk/java/internal/IdGenerator.java | 7 ++++ .../sdk/java/internal/InternalConfig.java | 2 ++ .../ly/count/sdk/java/internal/SDKCore.java | 9 +++++ .../ly/count/sdk/java/internal/TimeUtils.java | 2 +- .../sdk/java/internal/ScenarioUtilsTests.java | 35 +++++++++++++++++++ .../ly/count/sdk/java/internal/TestUtils.java | 30 ++++++++++++++++ .../count/sdk/java/internal/UtilsTests.java | 25 ------------- 7 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 sdk-java/src/main/java/ly/count/sdk/java/internal/IdGenerator.java create mode 100644 sdk-java/src/test/java/ly/count/sdk/java/internal/ScenarioUtilsTests.java diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/IdGenerator.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/IdGenerator.java new file mode 100644 index 00000000..05b9fa0e --- /dev/null +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/IdGenerator.java @@ -0,0 +1,7 @@ +package ly.count.sdk.java.internal; + +import javax.annotation.Nonnull; + +public interface IdGenerator { + @Nonnull String generateId(); +} diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java index 976f7b7c..2add2b0e 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java @@ -29,6 +29,8 @@ public class InternalConfig extends Config { ImmediateRequestGenerator immediateRequestGenerator = null; public SDKCore sdk; public StorageProvider storageProvider; + protected IdGenerator viewIdGenerator; + protected IdGenerator eventIdGenerator; /** * Shouldn't be used! diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java index 31b0fc68..ba5a361c 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java @@ -482,6 +482,15 @@ public void init(final InternalConfig givenConfig) { setDeviceIdFromStorageIfExist(config); requestQueueMemory = new ArrayDeque<>(config.getRequestQueueMaxSize()); + + if (config.viewIdGenerator == null) { + config.viewIdGenerator = Utils::safeRandomVal; + } + + if (config.eventIdGenerator == null) { + config.eventIdGenerator = Utils::safeRandomVal; + } + // ModuleSessions is always enabled, even without consent int consents = config.getFeatures1() | CoreFeature.Sessions.getIndex(); // build modules diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/TimeUtils.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/TimeUtils.java index 4f0614d0..842fd7ae 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/TimeUtils.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/TimeUtils.java @@ -33,7 +33,7 @@ public static Instant getCurrentInstant() { return getCurrentInstant(timestampMs()); } - private static Instant getCurrentInstant(long timestamp) { + protected static Instant getCurrentInstant(long timestamp) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timestamp); return new Instant(timestamp, diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ScenarioUtilsTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ScenarioUtilsTests.java new file mode 100644 index 00000000..f857f4a9 --- /dev/null +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ScenarioUtilsTests.java @@ -0,0 +1,35 @@ +package ly.count.sdk.java.internal; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ScenarioUtilsTests { + + /** + * "safeRandomVal" + * ### 001_validatingIDGenerator + *

+ * testing the ID generator function that is used for events and views + *

+ * Generate 2 values + *

+ * they should be different. + * They should be 21 chars long. + * They should contain on base64 characters. first 8 one is base64 string and last 13 one is timestamp + * + * @throws NumberFormatException for parsing part 2 + */ + @Test + public void _001_validatingIDGenerator() throws NumberFormatException { + String val1 = Utils.safeRandomVal(); + String val2 = Utils.safeRandomVal(); + + Assert.assertNotEquals(val2, val1); + + TestUtils.validateSafeRandomVal(val1); + TestUtils.validateSafeRandomVal(val2); + } +} diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 12cc69f7..fb37fbe8 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -13,6 +13,8 @@ import java.util.Map; import java.util.Scanner; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Stream; import ly.count.sdk.java.Config; import ly.count.sdk.java.Countly; @@ -586,4 +588,32 @@ protected static Map map(Map gonnaAddMap, Object } return map; } + + /** + * Validates a random generated safe value, + * Value length should be 21 + * Value should contain a timestamp at the end + * Value should be base64 encoded and first 8 should be it + * + * @param val + */ + static void validateSafeRandomVal(String val) { + Assert.assertEquals(21, val.length()); + + Pattern base64Pattern = Pattern.compile("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$"); + + String timestampStr = val.substring(val.length() - 13); + String base64Str = val.substring(0, val.length() - 13); + + Matcher matcher = base64Pattern.matcher(base64Str); + if (matcher.matches()) { + TimeUtils.Instant instant = TimeUtils.getCurrentInstant(Long.parseLong(timestampStr)); + Assert.assertTrue(instant.dow >= 0 && instant.dow < 7); + Assert.assertTrue(instant.hour >= 0 && instant.hour < 24); + Assert.assertTrue(instant.timestamp > 0); + Assert.assertTrue(instant.tz >= -720 && instant.tz <= 840); + } else { + Assert.fail("No match for " + val); + } + } } diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/UtilsTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/UtilsTests.java index 66459073..a3c83858 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/UtilsTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/UtilsTests.java @@ -13,8 +13,6 @@ import java.util.EnumSet; import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import ly.count.sdk.java.Config; import org.junit.Assert; import org.junit.Before; @@ -561,27 +559,4 @@ public void readStream() { Assert.assertNull(Utils.readStream(null, logger)); Assert.assertEquals(value, new String(Utils.readStream(new ByteArrayInputStream(value.getBytes()), logger))); } - - /** - * "safeRandomVal" - * An random value is generated and validated by the base64 regex, and consist of 2 parts - * Should return the string value generated by the algorithm and matches with the regex - * - * @throws NumberFormatException for parsing part 2 - */ - @Test - public void safeRandomVal() throws NumberFormatException { - String val = Utils.safeRandomVal(); - Pattern base64Pattern = Pattern.compile("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$"); - - String timestampStr = val.substring(val.length() - 13); - String base64Str = val.substring(0, val.length() - 13); - - Matcher matcher = base64Pattern.matcher(base64Str); - if (matcher.matches()) { - Assert.assertTrue(Long.parseLong(timestampStr) > 0); - } else { - Assert.fail("No match for " + val); - } - } }