Skip to content

Commit

Permalink
[Sdk 358] SDK Limits, App quit null pointer exception, (#113)
Browse files Browse the repository at this point in the history
* Max key lenght

* New sdk limits added.

* chnages,

* Substring logic updated

* bug fixes,
tests udated

* new unit tests added.

* Change log
SDK Version
Test lib issue fixed.

* revert changes.

* PR Changes

* PR Changes

* changes

* User custom detail key and value trim test added

* SDK Limits new Changes

* PR Changes

* Unit tests added

* PR Changes
  • Loading branch information
ZahidZafar authored Aug 4, 2021
1 parent eb81802 commit b974131
Show file tree
Hide file tree
Showing 14 changed files with 685 additions and 114 deletions.
2 changes: 1 addition & 1 deletion Assets/Plugins/CountlySDK/Helpers/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Plugins.CountlySDK.Helpers
{
internal class Constants
{
public const string SdkVersion = "20.11.4";
public const string SdkVersion = "20.11.5";

#if UNITY_EDITOR
public const string SdkName = "csharp-unity-editor";
Expand Down
27 changes: 26 additions & 1 deletion Assets/Plugins/CountlySDK/Models/CountlyConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,31 @@ public class CountlyConfiguration
/// </summary>
public int SessionDuration = 60;

/// <summary>
/// Maximum size of all string keys
/// </summary>
public int MaxKeyLength = 128;

/// <summary>
/// Maximum size of all values in our key-value pairs
/// </summary>
public int MaxValueSize = 256;

/// <summary>
/// Max amount of custom (dev provided) segmentation in one event
/// </summary>
public int MaxSegmentationValues = 30;

/// <summary>
///Limits how many stack trace lines would be recorded per thread
/// </summary>
public int MaxStackTraceLinesPerThread = 30;

/// <summary>
///Limits how many characters are allowed per stack trace line
/// </summary>
public int MaxStackTraceLineLength = 200;

/// <summary>
/// Set threshold value for the number of events that can be stored locally.
/// </summary>
Expand Down Expand Up @@ -224,4 +249,4 @@ public void AddNotificationListener(INotificationListener listener)
NotificationEventListeners.Add(listener);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class CountlyUserDetailsModel

[JsonProperty("custom")]
//dots (.) and dollar signs ($) in key names will be stripped out.
internal Dictionary<string, object> Custom { get; set; }
internal IDictionary<string, object> Custom { get; set; }

/// <summary>
/// Initializes a new instance of User Model with the specified params
Expand Down
97 changes: 97 additions & 0 deletions Assets/Plugins/CountlySDK/Services/AbstractBaseService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

using System;
using System.Collections;
using System.Collections.Generic;
using Plugins.CountlySDK.Enums;
using Plugins.CountlySDK.Models;
Expand All @@ -23,6 +24,102 @@ protected AbstractBaseService(CountlyConfiguration configuration, CountlyLogHelp
_consentService = consentService;
}

protected IDictionary<string, object> RemoveSegmentInvalidDataTypes(IDictionary<string, object> segments)
{

if (segments == null || segments.Count == 0) {
return segments;
}

string moduleName = GetType().Name;
int i = 0;
List<string> toRemove = new List<string>();
foreach (KeyValuePair<string, object> item in segments) {
if (++i > _configuration.MaxSegmentationValues) {
toRemove.Add(item.Key);
continue;
}
Type type = item.Value?.GetType();
bool isValidDataType = item.Value != null
&& (type == typeof(int)
|| type == typeof(bool)
|| type == typeof(float)
|| type == typeof(double)
|| type == typeof(string));


if (!isValidDataType) {
toRemove.Add(item.Key);
Log.Warning("[" + moduleName + "] RemoveSegmentInvalidDataTypes: In segmentation Data type '" + type + "' of item '" + item.Key + "' isn't valid.");
}
}

foreach (string k in toRemove) {
segments.Remove(k);
}

return segments;
}

protected string TrimKey(string k)
{
if (k.Length > _configuration.MaxKeyLength) {
Log.Warning("[" + GetType().Name + "] TrimKey : Max allowed key length is " + _configuration.MaxKeyLength + ". "+ k + " will be truncated.");
k = k.Substring(0, _configuration.MaxKeyLength);
}

return k;
}

protected string[] TrimValues(string[] values)
{
for (int i = 0; i < values.Length; ++i) {
if (values[i].Length > _configuration.MaxValueSize) {
Log.Warning("[" + GetType().Name + "] TrimKey : Max allowed value length is " + _configuration.MaxKeyLength + ". " + values[i] + " will be truncated.");
values[i] = values[i].Substring(0, _configuration.MaxValueSize);
}
}


return values;
}

protected string TrimValue(string fieldName, string v)
{
if (v.Length > _configuration.MaxValueSize) {
Log.Warning("[" + GetType().Name + "] TrimValue : Max allowed '" + fieldName + "' length is " + _configuration.MaxValueSize + ". " + v + " will be truncated.");
v = v.Substring(0, _configuration.MaxValueSize);
}

return v;
}

protected IDictionary<string, object> FixSegmentKeysAndValues(IDictionary<string, object> segments)
{
if (segments == null || segments.Count == 0) {
return segments;
}

IDictionary<string, object> segmentation = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> item in segments) {
string k = item.Key;
object v = item.Value;

if (k == null || v == null) {
continue;
}

k = TrimKey(k);

if (v.GetType() == typeof(string)) {
v = TrimValue(k, (string)v);
}

segmentation.Add(k, v);
}

return segmentation;
}
internal virtual void OnInitializationCompleted() { }
internal virtual void DeviceIdChanged(string deviceId, bool merged) { }
internal virtual void ConsentChanged(List<Consents> updatedConsents, bool newConsentValue) { }
Expand Down
49 changes: 42 additions & 7 deletions Assets/Plugins/CountlySDK/Services/CrashReportsCountlyService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Plugins.CountlySDK.Enums;
Expand All @@ -22,6 +23,38 @@ internal CrashReportsCountlyService(CountlyConfiguration configuration, CountlyL
_requestCountlyHelper = requestCountlyHelper;
}

#region Helper Methods
private string ManipulateStackTrace(string stackTrace)
{
string result = null;
if (!string.IsNullOrEmpty(stackTrace)) {
string[] lines = stackTrace.Split('\n');

int limit = lines.Length;

if (limit > _configuration.MaxStackTraceLinesPerThread) {
limit = _configuration.MaxStackTraceLinesPerThread;
}

for (int i = 0; i < limit; ++i) {
string line = lines[i];

if (line.Length > _configuration.MaxStackTraceLineLength) {
line = line.Substring(0, _configuration.MaxStackTraceLineLength);
}

if (i + 1 != limit) {
line += '\n';
}

result += line;
}
}

return result;
}
#endregion


/// <summary>
/// Called when there is an exception
Expand All @@ -42,11 +75,12 @@ public async void LogCallback(string message, string stackTrace, LogType type)
Log.Warning("[CrashReportsCountlyService] LogCallback : The parameter 'message' can't be null or empty");
return;
}
CountlyExceptionDetailModel model = ExceptionDetailModel(message, stackTrace, false, null);

CountlyExceptionDetailModel model = ExceptionDetailModel(message, ManipulateStackTrace(stackTrace), false, null);

if (_configuration.EnableAutomaticCrashReporting
&& (type == LogType.Error || type == LogType.Exception)) {
_=SendCrashReportInternal(model);
_ = SendCrashReportInternal(model);
}
}
}
Expand Down Expand Up @@ -75,8 +109,11 @@ public async Task SendCrashReportAsync(string message, string stackTrace, LogTyp
return;
}

