using System; using System.IO; using System.Xml; using System.Xml.Serialization; namespace SabreTools.Skippers { /// /// Individual test that applies to a SkipperRule /// public abstract class SkipperTest { #region Fields /// /// File offset to run the test /// /// null is EOF [XmlAttribute("offset")] public long? Offset { get; set; } /// /// Static value to be checked at the offset /// [XmlAttribute("value")] public byte[] Value { get; set; } /// /// Determines whether a pass or failure is expected /// [XmlAttribute("result")] public bool Result { get; set; } /// /// Byte mask to be applied to the tested bytes /// [XmlAttribute("mask")] public byte[] Mask { get; set; } /// /// Expected size of the input byte array, used with the Operator /// [XmlAttribute("size")] public long? Size { get; set; } // null is PO2, "power of 2" filesize /// /// Expected range value for the input byte array size, used with Size /// [XmlAttribute("operator")] 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 abstract bool Passes(Stream input); #region Checking Helpers /// /// Seek an input stream based on the test value /// /// Stream to seek /// True if the stream could seek, false on error protected 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 } /// /// Skipper test using AND /// [XmlType("and")] public class AndSkipperTest : SkipperTest { /// public override bool Passes(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; } } /// /// Skipper test using DATA /// [XmlType("data")] public class DataSkipperTest : SkipperTest { /// public override bool Passes(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; } } /// /// Skipper test using FILE /// [XmlType("file")] public class FileSkipperTest : SkipperTest { /// public override bool Passes(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; } } /// /// Skipper test using OR /// [XmlType("or")] public class OrSkipperTest : SkipperTest { /// public override bool Passes(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; } } /// /// Skipper test using XOR /// [XmlType("xor")] public class XorSkipperTest : SkipperTest { /// public override bool Passes(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; } } }