using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using BinaryObjectScanner.Utilities; namespace BinaryObjectScanner.Wrappers { public abstract class WrapperBase { #region Descriptive Properties /// /// Description of the object /// public abstract string Description { get; } #endregion #region Instance Variables /// /// Source of the original data /// protected DataSource _dataSource = DataSource.UNKNOWN; /// /// Source byte array data /// /// This is only populated if is protected byte[] _byteArrayData = null; /// /// Source byte array data offset /// /// This is only populated if is protected int _byteArrayOffset = -1; /// /// Source Stream data /// /// This is only populated if is protected Stream _streamData = null; #if NET6_0_OR_GREATER /// /// JSON serializer options for output printing /// protected System.Text.Json.JsonSerializerOptions _jsonSerializerOptions { get { var serializer = new System.Text.Json.JsonSerializerOptions { IncludeFields = true, WriteIndented = true }; serializer.Converters.Add(new ConcreteAbstractSerializer()); serializer.Converters.Add(new ConcreteInterfaceSerializer()); serializer.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); return serializer; } } #endif #endregion #region Data /// /// Validate the backing data source /// /// True if the data source is valid, false otherwise protected bool DataSourceIsValid() { switch (_dataSource) { // Byte array data requires both a valid array and offset case DataSource.ByteArray: return _byteArrayData != null && _byteArrayOffset >= 0; // Stream data requires both a valid stream case DataSource.Stream: return _streamData != null && _streamData.CanRead && _streamData.CanSeek; // Everything else is invalid case DataSource.UNKNOWN: default: return false; } } /// /// Check if a data segment is valid in the data source /// /// Position in the source /// Length of the data to check /// True if the positional data is valid, false otherwise protected bool SegmentValid(int position, int length) { // Validate the data souece if (!DataSourceIsValid()) return false; // If we have an invalid position if (position < 0 || position >= GetEndOfFile()) return false; switch (_dataSource) { case DataSource.ByteArray: return _byteArrayOffset + position + length <= _byteArrayData.Length; case DataSource.Stream: return position + length <= _streamData.Length; // Everything else is invalid case DataSource.UNKNOWN: default: return false; } } /// /// Read data from the source /// /// Position in the source to read from /// Length of the requested data /// Byte array containing the requested data, null on error protected byte[] ReadFromDataSource(int position, int length) { // Validate the data source if (!DataSourceIsValid()) return null; // Validate the requested segment if (!SegmentValid(position, length)) return null; // Read and return the data byte[] sectionData = null; switch (_dataSource) { case DataSource.ByteArray: sectionData = new byte[length]; Array.Copy(_byteArrayData, _byteArrayOffset + position, sectionData, 0, length); break; case DataSource.Stream: long currentLocation = _streamData.Position; _streamData.Seek(position, SeekOrigin.Begin); sectionData = _streamData.ReadBytes(length); _streamData.Seek(currentLocation, SeekOrigin.Begin); break; } return sectionData; } /// /// Read string data from the source /// /// Position in the source to read from /// Length of the requested data /// Number of characters needed to be a valid string /// String list containing the requested data, null on error protected List ReadStringsFromDataSource(int position, int length, int charLimit = 5) { // Read the data as a byte array first byte[] sourceData = ReadFromDataSource(position, length); if (sourceData == null) return null; // If we have an invalid character limit, default to 5 if (charLimit <= 0) charLimit = 5; // Create the string list to return var sourceStrings = new List(); // Setup cached data int sourceDataIndex = 0; string cachedString = string.Empty; // Check for ASCII strings while (sourceDataIndex < sourceData.Length) { // If we have a control character or an invalid byte if (sourceData[sourceDataIndex] < 0x20 || sourceData[sourceDataIndex] > 0x7F) { // If we have no cached string if (cachedString.Length == 0) { sourceDataIndex++; continue; } // If we have a cached string greater than the limit if (cachedString.Length >= charLimit) sourceStrings.Add(cachedString); cachedString = string.Empty; sourceDataIndex++; continue; } // All other characters get read in cachedString += Encoding.ASCII.GetString(sourceData, sourceDataIndex, 1); sourceDataIndex++; } // If we have a cached string greater than the limit if (cachedString.Length >= charLimit) sourceStrings.Add(cachedString); // Reset cached data sourceDataIndex = 0; cachedString = string.Empty; // We are limiting the check for Unicode characters with a second byte of 0x00 for now // Check for Unicode strings while (sourceDataIndex < sourceData.Length) { // Unicode characters are always 2 bytes if (sourceDataIndex == sourceData.Length - 1) break; ushort ch = BitConverter.ToUInt16(sourceData, sourceDataIndex); // If we have a null terminator or "invalid" character if (ch == 0x0000 || (ch & 0xFF00) != 0) { // If we have no cached string if (cachedString.Length == 0) { sourceDataIndex += 2; continue; } // If we have a cached string greater than the limit if (cachedString.Length >= charLimit) sourceStrings.Add(cachedString); cachedString = string.Empty; sourceDataIndex += 2; continue; } // All other characters get read in cachedString += Encoding.Unicode.GetString(sourceData, sourceDataIndex, 2); sourceDataIndex += 2; } // If we have a cached string greater than the limit if (cachedString.Length >= charLimit) sourceStrings.Add(cachedString); // Deduplicate the string list for storage sourceStrings = sourceStrings.Distinct().OrderBy(s => s).ToList(); // TODO: Complete implementation of string finding return sourceStrings; } /// /// Get the ending offset of the source /// /// Value greater than 0 for a valid end of file, -1 on error protected int GetEndOfFile() { // Validate the data souece if (!DataSourceIsValid()) return -1; // Return the effective endpoint switch (_dataSource) { case DataSource.ByteArray: return _byteArrayData.Length - _byteArrayOffset; case DataSource.Stream: return (int)_streamData.Length; case DataSource.UNKNOWN: default: return -1; } } #endregion #region Printing /// /// Export the item information as pretty-printed text /// public abstract StringBuilder PrettyPrint(); #if NET6_0_OR_GREATER /// /// Export the item information as JSON /// public abstract string ExportJSON(); #endif #endregion } }