Skip to content

Commit

Permalink
Refactor DaxText to immutable (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
albertospelta authored Mar 5, 2024
1 parent fa4521a commit ecac8bf
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 31 deletions.
12 changes: 6 additions & 6 deletions src/Dax.Vpax.Obfuscator/DaxModelObfuscator.ObfuscateText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ internal DaxText ObfuscateText(DaxText text)
if (Texts.TryGet(text, out var obfuscatedText))
return obfuscatedText ?? throw new InvalidOperationException($"Obfuscated text is null. {text.Value}");

_ = _obfuscator.Obfuscate(text);
obfuscatedText = _obfuscator.Obfuscate(text);

var retryLimit = DaxTextObfuscator.RetryLimitBeforeExtension + 100; // retry with the same length otherwise extend the obfuscated string length up to 100 characters
var retryCount = 0;
while (retryCount < retryLimit && IsRetryNeeded())
_ = _obfuscator.Obfuscate(text, ++retryCount);
obfuscatedText = _obfuscator.Obfuscate(obfuscatedText, ++retryCount);

if (retryCount >= retryLimit)
throw new InvalidOperationException($"Failed to obfuscate text. {text.Value} | {text.ObfuscatedValue}");
throw new InvalidOperationException($"Failed to obfuscate text. {obfuscatedText.Value} | {obfuscatedText.ObfuscatedValue}");

Texts.Add(text); // << throws in case of unresolved collision (duplicate value/obfuscated value)
return text;
Texts.Add(obfuscatedText); // << throws in case of unresolved collision (duplicate value/obfuscated value)
return obfuscatedText;

bool IsRetryNeeded() => text.IsObfuscatedAsDaxKeyword || Texts.Contains(text);
bool IsRetryNeeded() => obfuscatedText.IsObfuscatedAsDaxKeyword || Texts.Contains(obfuscatedText);
}
}
7 changes: 4 additions & 3 deletions src/Dax.Vpax.Obfuscator/DaxModelObfuscator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ void CreateKpiMeasure(DaxExpression kpi, string type)
{
if (string.IsNullOrWhiteSpace(kpi?.Expression)) return;

var text = new DaxText($"_{name} {type}");
text.ObfuscatedValue = $"_{obfuscatedName} {type}";
var value = $"_{name} {type}";
var obfuscatedValue = $"_{obfuscatedName} {type}";
var text = new DaxText(value, obfuscatedValue);

// It may already exist in case of incremental obfuscation
// Only add the KPI measure if it does not exist. Can happen in case of incremental obfuscation
if (Texts.IsIncrementalObfuscation && Texts.Contains(text))
return;

Expand Down
2 changes: 1 addition & 1 deletion src/Dax.Vpax.Obfuscator/DaxText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public DaxText(string value)
}

public string Value { get; }
public string ObfuscatedValue { get; internal set; }
public string ObfuscatedValue { get; }
public bool IsObfuscatedAsDaxKeyword => DaxTextObfuscator.DaxKeywords.Contains(ObfuscatedValue ?? throw new InvalidOperationException("ObfuscatedValue is null"));

public override string ToString() => string.Format(CultureInfo.InvariantCulture, "{0} | {1}", Value, ObfuscatedValue);
Expand Down
25 changes: 12 additions & 13 deletions src/Dax.Vpax.Obfuscator/DaxTextObfuscator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ public DaxText Obfuscate(DaxText text, int retryCount = 0)
if (text.ObfuscatedValue == null && retryCount != 0) throw new InvalidOperationException("Text has not been obfuscated yet.");

// Skip obfuscation for reserved tokens and empty or whitespace strings
if (ReservedTokens.Contains(text.Value) || string.IsNullOrWhiteSpace(text.Value)) {
text.ObfuscatedValue = text.Value;
return text;
}
if (ReservedTokens.Contains(text.Value) || string.IsNullOrWhiteSpace(text.Value))
return new DaxText(text.Value, text.Value); // ObfuscatedValue is the same as Value

var plaintext = text.Value;
var salt = retryCount != 0 ? _random.Next() : 0; // An additional salt used during retries to avoid generating the same obfuscated 'base' string when extending the string length
Expand All @@ -39,14 +37,16 @@ public DaxText Obfuscate(DaxText text, int retryCount = 0)
var retryLenght = Math.Max(0, retryCount - RetryLimitBeforeExtension);
var obfuscated = new char[plaintext.Length + retryLenght];

for (var i = 0; i < obfuscated.Length; i++) {
// Skip reserved chars
if (i < plaintext.Length && IsReservedChar(plaintext[i])) {
obfuscated[i] = plaintext[i];
for (var i = 0; i < obfuscated.Length; i++)
{
if (i < plaintext.Length && IsReservedChar(plaintext[i]))
{
obfuscated[i] = plaintext[i]; // Do not obfuscate reserved characters
continue;
}
// Always use an alphabetic char for the first char to avoid generating invalid DAX identifiers
if (i == 0) {

if (i == 0) // Always use an alphabetic char for the first char to avoid generating invalid DAX identifiers
{
obfuscated[i] = AlphaCharSet[random.Next(AlphaCharSet.Length)];
continue;
}
Expand All @@ -55,10 +55,9 @@ public DaxText Obfuscate(DaxText text, int retryCount = 0)
}

var obfuscatedValue = new string(obfuscated);
Debug.WriteLineIf(condition: retryCount > 0, $"\t>> Retry {retryCount} for: {text.Value} | {text.ObfuscatedValue} > {obfuscatedValue}");
Debug.WriteLineIf(retryCount > 0, $"\t>> Retry {retryCount} for: {text.Value} | {text.ObfuscatedValue} > {obfuscatedValue}");

text.ObfuscatedValue = obfuscatedValue;
return text;
return new DaxText(text.Value, obfuscatedValue);
}

private static bool IsReservedChar(char @char)
Expand Down
13 changes: 5 additions & 8 deletions tests/Dax.Vpax.Obfuscator.Tests/DaxTextCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public class DaxTextCollectionTests
[InlineData("a", "a")]
public void Add_DuplicateValue_Throws(string value1, string value2)
{
var text1 = new DaxText(value1); text1.ObfuscatedValue = value1;
var text2 = new DaxText(value2); text2.ObfuscatedValue = value2;
var text1 = new DaxText(value1, value1);
var text2 = new DaxText(value2, value2);

var texts = new DaxTextCollection();
texts.Add(text1);
Expand All @@ -32,7 +32,7 @@ public void Add_DuplicateValue_Throws__DifferentLengthsTest(int count)
var dictionary = new DaxTextCollection();

foreach (var value in values) {
var text = new DaxText(value); text.ObfuscatedValue = value;
var text = new DaxText(value, value);
dictionary.Add(text); // Seed the result dictionary
Assert.Throws<ArgumentException>(() => dictionary.Add(text));
}
Expand All @@ -44,11 +44,8 @@ public void Add_DuplicateValue_Throws__DifferentLengthsTest(int count)
[InlineData("a", "a")]
public void Add_DuplicateObfuscatedValue_Throws(string obfuscatedValue1, string obfuscatedValue2)
{
var text1 = new DaxText("X");
var text2 = new DaxText("Y");

text1.ObfuscatedValue = obfuscatedValue1;
text2.ObfuscatedValue = obfuscatedValue2;
var text1 = new DaxText("X", obfuscatedValue1);
var text2 = new DaxText("Y", obfuscatedValue2);

var texts = new DaxTextCollection();
texts.Add(text1);
Expand Down

0 comments on commit ecac8bf

Please sign in to comment.