/// <summary> /// Casts the parsed <see cref="IChannelFrame"/> to its specific implementation (i.e., <see cref="DataFrame"/>, <see cref="ConfigurationFrame"/> or <see cref="CommandFrame"/>). /// </summary> /// <param name="frame"><see cref="IChannelFrame"/> that was parsed by <see cref="FrameImageParserBase{TTypeIdentifier,TOutputType}"/> that implements protocol specific common frame header interface.</param> protected override void OnReceivedChannelFrame(IChannelFrame frame) { // Raise abstract channel frame events as a priority (i.e., IDataFrame, IConfigurationFrame, etc.) base.OnReceivedChannelFrame(frame); // Raise IEC 61850-90-5 specific channel frame events, if any have been subscribed if (frame != null && (ReceivedDataFrame != null || ReceivedConfigurationFrame != null || ReceivedCommandFrame != null)) { DataFrame dataFrame = frame as DataFrame; if (dataFrame != null) { if (ReceivedDataFrame != null) { ReceivedDataFrame(this, new EventArgs <DataFrame>(dataFrame)); } } else { ConfigurationFrame configFrame = frame as ConfigurationFrame; if (configFrame != null) { if (ReceivedConfigurationFrame != null) { ReceivedConfigurationFrame(this, new EventArgs <ConfigurationFrame>(configFrame)); } } else { CommandFrame commandFrame = frame as CommandFrame; if (commandFrame != null) { if (ReceivedCommandFrame != null) { ReceivedCommandFrame(this, new EventArgs <CommandFrame>(commandFrame)); } } } } } }
/// <summary> /// Creates a new <see cref="CommonFrameHeader"/> from specified parameters. /// </summary> /// <param name="configurationFrame">IEC 61850-90-5 <see cref="ConfigurationFrame"/> if available.</param> /// <param name="typeID">The IEC 61850-90-5 specific frame type of this frame.</param> /// <param name="idCode">The ID code of this frame.</param> /// <param name="timestamp">The timestamp of this frame.</param> /// <param name="msvID">MSVID to use for this frame, if any.</param> /// <param name="asduCount">ASDU count.</param> /// <param name="configurationRevision">Configuration revision.</param> public CommonFrameHeader(ConfigurationFrame configurationFrame, FrameType typeID, ushort idCode, Ticks timestamp, string msvID = null, int asduCount = 1, uint configurationRevision = 1) { m_frameType = typeID; m_idCode = idCode; m_timestamp = timestamp; m_version = 1; m_timebase = Common.Timebase; m_msvID = msvID; m_asduCount = asduCount; m_configurationRevision = configurationRevision; m_securityAlgorithm = SecurityAlgorithm.None; m_signatureAlgorithm = SignatureAlgorithm.None; if ((object)configurationFrame != null) { // Hang on to configured frame rate and ticks per frame m_framesPerSecond = configurationFrame.FrameRate; m_ticksPerFrame = Ticks.PerSecond / (double)m_framesPerSecond; } }
/// <summary> /// Creates a new <see cref="CommonFrameHeader"/> from given <paramref name="buffer"/>. /// </summary> /// <param name="configurationFrame">IEC 61850-90-5 <see cref="ConfigurationFrame"/> if already parsed.</param> /// <param name="useETRConfiguration">Determines if system should find associated ETR file using MSVID with same name for configuration.</param> /// <param name="guessConfiguration">Determines if system should try to guess at a possible configuration given payload size.</param> /// <param name="parseRedundantASDUs">Determines if system should expose redundantly parsed ASDUs.</param> /// <param name="ignoreSignatureValidationFailures">Determines if system should ignore checksum signature validation errors.</param> /// <param name="ignoreSampleSizeValidationFailures">Determines if system should ignore sample size validation errors.</param> /// <param name="angleFormat">Allows customization of the angle parsing format.</param> /// <param name="buffer">Buffer that contains data to parse.</param> /// <param name="startIndex">Start index into buffer where valid data begins.</param> /// <param name="length">Maximum length of valid data from offset.</param> // ReSharper disable once UnusedParameter.Local public CommonFrameHeader(ConfigurationFrame configurationFrame, bool useETRConfiguration, bool guessConfiguration, bool parseRedundantASDUs, bool ignoreSignatureValidationFailures, bool ignoreSampleSizeValidationFailures, AngleFormat angleFormat, byte[] buffer, int startIndex, int length) { const byte VersionNumberMask = (byte)IEC61850_90_5.FrameType.VersionNumberMask; // Cache behavioral connection parameters m_useETRConfiguration = useETRConfiguration; m_guessConfiguration = guessConfiguration; m_parseRedundantASDUs = parseRedundantASDUs; m_ignoreSignatureValidationFailures = ignoreSignatureValidationFailures; m_ignoreSampleSizeValidationFailures = ignoreSampleSizeValidationFailures; m_angleFormat = angleFormat; // Ignore the time base from configuration frame if available. The timebase is not adjustable for 61850. m_timebase = Common.Timebase; // See if frame is for a common IEEE C37.118 frame (e.g., for configuration or command) if (buffer[startIndex] == PhasorProtocols.Common.SyncByte) { // Strip out frame type and version information... m_frameType = (FrameType)(buffer[startIndex + 1] & ~VersionNumberMask); m_version = (byte)(buffer[startIndex + 1] & VersionNumberMask); m_frameLength = BigEndian.ToUInt16(buffer, startIndex + 2); m_idCode = BigEndian.ToUInt16(buffer, startIndex + 4); uint secondOfCentury = BigEndian.ToUInt32(buffer, startIndex + 6); uint fractionOfSecond = BigEndian.ToUInt32(buffer, startIndex + 10); long ticksBeyondSecond; // Without timebase, the best timestamp you can get is down to the whole second m_timestamp = (new UnixTimeTag((double)secondOfCentury)).ToDateTime().Ticks; // "Actual fractional seconds" are obtained by taking fractionOfSecond and dividing by timebase. // Since we are converting to ticks, we need to multiply by Ticks.PerSecond. // We do the multiplication first so that the whole operation can be done using integer arithmetic. // m_timebase / 2L is added before dividing by timebase in order to round the result. ticksBeyondSecond = (fractionOfSecond & ~Common.TimeQualityFlagsMask) * Ticks.PerSecond; m_timestamp += (ticksBeyondSecond + m_timebase / 2L) / m_timebase; if ((object)configurationFrame != null) { // Hang on to configured frame rate and ticks per frame m_framesPerSecond = configurationFrame.FrameRate; m_ticksPerFrame = Ticks.PerSecond / (double)m_framesPerSecond; } m_timeQualityFlags = fractionOfSecond & Common.TimeQualityFlagsMask; } else if (buffer[startIndex + 1] == Common.CltpTag) { // Make sure there is enough data to parse session header from frame if (length > Common.SessionHeaderSize) { // Manually assign frame type - this is an IEC 61850-90-5 data frame m_frameType = IEC61850_90_5.FrameType.DataFrame; // Calculate CLTP tag length int cltpTagLength = buffer[startIndex] + 1; // Initialize buffer parsing index starting past connectionless transport protocol header int index = startIndex + cltpTagLength; // Start calculating total frame length int frameLength = cltpTagLength; // Get session type (Goose, sampled values, etc.) SessionType sessionType = (SessionType)buffer[index++]; // Make sure session type is sampled values if (sessionType == SessionType.SampledValues) { byte headerSize = buffer[index]; // Make sure header size is standard if (headerSize == Common.SessionHeaderSize) { // Skip common header tag index += 3; // Get SPDU length m_spduLength = BigEndian.ToUInt32(buffer, index); index += 4; // Add SPDU length to total frame length (updated as of 10/3/2012 to accommodate extra 6 bytes) frameLength += (int)m_spduLength + 8; // Make sure full frame of data is available - cannot calculate full frame length needed for check sum // without the entire frame since signature algorithm calculation length varies by type and size if (length > m_spduLength + 13) { // Get SPDU packet number m_packetNumber = BigEndian.ToUInt32(buffer, index); // Get security algorithm type m_securityAlgorithm = (SecurityAlgorithm)buffer[index + 12]; // Get signature algorithm type m_signatureAlgorithm = (SignatureAlgorithm)buffer[index + 13]; // Get current key ID m_keyID = BigEndian.ToUInt32(buffer, index + 14); // Add signature calculation result length to total frame length switch (m_signatureAlgorithm) { case SignatureAlgorithm.None: break; case SignatureAlgorithm.Sha80: frameLength += 11; break; case SignatureAlgorithm.Sha128: case SignatureAlgorithm.Aes128: frameLength += 17; break; case SignatureAlgorithm.Sha256: frameLength += 33; break; case SignatureAlgorithm.Aes64: frameLength += 9; break; default: throw new InvalidOperationException("Invalid IEC 61850-90-5 signature algorithm detected: 0x" + buffer[index].ToString("X").PadLeft(2, '0')); } // Check signature algorithm packet checksum here, this step is skipped in data frame parsing due to non-standard location... if (m_signatureAlgorithm != SignatureAlgorithm.None) { int packetIndex = startIndex + cltpTagLength; int hmacIndex = (int)(packetIndex + m_spduLength + 2); // Check for signature tag if (buffer[hmacIndex++] == 0x85) { // KeyID is technically a lookup into derived rotating keys, but all these are using dummy key for now HMAC hmac = m_signatureAlgorithm <= SignatureAlgorithm.Sha256 ? (HMAC)(new ShaHmac(Common.DummyKey)) : (HMAC)(new AesHmac(Common.DummyKey)); int result = 0; switch (m_signatureAlgorithm) { case SignatureAlgorithm.None: break; case SignatureAlgorithm.Aes64: m_sourceHash = buffer.BlockCopy(hmacIndex, 8); m_calculatedHash = hmac.ComputeHash(buffer, packetIndex, (int)m_spduLength).BlockCopy(0, 8); result = m_sourceHash.CompareTo(0, m_calculatedHash, 0, 8); break; case SignatureAlgorithm.Sha80: m_sourceHash = buffer.BlockCopy(hmacIndex, 10); m_calculatedHash = hmac.ComputeHash(buffer, packetIndex, (int)m_spduLength).BlockCopy(0, 10); result = m_sourceHash.CompareTo(0, m_calculatedHash, 0, 10); break; case SignatureAlgorithm.Sha128: case SignatureAlgorithm.Aes128: m_sourceHash = buffer.BlockCopy(hmacIndex, 16); m_calculatedHash = hmac.ComputeHash(buffer, packetIndex, (int)m_spduLength).BlockCopy(0, 16); result = m_sourceHash.CompareTo(0, m_calculatedHash, 0, 16); break; case SignatureAlgorithm.Sha256: m_sourceHash = buffer.BlockCopy(hmacIndex, 32); m_calculatedHash = hmac.ComputeHash(buffer, packetIndex, (int)m_spduLength).BlockCopy(0, 32); result = m_sourceHash.CompareTo(0, m_calculatedHash, 0, 32); break; default: throw new NotSupportedException(string.Format("IEC 61850-90-5 signature algorithm \"{0}\" is not currently supported: ", m_signatureAlgorithm)); } if (result != 0 && !m_ignoreSignatureValidationFailures) { throw new CrcException("Invalid binary image detected - IEC 61850-90-5 check sum does not match."); } } else { throw new CrcException("Invalid binary image detected - expected IEC 61850-90-5 check sum does not exist."); } } // Get payload length index += 18; m_dataLength = (ushort)BigEndian.ToUInt32(buffer, index); index += 4; // Confirm payload type tag is sampled values if (buffer[index] != 0x82) { throw new InvalidOperationException("Encountered a payload that is not tagged 0x82 for sampled values: 0x" + buffer[index].ToString("X").PadLeft(2, '0')); } index++; // Get simulated bit value m_simulatedData = buffer[index++] != 0; // Get application ID m_applicationID = BigEndian.ToUInt16(buffer, index); index += 2; // Get ASDU payload size m_payloadSize = BigEndian.ToUInt16(buffer, index); index += 2; // Validate sampled value PDU tag exists and skip past it buffer.ValidateTag(SampledValueTag.SvPdu, ref index); // Parse number of ASDUs tag m_asduCount = buffer.ParseByteTag(SampledValueTag.AsduCount, ref index); if (m_asduCount == 0) { throw new InvalidOperationException("Total number of ADSUs must be greater than zero."); } // Validate sequence of ASDU tag exists and skip past it buffer.ValidateTag(SampledValueTag.SequenceOfAsdu, ref index); // Set header length m_headerLength = (ushort)(index - startIndex); // Set calculated frame length m_frameLength = (ushort)frameLength; } } else { throw new InvalidOperationException("Bad data stream, encountered an invalid session header size: " + headerSize); } } else { throw new InvalidOperationException(string.Format("This library can only parse IEC 61850-90-5 sampled value sessions, type \"{0}\" is not supported.", sessionType)); } } } else { throw new InvalidOperationException("Bad data stream, expected sync byte 0xAA or 0x01 as first byte in IEC 61850-90-5 frame, got 0x" + buffer[startIndex].ToString("X").PadLeft(2, '0')); } }
// Static Methods // Attempts to cast given frame into an IEC 61850-90-5 configuration frame - theoretically this will // allow the same configuration frame to be used for any protocol implementation internal static ConfigurationFrame CastToDerivedConfigurationFrame(IConfigurationFrame sourceFrame) { // See if frame is already an IEC 61850-90-5 configuration frame (if so, we don't need to do any work) ConfigurationFrame derivedFrame = sourceFrame as ConfigurationFrame; if (derivedFrame == null) { // Create a new IEC 61850-90-5 configuration frame converted from equivalent configuration information ConfigurationCell derivedCell; IFrequencyDefinition sourceFrequency; // Assuming timebase = 16777216 derivedFrame = new ConfigurationFrame(Common.Timebase, sourceFrame.IDCode, sourceFrame.Timestamp, sourceFrame.FrameRate); foreach (IConfigurationCell sourceCell in sourceFrame.Cells) { // Create new derived configuration cell derivedCell = new ConfigurationCell(derivedFrame, sourceCell.IDCode, sourceCell.NominalFrequency); string stationName = sourceCell.StationName; string idLabel = sourceCell.IDLabel; if (!string.IsNullOrWhiteSpace(stationName)) { derivedCell.StationName = stationName.TruncateLeft(derivedCell.MaximumStationNameLength); } if (!string.IsNullOrWhiteSpace(idLabel)) { derivedCell.IDLabel = idLabel.TruncateLeft(derivedCell.IDLabelLength); } // Create equivalent derived phasor definitions foreach (IPhasorDefinition sourcePhasor in sourceCell.PhasorDefinitions) { derivedCell.PhasorDefinitions.Add(new PhasorDefinition(derivedCell, sourcePhasor.Label, sourcePhasor.ScalingValue, sourcePhasor.Offset, sourcePhasor.PhasorType, null)); } // Create equivalent derived frequency definition sourceFrequency = sourceCell.FrequencyDefinition; if (sourceFrequency != null) { derivedCell.FrequencyDefinition = new FrequencyDefinition(derivedCell, sourceFrequency.Label); } // Create equivalent derived analog definitions (assuming analog type = SinglePointOnWave) foreach (IAnalogDefinition sourceAnalog in sourceCell.AnalogDefinitions) { derivedCell.AnalogDefinitions.Add(new AnalogDefinition(derivedCell, sourceAnalog.Label, sourceAnalog.ScalingValue, sourceAnalog.Offset, sourceAnalog.AnalogType)); } // Create equivalent derived digital definitions foreach (IDigitalDefinition sourceDigital in sourceCell.DigitalDefinitions) { derivedCell.DigitalDefinitions.Add(new DigitalDefinition(derivedCell, sourceDigital.Label, 0, 0)); } // Add cell to frame derivedFrame.Cells.Add(derivedCell); } } return(derivedFrame); }
/// <summary> /// Creates a new <see cref="ConfigurationCell"/> from specified parameters. /// </summary> /// <param name="parent">The reference to parent <see cref="ConfigurationFrame"/> of this <see cref="ConfigurationCell"/>.</param> /// <param name="idCode">The numeric ID code for this <see cref="ConfigurationCell"/>.</param> /// <param name="nominalFrequency">The nominal <see cref="LineFrequency"/> of the <see cref="FrequencyDefinition"/> of this <see cref="ConfigurationCell"/>.</param> public ConfigurationCell(ConfigurationFrame parent, ushort idCode, LineFrequency nominalFrequency) : this(parent) { IDCode = idCode; NominalFrequency = nominalFrequency; }