From b43997065c92c39e22536138083755dbc16ea8d1 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 30 Jul 2020 22:32:16 -0700 Subject: [PATCH] More cleanup of Skippers --- SabreTools.Library/Skippers/SkipperFile.cs | 311 +++++++++++++-------- SabreTools.Library/Skippers/SkipperRule.cs | 17 ++ SabreTools.Library/Skippers/SkipperTest.cs | 266 +++++++++++++++++- SabreTools.Library/Skippers/Transform.cs | 149 +--------- 4 files changed, 483 insertions(+), 260 deletions(-) diff --git a/SabreTools.Library/Skippers/SkipperFile.cs b/SabreTools.Library/Skippers/SkipperFile.cs index a23adb54..c1d47f10 100644 --- a/SabreTools.Library/Skippers/SkipperFile.cs +++ b/SabreTools.Library/Skippers/SkipperFile.cs @@ -78,6 +78,7 @@ namespace SabreTools.Library.Skippers /// Parse an XML document in as a SkipperFile /// /// XmlReader representing the document + /// True if the file could be parsed, false otherwise private bool Parse(XmlReader xtr) { if (xtr == null) @@ -136,6 +137,7 @@ namespace SabreTools.Library.Skippers /// Parse an XML document in as a SkipperRule /// /// XmlReader representing the document + /// Filled SkipperRule on success, null otherwise private SkipperRule ParseRule(XmlReader xtr) { if (xtr == null) @@ -206,124 +208,7 @@ namespace SabreTools.Library.Skippers if (subreader.NodeType != XmlNodeType.Element) subreader.Read(); - // Get the test type - SkipperTest test = new SkipperTest - { - Offset = 0, - Value = new byte[0], - Result = true, - Mask = new byte[0], - Size = 0, - Operator = HeaderSkipTestFileOperator.Equal, - }; - switch (subreader.Name.ToLowerInvariant()) - { - case "data": - test.Type = HeaderSkipTest.Data; - break; - - case "or": - test.Type = HeaderSkipTest.Or; - break; - - case "xor": - test.Type = HeaderSkipTest.Xor; - break; - - case "and": - test.Type = HeaderSkipTest.And; - break; - - case "file": - test.Type = HeaderSkipTest.File; - break; - - default: - subreader.Read(); - break; - } - - // Now populate all the parts that we can - if (subreader.GetAttribute("offset") != null) - { - string offset = subreader.GetAttribute("offset"); - if (offset.ToLowerInvariant() == "eof") - test.Offset = null; - else - test.Offset = Convert.ToInt64(offset, 16); - } - - if (subreader.GetAttribute("value") != null) - { - string value = subreader.GetAttribute("value"); - - // http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array - test.Value = new byte[value.Length / 2]; - for (int index = 0; index < test.Value.Length; index++) - { - string byteValue = value.Substring(index * 2, 2); - test.Value[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - } - } - - if (subreader.GetAttribute("result") != null) - { - string result = subreader.GetAttribute("result"); - if (!bool.TryParse(result, out bool resultBool)) - resultBool = true; - - test.Result = resultBool; - } - - if (subreader.GetAttribute("mask") != null) - { - string mask = subreader.GetAttribute("mask"); - - // http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array - test.Mask = new byte[mask.Length / 2]; - for (int index = 0; index < test.Mask.Length; index++) - { - string byteValue = mask.Substring(index * 2, 2); - test.Mask[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - } - } - - if (subreader.GetAttribute("size") != null) - { - string size = subreader.GetAttribute("size"); - if (size.ToLowerInvariant() == "po2") - test.Size = null; - else - test.Size = Convert.ToInt64(size, 16); - } - - if (subreader.GetAttribute("operator") != null) - { - string oper = subreader.GetAttribute("operator"); -#if NET_FRAMEWORK - switch (oper.ToLowerInvariant()) - { - case "less": - test.Operator = HeaderSkipTestFileOperator.Less; - break; - case "greater": - test.Operator = HeaderSkipTestFileOperator.Greater; - break; - case "equal": - default: - test.Operator = HeaderSkipTestFileOperator.Equal; - break; - } -#else - test.Operator = oper.ToLowerInvariant() switch - { - "less" => HeaderSkipTestFileOperator.Less, - "greater" => HeaderSkipTestFileOperator.Greater, - "equal" => HeaderSkipTestFileOperator.Equal, - _ => HeaderSkipTestFileOperator.Equal, - }; -#endif - } + SkipperTest test = ParseTest(xtr); // Add the created test to the rule rule.Tests.Add(test); @@ -339,6 +224,196 @@ namespace SabreTools.Library.Skippers } } + /// + /// Parse an XML document in as a SkipperTest + /// + /// XmlReader representing the document + /// Filled SkipperTest on success, null otherwise + private SkipperTest ParseTest(XmlReader xtr) + { + if (xtr == null) + return null; + + try + { + // Get the test type + SkipperTest test = new SkipperTest + { + Offset = 0, + Value = new byte[0], + Result = true, + Mask = new byte[0], + Size = 0, + Operator = HeaderSkipTestFileOperator.Equal, + }; + + switch (xtr.Name.ToLowerInvariant()) + { + case "data": + test.Type = HeaderSkipTest.Data; + break; + + case "or": + test.Type = HeaderSkipTest.Or; + break; + + case "xor": + test.Type = HeaderSkipTest.Xor; + break; + + case "and": + test.Type = HeaderSkipTest.And; + break; + + case "file": + test.Type = HeaderSkipTest.File; + break; + + default: + xtr.Read(); + break; + } + + // Now populate all the parts that we can + if (xtr.GetAttribute("offset") != null) + { + string offset = xtr.GetAttribute("offset"); + if (offset.ToLowerInvariant() == "eof") + test.Offset = null; + else + test.Offset = Convert.ToInt64(offset, 16); + } + + if (xtr.GetAttribute("value") != null) + { + string value = xtr.GetAttribute("value"); + + // http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array + test.Value = new byte[value.Length / 2]; + for (int index = 0; index < test.Value.Length; index++) + { + string byteValue = value.Substring(index * 2, 2); + test.Value[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + } + + if (xtr.GetAttribute("result") != null) + { + string result = xtr.GetAttribute("result"); + if (!bool.TryParse(result, out bool resultBool)) + resultBool = true; + + test.Result = resultBool; + } + + if (xtr.GetAttribute("mask") != null) + { + string mask = xtr.GetAttribute("mask"); + + // http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array + test.Mask = new byte[mask.Length / 2]; + for (int index = 0; index < test.Mask.Length; index++) + { + string byteValue = mask.Substring(index * 2, 2); + test.Mask[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + } + + if (xtr.GetAttribute("size") != null) + { + string size = xtr.GetAttribute("size"); + if (size.ToLowerInvariant() == "po2") + test.Size = null; + else + test.Size = Convert.ToInt64(size, 16); + } + + if (xtr.GetAttribute("operator") != null) + { + string oper = xtr.GetAttribute("operator"); +#if NET_FRAMEWORK + switch (oper.ToLowerInvariant()) + { + case "less": + test.Operator = HeaderSkipTestFileOperator.Less; + break; + case "greater": + test.Operator = HeaderSkipTestFileOperator.Greater; + break; + case "equal": + default: + test.Operator = HeaderSkipTestFileOperator.Equal; + break; + } +#else + test.Operator = oper.ToLowerInvariant() switch + { + "less" => HeaderSkipTestFileOperator.Less, + "greater" => HeaderSkipTestFileOperator.Greater, + "equal" => HeaderSkipTestFileOperator.Equal, + _ => HeaderSkipTestFileOperator.Equal, + }; +#endif + } + + return test; + } + catch + { + return null; + } + } + + #endregion + + #region Matching + + /// + /// Get the SkipperRule associated with a given stream + /// + /// Stream to be checked + /// Name of the skipper to be used, blank to find a matching skipper + /// The SkipperRule that matched the stream, null otherwise + public SkipperRule GetMatchingRule(Stream input, string skipperName) + { + // If we have no name supplied, try to blindly match + if (string.IsNullOrWhiteSpace(skipperName)) + return GetMatchingRule(input); + + // If the name matches the internal name of the skipper + else if (string.Equals(skipperName, Name, StringComparison.OrdinalIgnoreCase)) + return GetMatchingRule(input); + + // If the name matches the source file name of the skipper + else if (string.Equals(skipperName, SourceFile, StringComparison.OrdinalIgnoreCase)) + return GetMatchingRule(input); + + // Otherwise, nothing matches by default + return null; + } + + /// + /// Get the matching SkipperRule from all Rules, if possible + /// + /// Stream to be checked + /// The SkipperRule that matched the stream, null otherwise + private SkipperRule GetMatchingRule(Stream input) + { + // Loop through the rules until one is found that works + foreach (SkipperRule rule in Rules) + { + // Always reset the stream back to the original place + input.Seek(0, SeekOrigin.Begin); + + // If all tests in the rule pass, we return this rule + if (rule.PassesAllTests(input)) + return rule; + } + + // If nothing passed, we return null by default + return null; + } + #endregion } } diff --git a/SabreTools.Library/Skippers/SkipperRule.cs b/SabreTools.Library/Skippers/SkipperRule.cs index 936b48f0..4c3bf742 100644 --- a/SabreTools.Library/Skippers/SkipperRule.cs +++ b/SabreTools.Library/Skippers/SkipperRule.cs @@ -38,6 +38,23 @@ namespace SabreTools.Library.Skippers #endregion + /// + /// Check if a Stream passes all tests in the SkipperRule + /// + /// Stream to check + /// True if all tests passed, false otherwise + public bool PassesAllTests(Stream input) + { + bool success = true; + foreach (SkipperTest test in Tests) + { + bool result = test.Passes(input); + success &= result; + } + + return success; + } + /// /// Transform an input file using the given rule /// diff --git a/SabreTools.Library/Skippers/SkipperTest.cs b/SabreTools.Library/Skippers/SkipperTest.cs index d3f3f98b..237d3d58 100644 --- a/SabreTools.Library/Skippers/SkipperTest.cs +++ b/SabreTools.Library/Skippers/SkipperTest.cs @@ -1,12 +1,17 @@ +using System; +using System.IO; + using SabreTools.Library.Data; namespace SabreTools.Library.Skippers { /// - /// Intermediate class for storing Skipper Test information + /// Individual test that applies to a SkipperRule /// - public struct SkipperTest + public class SkipperTest { + #region Fields + /// /// Type of test to be run /// @@ -15,7 +20,8 @@ namespace SabreTools.Library.Skippers /// /// File offset to run the test /// - public long? Offset { get; set; } // null is EOF + /// null is EOF + public long? Offset { get; set; } /// /// Static value to be checked at the offset @@ -41,5 +47,259 @@ namespace SabreTools.Library.Skippers /// Expected range value for the input byte array size, used with Size /// public HeaderSkipTestFileOperator Operator { get; set; } + + #endregion + + /// + /// Check if a stream passes the test + /// + /// Stream to check rule against + /// The Stream is assumed to be in the proper position for a given test + public bool Passes(Stream input) + { + bool result = true; + switch (Type) + { + case HeaderSkipTest.And: + return CheckAnd(input); + case HeaderSkipTest.Data: + return CheckData(input); + case HeaderSkipTest.File: + return CheckFile(input); + case HeaderSkipTest.Or: + return CheckOr(input); + case HeaderSkipTest.Xor: + return CheckXor(input); + } + + return result; + } + + #region Checking Helpers + + /// + /// Run an And test against an input stream + /// + /// Stream to check rule against + /// True if the stream passed, false otherwise + private bool CheckAnd(Stream input) + { + // First seek to the correct position + Seek(input); + + bool result = true; + try + { + // Then apply the mask if it exists + byte[] read = new byte[Mask.Length]; + input.Read(read, 0, Mask.Length); + + byte[] masked = new byte[Mask.Length]; + for (int i = 0; i < read.Length; i++) + { + masked[i] = (byte)(read[i] & Mask[i]); + } + + // Finally, compare it against the value + for (int i = 0; i < Value.Length; i++) + { + if (masked[i] != Value[i]) + { + result = false; + break; + } + } + } + catch + { + result = false; + } + + // Return if the expected and actual results match + return result == Result; + } + + /// + /// Run a Data test against an input stream + /// + /// Stream to check rule against + /// True if the stream passed, false otherwise + private bool CheckData(Stream input) + { + // First seek to the correct position + if (!Seek(input)) + return false; + + // Then read and compare bytewise + bool result = true; + for (int i = 0; i < Value.Length; i++) + { + try + { + if (input.ReadByte() != Value[i]) + { + result = false; + break; + } + } + catch + { + result = false; + break; + } + } + + // Return if the expected and actual results match + return result == Result; + } + + /// + /// Run a File test against an input stream + /// + /// Stream to check rule against + /// True if the stream passed, false otherwise + private bool CheckFile(Stream input) + { + // First get the file size from stream + long size = input.Length; + + // If we have a null size, check that the size is a power of 2 + bool result = true; + if (Size == null) + { + // http://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2 + result = (((ulong)size & ((ulong)size - 1)) == 0); + } + else if (Operator == HeaderSkipTestFileOperator.Less) + { + result = (size < Size); + } + else if (Operator == HeaderSkipTestFileOperator.Greater) + { + result = (size > Size); + } + else if (Operator == HeaderSkipTestFileOperator.Equal) + { + result = (size == Size); + } + + // Return if the expected and actual results match + return result == Result; + } + + /// + /// Run an Or test against an input stream + /// + /// Stream to check rule against + /// True if the stream passed, false otherwise + private bool CheckOr(Stream input) + { + // First seek to the correct position + Seek(input); + + bool result = true; + try + { + // Then apply the mask if it exists + byte[] read = new byte[Mask.Length]; + input.Read(read, 0, Mask.Length); + + byte[] masked = new byte[Mask.Length]; + for (int i = 0; i < read.Length; i++) + { + masked[i] = (byte)(read[i] | Mask[i]); + } + + // Finally, compare it against the value + for (int i = 0; i < Value.Length; i++) + { + if (masked[i] != Value[i]) + { + result = false; + break; + } + } + } + catch + { + result = false; + } + + // Return if the expected and actual results match + return result == Result; + } + + /// + /// Run an Xor test against an input stream + /// + /// Stream to check rule against + /// True if the stream passed, false otherwise + private bool CheckXor(Stream input) + { + // First seek to the correct position + Seek(input); + + bool result = true; + try + { + // Then apply the mask if it exists + byte[] read = new byte[Mask.Length]; + input.Read(read, 0, Mask.Length); + + byte[] masked = new byte[Mask.Length]; + for (int i = 0; i < read.Length; i++) + { + masked[i] = (byte)(read[i] ^ Mask[i]); + } + + // Finally, compare it against the value + for (int i = 0; i < Value.Length; i++) + { + if (masked[i] != Value[i]) + { + result = false; + break; + } + } + } + catch + { + result = false; + } + + // Return if the expected and actual results match + return result == Result; + } + + /// + /// Seek an input stream based on the test value + /// + /// Stream to seek + /// True if the stream could seek, false on error + private bool Seek(Stream input) + { + try + { + // Null offset means EOF + if (Offset == null) + input.Seek(0, SeekOrigin.End); + + // Positive offset means from beginning + else if (Offset >= 0 && Offset <= input.Length) + input.Seek(Offset.Value, SeekOrigin.Begin); + + // Negative offset means from end + else if (Offset < 0 && Math.Abs(Offset.Value) <= input.Length) + input.Seek(Offset.Value, SeekOrigin.End); + + return true; + } + catch + { + return false; + } + } + + #endregion } } \ No newline at end of file diff --git a/SabreTools.Library/Skippers/Transform.cs b/SabreTools.Library/Skippers/Transform.cs index 9021c5ca..bac516fc 100644 --- a/SabreTools.Library/Skippers/Transform.cs +++ b/SabreTools.Library/Skippers/Transform.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; @@ -183,7 +182,6 @@ namespace SabreTools.Library.Skippers /// /// Name of the file to be checked /// Name of the skipper to be used, blank to find a matching skipper - /// Logger object for file and console output /// True if the underlying stream should be kept open, false otherwise /// The SkipperRule that matched the file public static SkipperRule GetMatchingRule(Stream input, string skipperName, bool keepOpen = false) @@ -199,154 +197,27 @@ namespace SabreTools.Library.Skippers List tempList = new List(); tempList.AddRange(List); + // Loop through all known SkipperFiles foreach (SkipperFile skipper in tempList) { - // If we're searching for the skipper OR we have a match to an inputted one - if (string.IsNullOrWhiteSpace(skipperName) - || (!string.IsNullOrWhiteSpace(skipper.Name) && skipperName.ToLowerInvariant() == skipper.Name.ToLowerInvariant()) - || (!string.IsNullOrWhiteSpace(skipper.Name) && skipperName.ToLowerInvariant() == skipper.SourceFile.ToLowerInvariant())) - { - // Loop through the rules until one is found that works - BinaryReader br = new BinaryReader(input); - - foreach (SkipperRule rule in skipper.Rules) - { - // Always reset the stream back to the original place - input.Seek(0, SeekOrigin.Begin); - - // For each rule, make sure it passes each test - bool success = true; - foreach (SkipperTest test in rule.Tests) - { - bool result = true; - switch (test.Type) - { - case HeaderSkipTest.Data: - // First seek to the correct position - if (test.Offset == null) - input.Seek(0, SeekOrigin.End); - else if (test.Offset > 0 && test.Offset <= input.Length) - input.Seek((long)test.Offset, SeekOrigin.Begin); - else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= input.Length) - input.Seek((long)test.Offset, SeekOrigin.End); - - // Then read and compare bytewise - result = true; - for (int i = 0; i < test.Value.Length; i++) - { - try - { - if (br.ReadByte() != test.Value[i]) - { - result = false; - break; - } - } - catch - { - result = false; - break; - } - } - - // Return if the expected and actual results match - success &= (result == test.Result); - break; - - case HeaderSkipTest.Or: - case HeaderSkipTest.Xor: - case HeaderSkipTest.And: - // First seek to the correct position - if (test.Offset == null) - input.Seek(0, SeekOrigin.End); - else if (test.Offset > 0 && test.Offset <= input.Length) - input.Seek((long)test.Offset, SeekOrigin.Begin); - else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= input.Length) - input.Seek((long)test.Offset, SeekOrigin.End); - - result = true; - try - { - // Then apply the mask if it exists - byte[] read = br.ReadBytes(test.Mask.Length); - byte[] masked = new byte[test.Mask.Length]; - for (int i = 0; i < read.Length; i++) - { - masked[i] = (byte)(test.Type == HeaderSkipTest.And ? read[i] & test.Mask[i] : - (test.Type == HeaderSkipTest.Or ? read[i] | test.Mask[i] : read[i] ^ test.Mask[i]) - ); - } - - // Finally, compare it against the value - for (int i = 0; i < test.Value.Length; i++) - { - if (masked[i] != test.Value[i]) - { - result = false; - break; - } - } - } - catch - { - result = false; - } - - // Return if the expected and actual results match - success &= (result == test.Result); - break; - - case HeaderSkipTest.File: - // First get the file size from stream - long size = input.Length; - - // If we have a null size, check that the size is a power of 2 - result = true; - if (test.Size == null) - { - // http://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2 - result = (((ulong)size & ((ulong)size - 1)) == 0); - } - else if (test.Operator == HeaderSkipTestFileOperator.Less) - { - result = (size < test.Size); - } - else if (test.Operator == HeaderSkipTestFileOperator.Greater) - { - result = (size > test.Size); - } - else if (test.Operator == HeaderSkipTestFileOperator.Equal) - { - result = (size == test.Size); - } - - // Return if the expected and actual results match - success &= (result == test.Result); - break; - } - } - - // If we still have a success, then return this rule - if (success) - { - // If we're not keeping the stream open, dispose of the binary reader - if (!keepOpen) - input.Dispose(); - - Globals.Logger.User(" Matching rule found!"); - return rule; - } - } - } + skipperRule = skipper.GetMatchingRule(input, skipperName); + if (skipperRule != null) + break; } // If we're not keeping the stream open, dispose of the binary reader if (!keepOpen) input.Dispose(); + // If the SkipperRule is null, make it empty + if (skipperRule == null) + skipperRule = new SkipperRule(); + // If we have a blank rule, inform the user if (skipperRule.Tests == null) Globals.Logger.Verbose("No matching rule found!"); + else + Globals.Logger.User("Matching rule found!"); return skipperRule; }