diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleAPMTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleAPMTests.java index b45e73eb..96e0aca4 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleAPMTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleAPMTests.java @@ -301,8 +301,8 @@ public void internalLimits_cancelTrace_keyLength() { } /** - * Test that tracing network keys are not affected by key length truncation - * Validate that the truncated version of the key is not present because it is not truncated + * Test that tracing network keys are affected by key length truncation + * Validate that the truncated version of the key is present because it is truncated */ @Test public void internalLimits_recordNetworkTrace_keyLength() throws JSONException { @@ -314,11 +314,11 @@ public void internalLimits_recordNetworkTrace_keyLength() throws JSONException { mCountly.apm().recordNetworkTrace(key, 234, 123, 456, 7654, 8765); Assert.assertFalse(mCountly.moduleAPM.networkTraces.containsKey(key)); // because it is sent to the request queue + Assert.assertFalse(mCountly.moduleAPM.networkTraces.containsKey("a_tra")); // because it is sent to the request queue Assert.assertFalse(mCountly.moduleAPM.codeTraces.containsKey(key)); - // also validate that the truncated version of the key is not present because it is not truncated - Assert.assertFalse(mCountly.moduleAPM.codeTraces.containsKey(UtilsInternalLimits.truncateKeyLength(key, 5, new ModuleLog(), "tag"))); - validateNetworkRequest(0, key, 8765 - 7654, 234, 123, 456); + Assert.assertFalse(mCountly.moduleAPM.codeTraces.containsKey("a_tra")); + validateNetworkRequest(0, "a_tra", 8765 - 7654, 234, 123, 456); } /** @@ -336,7 +336,7 @@ public void internalLimits_startNetworkTrace_keyLength() throws JSONException { mCountly.apm().startNetworkRequest(key, "ID"); mCountly.apm().endNetworkRequest(key, "ID", 200, 123, 456); - validateNetworkRequest(0, key, -1, 200, 123, 456); + validateNetworkRequest(0, "a_tra", -1, 200, 123, 456); } private void validateNetworkRequest(int rqIdx, String key, long duration, int responseCode, int requestPayloadSize, int responsePayloadSize) throws JSONException { diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleUserProfileTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleUserProfileTests.java index d2a5b656..a042ae2c 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleUserProfileTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleUserProfileTests.java @@ -534,7 +534,7 @@ public void internalLimit_setProperties() throws JSONException { /** * Given max value size truncates the values of the: * - Custom user property values - * - user property values + * - user property values except picture * Validate all values are truncated to the max value size that is 2 * And validate non-String values are not clipped * @@ -576,7 +576,7 @@ public void internalLimit_setProperties_maxValueSize() throws JSONException { ModuleUserProfile.ORG_KEY, "or", ModuleUserProfile.USERNAME_KEY, "us", ModuleUserProfile.NAME_KEY, "na", - ModuleUserProfile.PICTURE_KEY, "pi" + ModuleUserProfile.PICTURE_KEY, "picture" ), TestUtils.map( "custom1", "va", // because in user profiles, all values are stored as strings "custom2", "23", @@ -588,6 +588,67 @@ public void internalLimit_setProperties_maxValueSize() throws JSONException { ); } + /** + * Given max value size for pictures 4096 truncates the value of the picture + * and is not affected by the general max value size + * + * @throws JSONException if JSON parsing fails + */ + @Test + public void internalLimit_setProperties_maxValueSizePicture() throws JSONException { + Countly mCountly = Countly.sharedInstance(); + CountlyConfig config = TestUtils.createBaseConfig(); + config.sdkInternalLimits.setMaxValueSize(2); + mCountly.init(config); + + String picture = TestUtils.generateRandomString(6000); + + Countly.sharedInstance().userProfile().setProperties(TestUtils.map(ModuleUserProfile.PICTURE_KEY, picture)); + Countly.sharedInstance().userProfile().save(); + + validateUserProfileRequest(TestUtils.map(ModuleUserProfile.PICTURE_KEY, picture.substring(0, 4096)), TestUtils.map() + ); + } + + /** + * Given max segmentation values will truncate custom user properties to the correct length + * + * @throws JSONException if JSON parsing fails + */ + @Test + public void internalLimit_setProperties_maxSegmentationValues() throws JSONException { + Countly mCountly = Countly.sharedInstance(); + CountlyConfig config = TestUtils.createBaseConfig(); + config.sdkInternalLimits.setMaxSegmentationValues(2); + mCountly.init(config); + + Countly.sharedInstance().userProfile().setProperties(TestUtils.map("a", "b", "c", "d", "f", 5, "level", 45, "age", 101)); + Countly.sharedInstance().userProfile().save(); + + validateUserProfileRequest(TestUtils.map(), TestUtils.map("f", "5", "age", "101")); + } + + /** + * Given max segmentation values won't truncate custom mods + * + * @throws JSONException if JSON parsing fails + */ + @Test + public void internalLimit_customMods_maxSegmentationValues() throws JSONException { + Countly mCountly = Countly.sharedInstance(); + CountlyConfig config = TestUtils.createBaseConfig(); + config.sdkInternalLimits.setMaxSegmentationValues(2); + mCountly.init(config); + + Countly.sharedInstance().userProfile().incrementBy("inc", 1); + Countly.sharedInstance().userProfile().multiply("mul", 2_456_789); + Countly.sharedInstance().userProfile().push("rem", "ORIELY"); + Countly.sharedInstance().userProfile().push("rem", "HUH"); + Countly.sharedInstance().userProfile().save(); + + validateUserProfileRequest(TestUtils.map(), TestUtils.map("mul", json("$mul", 2_456_789), "rem", json("$push", new String[] { "ORIELY", "HUH" }), "inc", json("$inc", 1))); + } + /** * Given max value size truncates the values of the: * - Custom user property values @@ -645,6 +706,14 @@ public void setUserProperties_null() throws JSONException { validateUserProfileRequest(new HashMap<>(), new HashMap<>()); } + private JSONObject json(Object... args) { + return new JSONObject(TestUtils.map(args)); + } + + private void assertJsonsEquals(Object expected, Object actual) { + Assert.assertEquals(expected.toString(), actual.toString()); + } + private void validateUserProfileRequest(Map predefined, Map custom) throws JSONException { Map[] RQ = TestUtils.getCurrentRQ(); Assert.assertEquals(1, RQ.length); @@ -658,7 +727,11 @@ private void validateUserProfileRequest(Map predefined, Map entry : custom.entrySet()) { - Assert.assertEquals(entry.getValue(), customData.get(entry.getKey())); + if (entry.getValue() instanceof JSONObject) { + assertJsonsEquals(entry.getValue(), customData.get(entry.getKey())); + } else { + Assert.assertEquals(entry.getValue(), customData.get(entry.getKey())); + } } } } diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java b/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java index d1390993..8a430df0 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java @@ -568,4 +568,11 @@ protected static void put(JSONObject json, String key, Object value) { } catch (JSONException ignored) { } } + + protected static String generateRandomString(int length) { + byte[] array = new byte[length]; + new Random().nextBytes(array); + + return new String(array, java.nio.charset.StandardCharsets.UTF_8); + } } diff --git a/sdk/src/main/java/ly/count/android/sdk/ConfigSdkInternalLimits.java b/sdk/src/main/java/ly/count/android/sdk/ConfigSdkInternalLimits.java index c24ebf95..713517c7 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ConfigSdkInternalLimits.java +++ b/sdk/src/main/java/ly/count/android/sdk/ConfigSdkInternalLimits.java @@ -4,6 +4,7 @@ public class ConfigSdkInternalLimits { //SDK internal limits protected Integer maxKeyLength; protected Integer maxValueSize; + protected int maxValueSizePicture = 4096; protected Integer maxSegmentationValues; protected Integer maxBreadcrumbCount; protected Integer maxStackTraceLinesPerThread; diff --git a/sdk/src/main/java/ly/count/android/sdk/ModuleAPM.java b/sdk/src/main/java/ly/count/android/sdk/ModuleAPM.java index b4b81ad9..3bafa844 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleAPM.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleAPM.java @@ -324,6 +324,7 @@ void recordNetworkRequestInternal(String networkTraceKey, int responseCode, int } //validate trace key + networkTraceKey = UtilsInternalLimits.truncateKeyLength(networkTraceKey, _cly.config_.sdkInternalLimits.maxKeyLength, L, "[ModuleAPM] recordNetworkRequestInternal"); networkTraceKey = validateAndModifyTraceKey(networkTraceKey); Long responseTimeMs = endTimestamp - startTimestamp; diff --git a/sdk/src/main/java/ly/count/android/sdk/ModuleUserProfile.java b/sdk/src/main/java/ly/count/android/sdk/ModuleUserProfile.java index 8a8ae5e8..94f2f525 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleUserProfile.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleUserProfile.java @@ -155,6 +155,7 @@ protected JSONObject toJSON() { JSONObject ob; if (custom != null) { + UtilsInternalLimits.truncateSegmentationValues(custom, _cly.config_.sdkInternalLimits.maxSegmentationValues, "[ModuleUserProfile] toJSON", _cly.L); ob = new JSONObject(custom); } else { ob = new JSONObject(); @@ -235,6 +236,7 @@ void modifyCustomData(String key, Object value, String mod) { if (customMods == null) { customMods = new HashMap<>(); } + JSONObject ob; if (!mod.equals("$pull") && !mod.equals("$push") && !mod.equals("$addToSet")) { ob = new JSONObject(); @@ -282,8 +284,12 @@ void setPropertiesInternal(@NonNull Map data) { boolean isNamed = false; // limit to the picture path is applied when request is being made in the ConnectionProcessor - if (value instanceof String && !key.equals(PICTURE_PATH_KEY)) { - value = UtilsInternalLimits.truncateValueSize(value.toString(), _cly.config_.sdkInternalLimits.maxValueSize, _cly.L, "[ModuleUserProfile] setPropertiesInternal"); + if (value instanceof String) { + if (key.equals(PICTURE_PATH_KEY) || key.equals(PICTURE_KEY)) { + value = UtilsInternalLimits.truncateValueSize(value.toString(), _cly.config_.sdkInternalLimits.maxValueSizePicture, _cly.L, "[ModuleUserProfile] setPropertiesInternal"); + } else { + value = UtilsInternalLimits.truncateValueSize(value.toString(), _cly.config_.sdkInternalLimits.maxValueSize, _cly.L, "[ModuleUserProfile] setPropertiesInternal"); + } } for (String namedField : namedFields) {