diff --git a/SabreTools.IO.Extensions.Test/ByteArrayExtensionsTests.cs b/SabreTools.IO.Extensions.Test/ByteArrayExtensionsTests.cs index 46d2ca8..f124ef2 100644 --- a/SabreTools.IO.Extensions.Test/ByteArrayExtensionsTests.cs +++ b/SabreTools.IO.Extensions.Test/ByteArrayExtensionsTests.cs @@ -1,5 +1,5 @@ -using System; using System.Text; +using SabreTools.Matching; using Xunit; namespace SabreTools.IO.Extensions.Test @@ -129,394 +129,6 @@ namespace SabreTools.IO.Extensions.Test #endregion - #region FindAllPositions - - [Fact] - public void FindAllPositions_EmptyStack_NoMatches() - { - byte[] stack = []; - var positions = stack.FindAllPositions([0x01]); - Assert.Empty(positions); - } - - [Fact] - public void FindAllPositions_EmptyNeedle_NoMatches() - { - byte[] stack = [0x01]; - var positions = stack.FindAllPositions(Array.Empty()); - Assert.Empty(positions); - } - - [Fact] - public void FindAllPositions_LongerNeedle_NoMatches() - { - byte[] stack = [0x01]; - var positions = stack.FindAllPositions([0x01, 0x02]); - Assert.Empty(positions); - } - - [Fact] - public void FindAllPositions_InvalidStart_NoMatches() - { - byte[] stack = [0x01]; - var positions = stack.FindAllPositions([0x01, 0x02], start: -1); - Assert.Empty(positions); - - positions = stack.FindAllPositions([0x01, 0x02], start: 2); - Assert.Empty(positions); - } - - [Fact] - public void FindAllPositions_InvalidEnd_NoMatches() - { - byte[] stack = [0x01]; - var positions = stack.FindAllPositions([0x01, 0x02], end: -2); - Assert.Empty(positions); - - positions = stack.FindAllPositions([0x01, 0x02], end: 0); - Assert.Empty(positions); - - positions = stack.FindAllPositions([0x01, 0x02], end: 2); - Assert.Empty(positions); - } - - [Fact] - public void FindAllPositions_Matching_Matches() - { - byte[] stack = [0x01, 0x02]; - var positions = stack.FindAllPositions([0x01, 0x02]); - int position = Assert.Single(positions); - Assert.Equal(0, position); - } - - [Fact] - public void FindAllPositions_Mismatch_NoMatches() - { - byte[] stack = [0x01, 0x03]; - var positions = stack.FindAllPositions([0x01, 0x02]); - Assert.Empty(positions); - } - - [Fact] - public void FindAllPositions_Multiple_Matches() - { - byte[] stack = [0x01, 0x01]; - var positions = stack.FindAllPositions([0x01]); - Assert.Equal(2, positions.Count); - } - - #endregion - - #region FirstPosition - - [Fact] - public void FirstPosition_EmptyStack_NoMatches() - { - byte[] stack = []; - int position = stack.FirstPosition([0x01]); - Assert.Equal(-1, position); - } - - [Fact] - public void FirstPosition_EmptyNeedle_NoMatches() - { - byte[] stack = [0x01]; - int position = stack.FirstPosition(Array.Empty()); - Assert.Equal(-1, position); - } - - [Fact] - public void FirstPosition_LongerNeedle_NoMatches() - { - byte[] stack = [0x01]; - int position = stack.FirstPosition([0x01, 0x02]); - Assert.Equal(-1, position); - } - - [Fact] - public void FirstPosition_InvalidStart_NoMatches() - { - byte[] stack = [0x01]; - int position = stack.FirstPosition([0x01, 0x02], start: -1); - Assert.Equal(-1, position); - - position = stack.FirstPosition([0x01, 0x02], start: 2); - Assert.Equal(-1, position); - } - - [Fact] - public void FirstPosition_InvalidEnd_NoMatches() - { - byte[] stack = [0x01]; - int position = stack.FirstPosition([0x01, 0x02], end: -2); - Assert.Equal(-1, position); - - position = stack.FirstPosition([0x01, 0x02], end: 0); - Assert.Equal(-1, position); - - position = stack.FirstPosition([0x01, 0x02], end: 2); - Assert.Equal(-1, position); - } - - [Fact] - public void FirstPosition_Matching_Matches() - { - byte[] stack = [0x01, 0x02]; - int position = stack.FirstPosition([0x01, 0x02]); - Assert.Equal(0, position); - } - - [Fact] - public void FirstPosition_Mismatch_NoMatches() - { - byte[] stack = [0x01, 0x03]; - int position = stack.FirstPosition([0x01, 0x02]); - Assert.Equal(-1, position); - } - - [Fact] - public void FirstPosition_Multiple_Matches() - { - byte[] stack = [0x01, 0x01]; - int position = stack.FirstPosition([0x01]); - Assert.Equal(0, position); - } - - #endregion - - #region LastPosition - - [Fact] - public void LastPosition_EmptyStack_NoMatches() - { - byte[] stack = []; - int position = stack.LastPosition([0x01]); - Assert.Equal(-1, position); - } - - [Fact] - public void LastPosition_EmptyNeedle_NoMatches() - { - byte[] stack = [0x01]; - int position = stack.LastPosition(Array.Empty()); - Assert.Equal(-1, position); - } - - [Fact] - public void LastPosition_LongerNeedle_NoMatches() - { - byte[] stack = [0x01]; - int position = stack.LastPosition([0x01, 0x02]); - Assert.Equal(-1, position); - } - - [Fact] - public void LastPosition_InvalidStart_NoMatches() - { - byte[] stack = [0x01]; - int position = stack.LastPosition([0x01, 0x02], start: -1); - Assert.Equal(-1, position); - - position = stack.LastPosition([0x01, 0x02], start: 2); - Assert.Equal(-1, position); - } - - [Fact] - public void LastPosition_InvalidEnd_NoMatches() - { - byte[] stack = [0x01]; - int position = stack.LastPosition([0x01, 0x02], end: -2); - Assert.Equal(-1, position); - - position = stack.LastPosition([0x01, 0x02], end: 0); - Assert.Equal(-1, position); - - position = stack.LastPosition([0x01, 0x02], end: 2); - Assert.Equal(-1, position); - } - - [Fact] - public void LastPosition_Matching_Matches() - { - byte[] stack = [0x01, 0x02]; - int position = stack.LastPosition([0x01, 0x02]); - Assert.Equal(0, position); - } - - [Fact] - public void LastPosition_Mismatch_NoMatches() - { - byte[] stack = [0x01, 0x03]; - int position = stack.LastPosition([0x01, 0x02]); - Assert.Equal(-1, position); - } - - [Fact] - public void LastPosition_Multiple_Matches() - { - byte[] stack = [0x01, 0x01]; - int position = stack.LastPosition([0x01]); - Assert.Equal(1, position); - } - - #endregion - - #region EqualsExactly - - [Fact] - public void EqualsExactly_EmptyStack_NoMatches() - { - byte[] stack = []; - bool found = stack.EqualsExactly([0x01]); - Assert.False(found); - } - - [Fact] - public void EqualsExactly_EmptyNeedle_NoMatches() - { - byte[] stack = [0x01]; - bool found = stack.EqualsExactly(Array.Empty()); - Assert.False(found); - } - - [Fact] - public void EqualsExactly_ShorterNeedle_NoMatches() - { - byte[] stack = [0x01, 0x02]; - bool found = stack.EqualsExactly([0x01]); - Assert.False(found); - } - - [Fact] - public void EqualsExactly_LongerNeedle_NoMatches() - { - byte[] stack = [0x01]; - bool found = stack.EqualsExactly([0x01, 0x02]); - Assert.False(found); - } - - [Fact] - public void EqualsExactly_Matching_Matches() - { - byte[] stack = [0x01, 0x02]; - bool found = stack.EqualsExactly([0x01, 0x02]); - Assert.True(found); - } - - [Fact] - public void EqualsExactly_Mismatch_NoMatches() - { - byte[] stack = [0x01, 0x03]; - bool found = stack.EqualsExactly([0x01, 0x02]); - Assert.False(found); - } - - #endregion - - #region StartsWith - - [Fact] - public void StartsWith_EmptyStack_NoMatches() - { - byte[] stack = []; - bool found = stack.StartsWith([0x01]); - Assert.False(found); - } - - [Fact] - public void StartsWith_EmptyNeedle_NoMatches() - { - byte[] stack = [0x01]; - bool found = stack.StartsWith(Array.Empty()); - Assert.False(found); - } - - [Fact] - public void StartsWith_LongerNeedle_NoMatches() - { - byte[] stack = [0x01]; - bool found = stack.StartsWith([0x01, 0x02]); - Assert.False(found); - } - - [Fact] - public void StartsWith_Matching_Matches() - { - byte[] stack = [0x01, 0x02]; - bool found = stack.StartsWith([0x01, 0x02]); - Assert.True(found); - } - - [Fact] - public void StartsWith_Mismatch_NoMatches() - { - byte[] stack = [0x01, 0x03]; - bool found = stack.StartsWith([0x01, 0x02]); - Assert.False(found); - } - - [Fact] - public void StartsWith_Multiple_Matches() - { - byte[] stack = [0x01, 0x01]; - bool found = stack.StartsWith([0x01]); - Assert.True(found); - } - - #endregion - - #region EndsWith - - [Fact] - public void EndsWith_EmptyStack_NoMatches() - { - byte[] stack = []; - bool found = stack.EndsWith([0x01]); - Assert.False(found); - } - - [Fact] - public void EndsWith_EmptyNeedle_NoMatches() - { - byte[] stack = [0x01]; - bool found = stack.EndsWith(Array.Empty()); - Assert.False(found); - } - - [Fact] - public void EndsWith_LongerNeedle_NoMatches() - { - byte[] stack = [0x01]; - bool found = stack.StartsWith([0x01, 0x02]); - Assert.False(found); - } - - [Fact] - public void EndsWith_Matching_Matches() - { - byte[] stack = [0x01, 0x02]; - bool found = stack.EndsWith([0x01, 0x02]); - Assert.True(found); - } - - [Fact] - public void EndsWith_Mismatch_NoMatches() - { - byte[] stack = [0x01, 0x03]; - bool found = stack.EndsWith([0x01, 0x02]); - Assert.False(found); - } - - [Fact] - public void EndsWith_Multiple_Matches() - { - byte[] stack = [0x01, 0x01]; - bool found = stack.EndsWith([0x01]); - Assert.True(found); - } - - #endregion - #region Add [Theory] diff --git a/SabreTools.IO.Extensions/ByteArrayExtensions.cs b/SabreTools.IO.Extensions/ByteArrayExtensions.cs index 99e885c..7ed2696 100644 --- a/SabreTools.IO.Extensions/ByteArrayExtensions.cs +++ b/SabreTools.IO.Extensions/ByteArrayExtensions.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using SabreTools.Matching; using SabreTools.Numerics.Extensions; namespace SabreTools.IO.Extensions @@ -55,227 +53,6 @@ namespace SabreTools.IO.Extensions return Array.TrueForAll(arr, b => b >= 0x30 && b <= 0x39); } - #region Matching - - /// - /// Find all positions of one array in another, if possible - /// - /// Byte array to search within - /// Byte array representing the search value - /// Optional starting position in the stack, defaults to 0 - /// Optional ending position in the stack, defaults to -1 (length of stack) - public static List FindAllPositions(this byte[] stack, byte[] needle, int start = 0, int end = -1) - { - byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); - return FindAllPositions(stack, nullableNeedle, start, end); - } - - /// - /// Find all positions of one array in another, if possible - /// - /// Byte array to search within - /// Byte array representing the search value - /// Optional starting position in the stack, defaults to 0 - /// Optional ending position in the stack, defaults to -1 (length of stack) - public static List FindAllPositions(this byte[] stack, byte?[] needle, int start = 0, int end = -1) - { - // Get the outgoing list - List positions = []; - - // If either set is null or empty - if (stack.Length == 0 || needle.Length == 0) - return positions; - - // If the needle is longer than the stack - if (needle.Length > stack.Length) - return positions; - - // Normalize the end value, if necessary - if (end == -1) - end = stack.Length; - - // Validate the start and end values - if (start < 0 || start >= stack.Length) - return positions; - if (end < -1 || end < start || end > stack.Length) - return positions; - - // Loop while there is data to check - while (start < end) - { - // Create a new matcher for this segment - var matcher = new ContentMatch(needle, start, end); - - // Get the next matching position - int position = matcher.Match(stack, reverse: false); - if (position < 0) - break; - - // Append the position and reset the start index - positions.Add(position); - start = position + 1; - } - - return positions; - } - - /// - /// Find the first position of one array in another, if possible - /// - /// Byte array to search within - /// Byte array representing the search value - /// Optional starting position in the stack, defaults to 0 - /// Optional ending position in the stack, defaults to -1 (length of stack) - public static int FirstPosition(this byte[] stack, byte[] needle, int start = 0, int end = -1) - { - byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); - return FirstPosition(stack, nullableNeedle, start, end); - } - - /// - /// Find the first position of one array in another, if possible - /// - /// Byte array to search within - /// Byte array representing the search value - /// Optional starting position in the stack, defaults to 0 - /// Optional ending position in the stack, defaults to -1 (length of stack) - public static int FirstPosition(this byte[] stack, byte?[] needle, int start = 0, int end = -1) - { - // If either set is null or empty - if (stack.Length == 0 || needle.Length == 0) - return -1; - - // If the needle is longer than the stack - if (needle.Length > stack.Length) - return -1; - - var matcher = new ContentMatch(needle, start, end); - return matcher.Match(stack, reverse: false); - } - - /// - /// Find the last position of one array in another, if possible - /// - /// Byte array to search within - /// Byte array representing the search value - /// Optional starting position in the stack, defaults to 0 - /// Optional ending position in the stack, defaults to -1 (length of stack) - public static int LastPosition(this byte[] stack, byte[] needle, int start = 0, int end = -1) - { - byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); - return LastPosition(stack, nullableNeedle, start, end); - } - - /// - /// Find the last position of one array in another, if possible - /// - /// Byte array to search within - /// Byte array representing the search value - /// Optional starting position in the stack, defaults to 0 - /// Optional ending position in the stack, defaults to -1 (length of stack) - public static int LastPosition(this byte[] stack, byte?[] needle, int start = 0, int end = -1) - { - // If either set is null or empty - if (stack.Length == 0 || needle.Length == 0) - return -1; - - // If the needle is longer than the stack - if (needle.Length > stack.Length) - return -1; - - var matcher = new ContentMatch(needle, start, end); - return matcher.Match(stack, reverse: true); - } - - /// - /// Check if a byte array exactly matches another - /// - /// Byte array to search within - /// Byte array representing the search value - public static bool EqualsExactly(this byte[] stack, byte[] needle) - { - byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); - return EqualsExactly(stack, nullableNeedle); - } - - /// - /// Check if a byte array exactly matches another - /// - /// Byte array to search within - /// Byte array representing the search value - public static bool EqualsExactly(this byte[] stack, byte?[] needle) - { - // If either set is null or empty - if (stack.Length == 0 || needle.Length == 0) - return false; - - // If the needle is not the exact length of the stack - if (needle.Length != stack.Length) - return false; - - return FirstPosition(stack, needle, start: 0, end: 1) == 0; - } - - /// - /// Check if a byte array starts with another - /// - /// Byte array to search within - /// Byte array representing the search value - public static bool StartsWith(this byte[] stack, byte[] needle) - { - byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); - return StartsWith(stack, nullableNeedle); - } - - /// - /// Check if a byte array starts with another - /// - /// Byte array to search within - /// Byte array representing the search value - public static bool StartsWith(this byte[] stack, byte?[] needle) - { - // If either set is null or empty - if (stack.Length == 0 || needle.Length == 0) - return false; - - // If the needle is longer than the stack - if (needle.Length > stack.Length) - return false; - - return FirstPosition(stack, needle, start: 0, end: 1) > -1; - } - - /// - /// Check if a byte array ends with another - /// - /// Byte array to search within - /// Byte array representing the search value - public static bool EndsWith(this byte[] stack, byte[] needle) - { - byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); - return EndsWith(stack, nullableNeedle); - } - - /// - /// Check if a byte array ends with another - /// - /// Byte array to search within - /// Byte array representing the search value - public static bool EndsWith(this byte[] stack, byte?[] needle) - { - // If either set is null or empty - if (stack.Length == 0 || needle.Length == 0) - return false; - - // If the needle is longer than the stack - if (needle.Length > stack.Length) - return false; - - return FirstPosition(stack, needle, start: stack.Length - needle.Length) > -1; - } - - #endregion - #region Math /// diff --git a/SabreTools.IO.Meta/SabreTools.IO.Meta.csproj b/SabreTools.IO.Meta/SabreTools.IO.Meta.csproj index df65d5b..03b187a 100644 --- a/SabreTools.IO.Meta/SabreTools.IO.Meta.csproj +++ b/SabreTools.IO.Meta/SabreTools.IO.Meta.csproj @@ -42,7 +42,7 @@ - + diff --git a/SabreTools.IO.Test/TransformTests.cs b/SabreTools.IO.Test/TransformTests.cs index 5c83151..fd8c63b 100644 --- a/SabreTools.IO.Test/TransformTests.cs +++ b/SabreTools.IO.Test/TransformTests.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using SabreTools.IO.Extensions; +using SabreTools.Matching; using Xunit; #pragma warning disable IDE0230 // Use UTF-8 string literal diff --git a/SabreTools.Matching.Test/ByteArrayExtensionsTests.cs b/SabreTools.Matching.Test/ByteArrayExtensionsTests.cs new file mode 100644 index 0000000..64ae3f5 --- /dev/null +++ b/SabreTools.Matching.Test/ByteArrayExtensionsTests.cs @@ -0,0 +1,396 @@ +using System; +using Xunit; + +namespace SabreTools.Matching.Test +{ + public class ByteArrayExtensionsTests + { + #region FindAllPositions + + [Fact] + public void FindAllPositions_EmptyStack_NoMatches() + { + byte[] stack = []; + var positions = stack.FindAllPositions([0x01]); + Assert.Empty(positions); + } + + [Fact] + public void FindAllPositions_EmptyNeedle_NoMatches() + { + byte[] stack = [0x01]; + var positions = stack.FindAllPositions(Array.Empty()); + Assert.Empty(positions); + } + + [Fact] + public void FindAllPositions_LongerNeedle_NoMatches() + { + byte[] stack = [0x01]; + var positions = stack.FindAllPositions([0x01, 0x02]); + Assert.Empty(positions); + } + + [Fact] + public void FindAllPositions_InvalidStart_NoMatches() + { + byte[] stack = [0x01]; + var positions = stack.FindAllPositions([0x01, 0x02], start: -1); + Assert.Empty(positions); + + positions = stack.FindAllPositions([0x01, 0x02], start: 2); + Assert.Empty(positions); + } + + [Fact] + public void FindAllPositions_InvalidEnd_NoMatches() + { + byte[] stack = [0x01]; + var positions = stack.FindAllPositions([0x01, 0x02], end: -2); + Assert.Empty(positions); + + positions = stack.FindAllPositions([0x01, 0x02], end: 0); + Assert.Empty(positions); + + positions = stack.FindAllPositions([0x01, 0x02], end: 2); + Assert.Empty(positions); + } + + [Fact] + public void FindAllPositions_Matching_Matches() + { + byte[] stack = [0x01, 0x02]; + var positions = stack.FindAllPositions([0x01, 0x02]); + int position = Assert.Single(positions); + Assert.Equal(0, position); + } + + [Fact] + public void FindAllPositions_Mismatch_NoMatches() + { + byte[] stack = [0x01, 0x03]; + var positions = stack.FindAllPositions([0x01, 0x02]); + Assert.Empty(positions); + } + + [Fact] + public void FindAllPositions_Multiple_Matches() + { + byte[] stack = [0x01, 0x01]; + var positions = stack.FindAllPositions([0x01]); + Assert.Equal(2, positions.Count); + } + + #endregion + + #region FirstPosition + + [Fact] + public void FirstPosition_EmptyStack_NoMatches() + { + byte[] stack = []; + int position = stack.FirstPosition([0x01]); + Assert.Equal(-1, position); + } + + [Fact] + public void FirstPosition_EmptyNeedle_NoMatches() + { + byte[] stack = [0x01]; + int position = stack.FirstPosition(Array.Empty()); + Assert.Equal(-1, position); + } + + [Fact] + public void FirstPosition_LongerNeedle_NoMatches() + { + byte[] stack = [0x01]; + int position = stack.FirstPosition([0x01, 0x02]); + Assert.Equal(-1, position); + } + + [Fact] + public void FirstPosition_InvalidStart_NoMatches() + { + byte[] stack = [0x01]; + int position = stack.FirstPosition([0x01, 0x02], start: -1); + Assert.Equal(-1, position); + + position = stack.FirstPosition([0x01, 0x02], start: 2); + Assert.Equal(-1, position); + } + + [Fact] + public void FirstPosition_InvalidEnd_NoMatches() + { + byte[] stack = [0x01]; + int position = stack.FirstPosition([0x01, 0x02], end: -2); + Assert.Equal(-1, position); + + position = stack.FirstPosition([0x01, 0x02], end: 0); + Assert.Equal(-1, position); + + position = stack.FirstPosition([0x01, 0x02], end: 2); + Assert.Equal(-1, position); + } + + [Fact] + public void FirstPosition_Matching_Matches() + { + byte[] stack = [0x01, 0x02]; + int position = stack.FirstPosition([0x01, 0x02]); + Assert.Equal(0, position); + } + + [Fact] + public void FirstPosition_Mismatch_NoMatches() + { + byte[] stack = [0x01, 0x03]; + int position = stack.FirstPosition([0x01, 0x02]); + Assert.Equal(-1, position); + } + + [Fact] + public void FirstPosition_Multiple_Matches() + { + byte[] stack = [0x01, 0x01]; + int position = stack.FirstPosition([0x01]); + Assert.Equal(0, position); + } + + #endregion + + #region LastPosition + + [Fact] + public void LastPosition_EmptyStack_NoMatches() + { + byte[] stack = []; + int position = stack.LastPosition([0x01]); + Assert.Equal(-1, position); + } + + [Fact] + public void LastPosition_EmptyNeedle_NoMatches() + { + byte[] stack = [0x01]; + int position = stack.LastPosition(Array.Empty()); + Assert.Equal(-1, position); + } + + [Fact] + public void LastPosition_LongerNeedle_NoMatches() + { + byte[] stack = [0x01]; + int position = stack.LastPosition([0x01, 0x02]); + Assert.Equal(-1, position); + } + + [Fact] + public void LastPosition_InvalidStart_NoMatches() + { + byte[] stack = [0x01]; + int position = stack.LastPosition([0x01, 0x02], start: -1); + Assert.Equal(-1, position); + + position = stack.LastPosition([0x01, 0x02], start: 2); + Assert.Equal(-1, position); + } + + [Fact] + public void LastPosition_InvalidEnd_NoMatches() + { + byte[] stack = [0x01]; + int position = stack.LastPosition([0x01, 0x02], end: -2); + Assert.Equal(-1, position); + + position = stack.LastPosition([0x01, 0x02], end: 0); + Assert.Equal(-1, position); + + position = stack.LastPosition([0x01, 0x02], end: 2); + Assert.Equal(-1, position); + } + + [Fact] + public void LastPosition_Matching_Matches() + { + byte[] stack = [0x01, 0x02]; + int position = stack.LastPosition([0x01, 0x02]); + Assert.Equal(0, position); + } + + [Fact] + public void LastPosition_Mismatch_NoMatches() + { + byte[] stack = [0x01, 0x03]; + int position = stack.LastPosition([0x01, 0x02]); + Assert.Equal(-1, position); + } + + [Fact] + public void LastPosition_Multiple_Matches() + { + byte[] stack = [0x01, 0x01]; + int position = stack.LastPosition([0x01]); + Assert.Equal(1, position); + } + + #endregion + + #region EqualsExactly + + [Fact] + public void EqualsExactly_EmptyStack_NoMatches() + { + byte[] stack = []; + bool found = stack.EqualsExactly([0x01]); + Assert.False(found); + } + + [Fact] + public void EqualsExactly_EmptyNeedle_NoMatches() + { + byte[] stack = [0x01]; + bool found = stack.EqualsExactly(Array.Empty()); + Assert.False(found); + } + + [Fact] + public void EqualsExactly_ShorterNeedle_NoMatches() + { + byte[] stack = [0x01, 0x02]; + bool found = stack.EqualsExactly([0x01]); + Assert.False(found); + } + + [Fact] + public void EqualsExactly_LongerNeedle_NoMatches() + { + byte[] stack = [0x01]; + bool found = stack.EqualsExactly([0x01, 0x02]); + Assert.False(found); + } + + [Fact] + public void EqualsExactly_Matching_Matches() + { + byte[] stack = [0x01, 0x02]; + bool found = stack.EqualsExactly([0x01, 0x02]); + Assert.True(found); + } + + [Fact] + public void EqualsExactly_Mismatch_NoMatches() + { + byte[] stack = [0x01, 0x03]; + bool found = stack.EqualsExactly([0x01, 0x02]); + Assert.False(found); + } + + #endregion + + #region StartsWith + + [Fact] + public void StartsWith_EmptyStack_NoMatches() + { + byte[] stack = []; + bool found = stack.StartsWith([0x01]); + Assert.False(found); + } + + [Fact] + public void StartsWith_EmptyNeedle_NoMatches() + { + byte[] stack = [0x01]; + bool found = stack.StartsWith(Array.Empty()); + Assert.False(found); + } + + [Fact] + public void StartsWith_LongerNeedle_NoMatches() + { + byte[] stack = [0x01]; + bool found = stack.StartsWith([0x01, 0x02]); + Assert.False(found); + } + + [Fact] + public void StartsWith_Matching_Matches() + { + byte[] stack = [0x01, 0x02]; + bool found = stack.StartsWith([0x01, 0x02]); + Assert.True(found); + } + + [Fact] + public void StartsWith_Mismatch_NoMatches() + { + byte[] stack = [0x01, 0x03]; + bool found = stack.StartsWith([0x01, 0x02]); + Assert.False(found); + } + + [Fact] + public void StartsWith_Multiple_Matches() + { + byte[] stack = [0x01, 0x01]; + bool found = stack.StartsWith([0x01]); + Assert.True(found); + } + + #endregion + + #region EndsWith + + [Fact] + public void EndsWith_EmptyStack_NoMatches() + { + byte[] stack = []; + bool found = stack.EndsWith([0x01]); + Assert.False(found); + } + + [Fact] + public void EndsWith_EmptyNeedle_NoMatches() + { + byte[] stack = [0x01]; + bool found = stack.EndsWith(Array.Empty()); + Assert.False(found); + } + + [Fact] + public void EndsWith_LongerNeedle_NoMatches() + { + byte[] stack = [0x01]; + bool found = stack.StartsWith([0x01, 0x02]); + Assert.False(found); + } + + [Fact] + public void EndsWith_Matching_Matches() + { + byte[] stack = [0x01, 0x02]; + bool found = stack.EndsWith([0x01, 0x02]); + Assert.True(found); + } + + [Fact] + public void EndsWith_Mismatch_NoMatches() + { + byte[] stack = [0x01, 0x03]; + bool found = stack.EndsWith([0x01, 0x02]); + Assert.False(found); + } + + [Fact] + public void EndsWith_Multiple_Matches() + { + byte[] stack = [0x01, 0x01]; + bool found = stack.EndsWith([0x01]); + Assert.True(found); + } + + #endregion + } +} diff --git a/SabreTools.Matching/ByteArrayExtensions.cs b/SabreTools.Matching/ByteArrayExtensions.cs new file mode 100644 index 0000000..2a89bba --- /dev/null +++ b/SabreTools.Matching/ByteArrayExtensions.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; + +namespace SabreTools.Matching +{ + public static class ByteArrayExtensions + { + /// + /// Find all positions of one array in another, if possible + /// + /// Byte array to search within + /// Byte array representing the search value + /// Optional starting position in the stack, defaults to 0 + /// Optional ending position in the stack, defaults to -1 (length of stack) + public static List FindAllPositions(this byte[] stack, byte[] needle, int start = 0, int end = -1) + { + byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); + return FindAllPositions(stack, nullableNeedle, start, end); + } + + /// + /// Find all positions of one array in another, if possible + /// + /// Byte array to search within + /// Byte array representing the search value + /// Optional starting position in the stack, defaults to 0 + /// Optional ending position in the stack, defaults to -1 (length of stack) + public static List FindAllPositions(this byte[] stack, byte?[] needle, int start = 0, int end = -1) + { + // Get the outgoing list + List positions = []; + + // If either set is null or empty + if (stack.Length == 0 || needle.Length == 0) + return positions; + + // If the needle is longer than the stack + if (needle.Length > stack.Length) + return positions; + + // Normalize the end value, if necessary + if (end == -1) + end = stack.Length; + + // Validate the start and end values + if (start < 0 || start >= stack.Length) + return positions; + if (end < -1 || end < start || end > stack.Length) + return positions; + + // Loop while there is data to check + while (start < end) + { + // Create a new matcher for this segment + var matcher = new ContentMatch(needle, start, end); + + // Get the next matching position + int position = matcher.Match(stack, reverse: false); + if (position < 0) + break; + + // Append the position and reset the start index + positions.Add(position); + start = position + 1; + } + + return positions; + } + + /// + /// Find the first position of one array in another, if possible + /// + /// Byte array to search within + /// Byte array representing the search value + /// Optional starting position in the stack, defaults to 0 + /// Optional ending position in the stack, defaults to -1 (length of stack) + public static int FirstPosition(this byte[] stack, byte[] needle, int start = 0, int end = -1) + { + byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); + return FirstPosition(stack, nullableNeedle, start, end); + } + + /// + /// Find the first position of one array in another, if possible + /// + /// Byte array to search within + /// Byte array representing the search value + /// Optional starting position in the stack, defaults to 0 + /// Optional ending position in the stack, defaults to -1 (length of stack) + public static int FirstPosition(this byte[] stack, byte?[] needle, int start = 0, int end = -1) + { + // If either set is null or empty + if (stack.Length == 0 || needle.Length == 0) + return -1; + + // If the needle is longer than the stack + if (needle.Length > stack.Length) + return -1; + + var matcher = new ContentMatch(needle, start, end); + return matcher.Match(stack, reverse: false); + } + + /// + /// Find the last position of one array in another, if possible + /// + /// Byte array to search within + /// Byte array representing the search value + /// Optional starting position in the stack, defaults to 0 + /// Optional ending position in the stack, defaults to -1 (length of stack) + public static int LastPosition(this byte[] stack, byte[] needle, int start = 0, int end = -1) + { + byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); + return LastPosition(stack, nullableNeedle, start, end); + } + + /// + /// Find the last position of one array in another, if possible + /// + /// Byte array to search within + /// Byte array representing the search value + /// Optional starting position in the stack, defaults to 0 + /// Optional ending position in the stack, defaults to -1 (length of stack) + public static int LastPosition(this byte[] stack, byte?[] needle, int start = 0, int end = -1) + { + // If either set is null or empty + if (stack.Length == 0 || needle.Length == 0) + return -1; + + // If the needle is longer than the stack + if (needle.Length > stack.Length) + return -1; + + var matcher = new ContentMatch(needle, start, end); + return matcher.Match(stack, reverse: true); + } + + /// + /// Check if a byte array exactly matches another + /// + /// Byte array to search within + /// Byte array representing the search value + public static bool EqualsExactly(this byte[] stack, byte[] needle) + { + byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); + return EqualsExactly(stack, nullableNeedle); + } + + /// + /// Check if a byte array exactly matches another + /// + /// Byte array to search within + /// Byte array representing the search value + public static bool EqualsExactly(this byte[] stack, byte?[] needle) + { + // If either set is null or empty + if (stack.Length == 0 || needle.Length == 0) + return false; + + // If the needle is not the exact length of the stack + if (needle.Length != stack.Length) + return false; + + return FirstPosition(stack, needle, start: 0, end: 1) == 0; + } + + /// + /// Check if a byte array starts with another + /// + /// Byte array to search within + /// Byte array representing the search value + public static bool StartsWith(this byte[] stack, byte[] needle) + { + byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); + return StartsWith(stack, nullableNeedle); + } + + /// + /// Check if a byte array starts with another + /// + /// Byte array to search within + /// Byte array representing the search value + public static bool StartsWith(this byte[] stack, byte?[] needle) + { + // If either set is null or empty + if (stack.Length == 0 || needle.Length == 0) + return false; + + // If the needle is longer than the stack + if (needle.Length > stack.Length) + return false; + + return FirstPosition(stack, needle, start: 0, end: 1) > -1; + } + + /// + /// Check if a byte array ends with another + /// + /// Byte array to search within + /// Byte array representing the search value + public static bool EndsWith(this byte[] stack, byte[] needle) + { + byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b); + return EndsWith(stack, nullableNeedle); + } + + /// + /// Check if a byte array ends with another + /// + /// Byte array to search within + /// Byte array representing the search value + public static bool EndsWith(this byte[] stack, byte?[] needle) + { + // If either set is null or empty + if (stack.Length == 0 || needle.Length == 0) + return false; + + // If the needle is longer than the stack + if (needle.Length > stack.Length) + return false; + + return FirstPosition(stack, needle, start: stack.Length - needle.Length) > -1; + } + } +} diff --git a/SabreTools.Matching/ExtensionAttribute.cs b/SabreTools.Matching/ExtensionAttribute.cs new file mode 100644 index 0000000..478ed8b --- /dev/null +++ b/SabreTools.Matching/ExtensionAttribute.cs @@ -0,0 +1,9 @@ +#if NET20 + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + internal sealed class ExtensionAttribute : Attribute { } +} + +#endif diff --git a/SabreTools.Matching/README.MD b/SabreTools.Matching/README.MD index c05d27e..9b03f8b 100644 --- a/SabreTools.Matching/README.MD +++ b/SabreTools.Matching/README.MD @@ -16,6 +16,11 @@ This library contains classes designed to make matching contents and paths easie | `ContentMatch` | `IMatch` | Matches contents of a byte array, allowing wildcard bytes using `null` | | `ContentMatchSet` | `IMatchSet` | Group of logically-linked `ContentMatch` | | `FilePathMatch` | `IMatch` | Specialization of `PathMatch` that assumes the string is a filename | -| `MatchUtil` | N/A | Utility class for easier invocation of `SabreTools.Matching` functionality | | `PathMatch` | `IMatch` | Matches a set of strings representing a directory hierarchy | | `PathMatchSet` | `IMatchSet` | Group of logically-linked `PathMatch` | + +## Utility Classes + +| Class | Description | +| `ByteArrayExtensions` | Extensions for searching and matching within a byte array, similar to `System.Linq` | +| `MatchUtil` | Utility class for easier invocation of `SabreTools.Matching` functionality | diff --git a/SabreTools.Security.Cryptography/MoPaQDecrypter.cs b/SabreTools.Security.Cryptography/MoPaQDecrypter.cs index 529ee8d..7424a67 100644 --- a/SabreTools.Security.Cryptography/MoPaQDecrypter.cs +++ b/SabreTools.Security.Cryptography/MoPaQDecrypter.cs @@ -2,7 +2,7 @@ using System; using System.IO; using System.Text; using SabreTools.Hashing; -using SabreTools.IO.Extensions; +using SabreTools.Matching; #pragma warning disable IDE0051 namespace SabreTools.Security.Cryptography