//****************************************************************************************************** // PhysicalParser.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 05/03/2012 - Stephen C. Wills, Grid Protection Alliance // Generated original version of source code. // 12/17/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Text; using GSF.IO.Checksums; using Ionic.Zlib; namespace GSF.PQDIF.Physical { #region [ Enumerations ] /// /// Enumeration which defines the types of compression used in PQDIF files. /// public enum CompressionStyle : uint { /// /// No compression. /// None = 0, /// /// Compress the entire file after the container record. /// This compression style is deprecated and is currently /// not supported by this PQDIF library. /// TotalFile = 1, /// /// Compress the body of each record. /// RecordLevel = 2 } /// /// Enumeration which defines the algorithms used to compress PQDIF files. /// public enum CompressionAlgorithm : uint { /// /// No compression. /// None = 0, /// /// Zlib compression. /// http://www.zlib.net/ /// Zlib = 1, /// /// PKZIP compression. /// This compression algorithm is deprecated and /// is currently not supported by this PQDIF library. /// PKZIP = 64 } #endregion /// /// Represents a parser which parses the physical structure of a PQDIF file. /// /// /// /// This class is used internally by the to read the /// physical structure of the PQDIF file. If your goal is to read data from a PQD file into /// an application, you probably do not want to instantiate the physical parser directly. /// Instead, the logical parser will apply the rules governing the logical structure of the PQDIF /// file to make the data more readily usable by an application that will be processing that data. /// /// /// /// The following example of usage was adapted from the PQDIFDump utility, which represents a /// bare minimum level of effort to read an uncompressed PQDIF file and display its contents /// in a console application. /// /// /// /// string fileName = args[0]; /// /// using (PhysicalParser parser = new PhysicalParser(fileName)) /// { /// parser.Open(); /// /// while (parser.HasNextRecord()) /// { /// Record record = parser.NextRecord(); /// Console.WriteLine(record); /// Console.WriteLine(); /// } /// } /// /// public class PhysicalParser : IDisposable { #region [ Members ] // Nested Types private class UnknownElement : Element { private ElementType m_typeOfElement; public UnknownElement(ElementType typeOfElement) { m_typeOfElement = typeOfElement; } public override ElementType TypeOfElement => m_typeOfElement; } private BinaryReader m_fileReader; private CompressionStyle m_compressionStyle; private CompressionAlgorithm m_compressionAlgorithm; private bool m_hasNextRecord; private HashSet m_headerAddresses; private List m_exceptionList; private int m_maximumExceptionsAllowed = 100; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// /// Path to the PQDIF file to be parsed. public PhysicalParser(string filePath) : this() { FilePath = filePath; } /// /// Creates a new instance of the class. /// /// The stream containing the PQDIF file data. /// True to leave the stream open when closing the parser; otherwise false. /// is null. /// is not both readable and seekable. public PhysicalParser(Stream stream, bool leaveOpen = false) : this() { Open(stream, leaveOpen); } private PhysicalParser() { m_headerAddresses = new HashSet(); m_exceptionList = new List(); } #endregion #region [ Properties ] /// /// Gets or sets the file path (not just the name) of the PQDIF file to be parsed. /// Obsolete in favor of . /// [Obsolete("Property is deprecated. Please use FilePath instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public string FileName { get { return FilePath; } set { FilePath = value; } } /// /// Gets or sets the file path of the PQDIF file to be parsed. /// public string FilePath { get; set; } /// /// Gets all the exceptions encountered while parsing. /// public List ExceptionList { get { return m_exceptionList; } } /// /// Gets or sets the compression style used by the PQDIF file. /// /// compression has been deprecated by the standard and is not supported by this parser. public CompressionStyle CompressionStyle { get { return m_compressionStyle; } set { if (value == CompressionStyle.TotalFile) throw new NotSupportedException("Total file compression has been deprecated and is not supported"); m_compressionStyle = value; } } /// /// Gets or sets the compression algorithm used by the PQDIF file. /// /// compression has been deprecated by the standard and is not supported by this parser. public CompressionAlgorithm CompressionAlgorithm { get { return m_compressionAlgorithm; } set { if (value == CompressionAlgorithm.PKZIP) throw new NotSupportedException("PKZIP compression has been deprecated and is not supported"); m_compressionAlgorithm = value; } } /// /// Gets or sets the maximum number of exceptions /// in the exception list before parser will quit. /// /// Enter a negative value to disable this safeguard. public int MaximumExceptionsAllowed { get { return m_maximumExceptionsAllowed; } set { m_maximumExceptionsAllowed = value; } } /// /// Gets a value that indicates whether the maximum number of exceptions has been reached. /// public bool MaximumExceptionsReached { get { return (m_maximumExceptionsAllowed >= 0) && (m_exceptionList.Count > m_maximumExceptionsAllowed); } } #endregion #region [ Methods ] /// /// Opens the PQDIF file. /// /// has not been defined. public void Open() { if ((object)FilePath == null) throw new InvalidOperationException("Unable to open PQDIF file when no file name has been defined."); using (m_fileReader) { m_fileReader = new BinaryReader(File.OpenRead(FilePath)); } m_hasNextRecord = true; } /// /// Opens a PQDIF file from a stream of data. /// /// The stream from which to read the PQDIF file. /// True to leave the stream open when closing the parser; false otherwise. /// is null. /// is not both readable and seekable. public void Open(Stream stream, bool leaveOpen = false) { if ((object)stream == null) throw new ArgumentNullException(nameof(stream)); if (!stream.CanRead) throw new InvalidOperationException("Stream must be readable in order to parse PQDIF file data."); if (!stream.CanSeek) throw new InvalidOperationException("Stream must be seekable in order to parse PQDIF file data."); using (m_fileReader) { m_fileReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen); } m_hasNextRecord = true; } /// /// Returns true if this parser has not reached the end of the PQDIF file. /// /// false if the end of the file has been reached; true otherwise public bool HasNextRecord() { return m_hasNextRecord; } /// /// Reads the next record from the PQDIF file. /// /// The next record to be parsed from the PQDIF file. public Record NextRecord() { RecordHeader header; RecordBody body; if (!m_hasNextRecord) Reset(); header = ReadRecordHeader(); body = ReadRecordBody(header.BodySize); if ((object)body != null) body.Collection.TagOfElement = header.RecordTypeTag; m_hasNextRecord = header.NextRecordPosition > 0 && header.NextRecordPosition < m_fileReader.BaseStream.Length && m_headerAddresses.Add(header.NextRecordPosition) && !MaximumExceptionsReached; m_fileReader.BaseStream.Seek(header.NextRecordPosition, SeekOrigin.Begin); return new Record(header, body); } /// /// Jumps in the file to the location of the record with the given header. /// /// The header to seek to. public void Seek(RecordHeader header) { m_fileReader.BaseStream.Seek(header.Position, SeekOrigin.Begin); } /// /// Sets the parser back to the beginning of the file. /// public void Reset() { m_compressionAlgorithm = CompressionAlgorithm.None; m_compressionStyle = CompressionStyle.None; m_fileReader.BaseStream.Seek(0, SeekOrigin.Begin); m_hasNextRecord = true; m_headerAddresses.Clear(); m_exceptionList.Clear(); } /// /// Closes the PQDIF file. /// public void Close() { m_fileReader.Close(); m_hasNextRecord = false; } /// /// Releases all resources held by this parser. /// public void Dispose() { if ((object)m_fileReader != null) { m_fileReader.Dispose(); m_fileReader = null; } m_hasNextRecord = false; } // Reads the header of a record from the PQDIF file. private RecordHeader ReadRecordHeader() { return new RecordHeader() { Position = (int)m_fileReader.BaseStream.Position, RecordSignature = new Guid(m_fileReader.ReadBytes(16)), RecordTypeTag = new Guid(m_fileReader.ReadBytes(16)), HeaderSize = m_fileReader.ReadInt32(), BodySize = m_fileReader.ReadInt32(), NextRecordPosition = m_fileReader.ReadInt32(), Checksum = m_fileReader.ReadUInt32(), Reserved = m_fileReader.ReadBytes(16) }; } // Reads the body of a record from the PQDIF file. private RecordBody ReadRecordBody(int byteSize) { byte[] bytes; Adler32 checksum; if (byteSize == 0) return null; bytes = m_fileReader.ReadBytes(byteSize); checksum = new Adler32(); checksum.Update(bytes); if (m_compressionAlgorithm == CompressionAlgorithm.Zlib && m_compressionStyle != CompressionStyle.None) bytes = ZlibStream.UncompressBuffer(bytes); using (MemoryStream stream = new MemoryStream(bytes)) using (BinaryReader reader = new BinaryReader(stream)) { return new RecordBody() { Collection = ReadCollection(reader), Checksum = checksum.Value }; } } // Reads an element from the PQDIF file. private Element ReadElement(BinaryReader recordBodyReader) { Element element; Guid tagOfElement = Guid.Empty; ElementType typeOfElement = 0; PhysicalType typeOfValue = 0; bool isEmbedded; // Calculate the location of the next element // after this one in case we encounter any errors long nextLink = recordBodyReader.BaseStream.Position + 28L; try { tagOfElement = new Guid(recordBodyReader.ReadBytes(16)); typeOfElement = (ElementType)recordBodyReader.ReadByte(); typeOfValue = (PhysicalType)recordBodyReader.ReadByte(); isEmbedded = recordBodyReader.ReadByte() != 0; // Read reserved byte recordBodyReader.ReadByte(); long link; long returnLink; returnLink = recordBodyReader.BaseStream.Position + 8L; if (!isEmbedded || typeOfElement != ElementType.Scalar) { link = recordBodyReader.ReadInt32(); if (link < 0 || link >= recordBodyReader.BaseStream.Length) throw new InvalidOperationException("Element link is outside the bounds of the file"); recordBodyReader.BaseStream.Seek(link, SeekOrigin.Begin); } switch (typeOfElement) { case ElementType.Collection: element = ReadCollection(recordBodyReader); break; case ElementType.Scalar: element = ReadScalar(recordBodyReader, typeOfValue); break; case ElementType.Vector: element = ReadVector(recordBodyReader, typeOfValue); break; default: element = new UnknownElement(typeOfElement); element.TypeOfValue = typeOfValue; break; } element.TagOfElement = tagOfElement; recordBodyReader.BaseStream.Seek(returnLink, SeekOrigin.Begin); return element; } catch (Exception ex) { m_exceptionList.Add(ex); // Jump to the location of the next element after this one if (nextLink < recordBodyReader.BaseStream.Length) recordBodyReader.BaseStream.Seek(nextLink, SeekOrigin.Begin); else recordBodyReader.BaseStream.Seek(0L, SeekOrigin.End); return new ErrorElement(typeOfElement, ex) { TagOfElement = tagOfElement, TypeOfValue = typeOfValue }; } } // Reads a collection element from the PQDIF file. private CollectionElement ReadCollection(BinaryReader recordBodyReader) { int size = recordBodyReader.ReadInt32(); CollectionElement collection = new CollectionElement(); collection.ReadSize = size; for (int i = 0; i < size; i++) { collection.AddElement(ReadElement(recordBodyReader)); if (recordBodyReader.BaseStream.Position >= recordBodyReader.BaseStream.Length || MaximumExceptionsReached) break; } return collection; } // Reads a vector element from the PQDIF file. private VectorElement ReadVector(BinaryReader recordBodyReader, PhysicalType typeOfValue) { VectorElement element = new VectorElement() { Size = recordBodyReader.ReadInt32(), TypeOfValue = typeOfValue }; byte[] values = recordBodyReader.ReadBytes(element.Size * typeOfValue.GetByteSize()); element.SetValues(values, 0); return element; } // Reads a scalar element from the PQDIF file. private ScalarElement ReadScalar(BinaryReader recordBodyReader, PhysicalType typeOfValue) { ScalarElement element = new ScalarElement() { TypeOfValue = typeOfValue }; byte[] value = recordBodyReader.ReadBytes(typeOfValue.GetByteSize()); element.SetValue(value, 0); return element; } #endregion } }