CountlyExceptionDetailModel model = ExceptionDetailModel(message, stackTrace, nonfatal, segments);
_=SendCrashReportInternal(model);
IDictionary<string, object> segmentation = RemoveSegmentInvalidDataTypes(segments);
segmentation = FixSegmentKeysAndValues(segments);

CountlyExceptionDetailModel model = ExceptionDetailModel(message, ManipulateStackTrace(stackTrace), nonfatal, segmentation);
_ = SendCrashReportInternal(model);
}

}
Expand All @@ -100,8 +137,6 @@ internal async Task SendCrashReportInternal(CountlyExceptionDetailModel model)

/// <summary>
/// Adds string value to a list which is later sent over as logs whenever a cash is reported by system.
/// The length of a breadcrumb is limited to 1000 characters. Only first 1000 characters will be accepted in case the length is more
/// than 1000 characters.
/// </summary>
/// <param name="value">a bread crumb for the crash report</param>
public void AddBreadcrumbs(string value)
Expand All @@ -116,7 +151,7 @@ public void AddBreadcrumbs(string value)
return;
}

string validBreadcrumb = value.Length > 1000 ? value.Substring(0, 1000) : value;
string validBreadcrumb = value.Length > _configuration.MaxValueSize ? value.Substring(0, _configuration.MaxValueSize) : value;

