Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
* Added "disableBackoffMechanism()" init config method to disable the backoff behavior.
* Added support for server requested SDK log gathering. When the server asks for it, the SDK uploads its own internal log lines, even if logging is disabled. While consent is required, a line is uploaded only while the consent of the feature it belongs to is given.
* Added support for the SDK connection test. When the server asks for it, the SDK checks that it can reach every endpoint it depends on and reports the results.
* Added "setMaxKeyLength(int)" init config method to limit the length of event keys, view names, segmentation keys and custom user property keys. Longer keys are truncated. There is no limit by default, and the key length limit of the SDK behavior settings overrides it.

* Mitigated an issue where saving the SDK storage while remote config values were changing could empty the storage file.
* Mitigated an issue where a storage file cut short by a stopped process made initialization fail.
Expand Down
25 changes: 25 additions & 0 deletions sdk-java/src/main/java/ly/count/sdk/java/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@
protected int networkReadTimeout = 30;

/**
* How long to wait between requests in milliseconds. Used to decrease CPU & I/O load on the device.

Check warning on line 107 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'
*/
protected int networkRequestCooldown = 1000;

/**
* How long to wait between Device ID change & push token requests, in milliseconds.

Check warning on line 112 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'

Check warning on line 112 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'
* Required by Countly server, don't change unless you know what you're doing!
*/
protected int networkImportantRequestCooldown = 5000;
Expand All @@ -126,7 +126,7 @@
* This ensures pinning continues to work after certificate prolongation.
* Certificates ({@link #certificatePins}) on the other hand have specific expiry date.
* In case you chose this way of pinning, you MUST ensure that ALL installs of your app
* have both certificates (old & new) until expiry date.

Check warning on line 129 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'
*/
protected Set<String> publicKeyPins = null;

Expand All @@ -142,7 +142,7 @@
* This ensures pinning continues to work after certificate prolongation.
* Certificates on the other hand have specific expiry date.
* In case you chose this way of pinning, you MUST ensure that ALL installs of your app
* have both certificates (old & new) until expiry date.

Check warning on line 145 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'
*/
protected Set<String> certificatePins = null;

Expand All @@ -168,7 +168,7 @@
/**
* {@link CrashProcessor}-implementing class which is instantiated when application
* crashes or crash is reported programmatically using {@link Session#addCrashReport(Throwable, boolean, String, Map, String...)}.
* Crash processor helps you to add custom data in event of a crash: custom crash segments & crash logs.

Check warning on line 171 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'
*/
protected String crashProcessorClass = null;

Expand Down Expand Up @@ -226,6 +226,11 @@
*/
protected int maxBreadcrumbCount = 100;

/**
* Maximum length of the keys the SDK records, no limit by default.
*/
protected int maxKeyLength = Integer.MAX_VALUE;

/**
* Enable automatic crash reporting for unhandled exceptions.
*/
Expand Down Expand Up @@ -322,6 +327,26 @@
return this;
}

/**
* Set the maximum length of the keys the SDK records: event keys, view names, segmentation keys,
* custom user property keys, and the keys of custom user property modifiers. Longer keys are
* truncated to this length. There is no limit by default. The key length limit of the SDK behavior
* settings overrides this value.
*
* @param maxKeyLength the maximum key length, at least 1
* @return {@code this} instance for method chaining
*/
public Config setMaxKeyLength(int maxKeyLength) {
if (maxKeyLength < 1) {
if (configLog != null) {
configLog.e("[Config] maxKeyLength cannot be less than 1");
}
} else {
this.maxKeyLength = maxKeyLength;
}
return this;
}

/**
* Disable automatic crash reporting for unhandled exceptions.
*
Expand Down Expand Up @@ -688,7 +713,7 @@
}

/**
* Disable update requests completely. Only begin & end requests will be sent + some special

Check warning on line 716 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'
* cases if applicable like User Profile change or Push token updated.
*
* @return {@code this} instance for method chaining
Expand Down Expand Up @@ -786,7 +811,7 @@

/**
* How long to wait between requests in seconds.
* Used to decrease CPU & I/O load on the device in case of batch requests.

Check warning on line 814 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'
*
* @param milliseconds cooldown period in seconds
* @return {@code this} instance for method chaining
Expand All @@ -804,7 +829,7 @@

/**
* Set read timeout in seconds for HTTP requests SDK sends to Countly server. Defaults to 30.
* Used to decrease CPU & I/O load on the device in case of batch requests.

Check warning on line 832 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'
*
* @param milliseconds read timeout in milliseconds
* @return {@code this} instance for method chaining
Expand Down Expand Up @@ -832,7 +857,7 @@
* This ensures pinning continues to work after certificate prolongation.
* Certificates ({@link #certificatePins}) on the other hand have specific expiry date.
* In case you chose this way of pinning, you MUST ensure that ALL installs of your app
* have both certificates (old & new) until expiry date.

Check warning on line 860 in sdk-java/src/main/java/ly/count/sdk/java/Config.java

View workflow job for this annotation

GitHub Actions / Release checks (26.8.0)

invalid input: '&'
*
* NOTE: when {@link #serverURL} doesn't have {@code "https://"} public key pinning doesn't work
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,15 @@ int getMaxBreadcrumbCount() {
return maxBreadcrumbCount;
}

/**
* The key length limit the developer set.
*
* @return the limit, {@link Integer#MAX_VALUE} when none is set
*/
int getMaxKeyLength() {
return maxKeyLength;
}

String[] getLocationParams() {
return new String[] { country, city, location, ip };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ private void seedFromDeveloperConfig(@Nonnull InternalConfig config) {
currentVEventQueueSizeThreshold = config.getEventsBufferSize();
currentVSessionUpdateInterval = config.getSendUpdateEachSeconds();
currentVMaxBreadcrumbCount = config.getMaxBreadcrumbCount();
currentVMaxKeyLength = config.getMaxKeyLength();
currentVZoneTimerInterval = config.content.zoneTimerInterval;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public void fluentChain_acceptsEverySaneValueAndReadsItBack() {
.setEventQueueSizeToSend(9)
.setRequestQueueMaxSize(321)
.setMaxBreadcrumbCount(12)
.setMaxKeyLength(64)
.setNetworkConnectTimeout(11)
.setNetworkReadTimeout(12)
.setNetworkRequestCooldown(1300)
Expand Down Expand Up @@ -92,6 +93,7 @@ public void fluentChain_acceptsEverySaneValueAndReadsItBack() {
Assert.assertEquals("test-platform", SDKCore.instance.config.getSdkPlatform());
// These two are only readable off the internal config, which is what the modules see.
Assert.assertEquals(12, SDKCore.instance.config.getMaxBreadcrumbCount());
Assert.assertEquals(64, SDKCore.instance.config.getMaxKeyLength());
Assert.assertFalse(SDKCore.instance.config.isUnhandledCrashReportingEnabled());
}

Expand Down Expand Up @@ -144,6 +146,15 @@ public void outOfRangeValues_areRefusedAndLeaveTheGoodValueInPlace() {
config.setEventQueueSizeToSend(-5);
Assert.assertEquals("a negative event queue size must be refused", 23, config.getEventsBufferSize());

// key length accepts 1 and up, and sets no limit until it is called
Assert.assertEquals(Integer.MAX_VALUE, new InternalConfig(config).getMaxKeyLength());
config.setMaxKeyLength(40);
for (int bad : new int[] { 0, -1, Integer.MIN_VALUE }) {
config.setMaxKeyLength(bad);
Assert.assertEquals("key length must reject " + bad, 40, new InternalConfig(config).getMaxKeyLength());
}
Assert.assertEquals(1, new InternalConfig(config.setMaxKeyLength(1)).getMaxKeyLength());

// The boundary values themselves are valid.
Assert.assertEquals(300, config.setNetworkConnectTimeout(300).getNetworkConnectionTimeout());
Assert.assertEquals(1, config.setNetworkReadTimeout(1).getNetworkReadTimeout());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,77 @@ public void aResponseChangingTheLimits_appliesFromTheNextCall() {
Assert.assertEquals(truncateLines(stackTraceOf(exception), 12), crashes.get(0).getString("_error"));
}

/**
* The key length limit of the developer configuration cuts keys while the settings set none, the
* settings override it while they carry {@code lkl}, and it applies again once they stop.
* <p>
* Verifies that a developer limit of 5 cuts the event key and the segmentation keys while the
* values stay whole, that a response with a key length limit of 8 cuts to 8 instead, and that a
* response leaving the limit out brings back the developer limit of 5.
*/
@Test
public void theDeveloperKeyLengthLimit_cutsKeys_untilTheSettingsOverrideIt() {
server.respondWith(new ServerConfigBuilder().networking(false));
init(configWithWarnings().enableFeatures(Config.Feature.Events).setEventQueueSizeToSend(100).setMaxKeyLength(5));
Assert.assertEquals(5, provider().getMaxKeyLength());
Map<String, Object> segmentation = TestUtils.map("segment_key", "segment_value", "second", 2);

Countly.instance().events().recordEvent("event_key", segmentation);
push(new ServerConfigBuilder().keyLengthLimit(8));
Assert.assertEquals(8, provider().getMaxKeyLength());
Countly.instance().events().recordEvent("event_key", segmentation);
push(new ServerConfigBuilder().networking(false));
Assert.assertEquals(5, provider().getMaxKeyLength());
Countly.instance().events().recordEvent("event_key", segmentation);

List<EventImpl> queued = TestUtils.getCurrentEQ();
Assert.assertEquals(keysOf(queued).toString(), 3, queued.size());
assertEvent(queued.get(0), "event", TestUtils.map("segme", "segment_value", "secon", 2));
assertEvent(queued.get(1), "event_ke", TestUtils.map("segment_", "segment_value", "second", 2));
assertEvent(queued.get(2), "event", TestUtils.map("segme", "segment_value", "secon", 2));
}

/**
* Without any SDK behavior settings, the key length limit of the developer configuration cuts the
* keys of events, views and custom user properties.
* <p>
* Verifies with a limit of 4 the event key and segmentation keys, the view name, and the custom
* user property keys of a set and of a modification, while the keys the SDK adds to a view stay
* whole.
*/
@Test
public void withoutSettings_theDeveloperKeyLengthLimitCutsKeys() {
Countly.instance().init(TestUtils.getBaseConfig().setLogListener(this::keepWarning)
.enableFeatures(Config.Feature.Events, Config.Feature.Views, Config.Feature.UserProfiles)
.setEventQueueSizeToSend(100).setMaxKeyLength(4));

Countly.instance().events().recordEvent("event_key", TestUtils.map("segment_key", "segment_value"));
Countly.instance().views().startView("view_name");
List<EventImpl> queued = TestUtils.getCurrentEQ();
Assert.assertEquals(keysOf(queued).toString(), 2, queued.size());
assertEvent(queued.get(0), "even", TestUtils.map("segm", "segment_value"));
Assert.assertEquals("view", queued.get(1).segmentation.get(ModuleViews.KEY_NAME));
Assert.assertEquals("1", queued.get(1).segmentation.get(ModuleViews.KEY_VISIT));

Countly.instance().userProfile().setProperty("custom_key", "custom_value");
Countly.instance().userProfile().push("list_key", "item");
Countly.instance().userProfile().save();
TestUtils.letTheClockCatchUp();

JSONObject details = null;
for (Map<String, String> request : TestUtils.getCurrentRQ()) {
if (request.containsKey("user_details")) {
details = new JSONObject(request.get("user_details"));
}
}
Assert.assertNotNull(details);
JSONObject custom = details.getJSONObject(ModuleUserProfile.CUSTOM_KEY);
Assert.assertEquals("custom_value", custom.getString("cust"));
Assert.assertEquals("item", custom.getJSONObject("list").getString("$push"));
Assert.assertEquals(2, custom.length());
Assert.assertTrue(warningsContaining("key length limit of [4]") > 0);
}

/**
* With a live response that sets no limit, nothing any feature sends is cut, however long or wide.
* <p>
Expand Down
Loading