if (_crashBreadcrumbs.Count == _configuration.TotalBreadcrumbsAllowed) {
_crashBreadcrumbs.Dequeue();
Expand Down
56 changes: 14 additions & 42 deletions Assets/Plugins/CountlySDK/Services/EventCountlyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ internal void AddEventsToRequestQueue()
/// <returns></returns>
internal async Task RecordEventAsync(CountlyEventModel @event)
{

Log.Debug("[EventCountlyService] RecordEventAsync : " + @event.ToString());

if (_configuration.EnableTestMode) {
Expand Down Expand Up @@ -163,29 +162,15 @@ public async Task RecordEventAsync(string key, IDictionary<string, object> segme
return;
}

if (segmentation != null) {
List<string> toRemove = new List<string>();

foreach (KeyValuePair<string, object> item in segmentation) {
bool isValidDataType = item.Value != null
&& (item.Value.GetType() == typeof(int)
|| item.Value.GetType() == typeof(bool)
|| item.Value.GetType() == typeof(float)
|| item.Value.GetType() == typeof(double)
|| item.Value.GetType() == typeof(string));

if (!isValidDataType) {
toRemove.Add(item.Key);
Log.Warning("[EventCountlyService] ReportCustomEventAsync : In segmentation Data type '" + (item.Value?.GetType()) + "' of item '" + item.Key + "' isn't valid.");
}
}

foreach (string k in toRemove) {
segmentation.Remove(k);
}
if (key.Length > _configuration.MaxKeyLength) {
Log.Warning("[EventCountlyService] RecordEventAsync : Max allowed key length is " + _configuration.MaxKeyLength);
key = key.Substring(0, _configuration.MaxKeyLength);
}

CountlyEventModel @event = new CountlyEventModel(key, segmentation, count, sum, duration);
IDictionary<string, object> segments = RemoveSegmentInvalidDataTypes(segmentation);
segments = FixSegmentKeysAndValues(segments);

CountlyEventModel @event = new CountlyEventModel(key, segments, count, sum, duration);

_ = RecordEventAsync(@event);
}
Expand Down Expand Up @@ -219,29 +204,16 @@ public async Task ReportCustomEventAsync(string key,
return;
}

if (key.Length > _configuration.MaxKeyLength) {
Log.Warning("[EventCountlyService] ReportCustomEventAsync : Max allowed key length is " + _configuration.MaxKeyLength);
key = key.Substring(0, _configuration.MaxKeyLength);
}

if (segmentation != null) {
List<string> toRemove = new List<string>();

foreach (KeyValuePair<string, object> item in segmentation) {
bool isValidDataType = item.Value.GetType() == typeof(int)
|| item.Value.GetType() == typeof(bool)
|| item.Value.GetType() == typeof(float)
|| item.Value.GetType() == typeof(double)
|| item.Value.GetType() == typeof(string);

if (!isValidDataType) {
toRemove.Add(item.Key);
Log.Warning("[EventCountlyService] ReportCustomEventAsync : In segmentation Data type '" + (item.Value?.GetType()) + "' of item '" + item.Key + "'isn't valid.");
}
}

foreach (string k in toRemove) {
segmentation.Remove(k);
}
}
IDictionary<string, object> segments = RemoveSegmentInvalidDataTypes(segmentation);
segments = FixSegmentKeysAndValues(segments);

CountlyEventModel @event = new CountlyEventModel(key, segmentation, count, sum, duration);
CountlyEventModel @event = new CountlyEventModel(key, segments, count, sum, duration);

_ = RecordEventAsync(@event);
}
Expand Down
Loading

0 comments on commit b974131

Please sign in to comment.