/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Microsoft.Extensions.Logging; using Opc.Ua.PubSub.PublishedData; namespace Opc.Ua.PubSub.Encoding { /// /// The UADPDataSetMessage class handler. /// It handles the UADPDataSetMessage encoding /// public class UadpDataSetMessage : UaDataSetMessage { /// /// Validation masks /// private const byte kFieldTypeUsedBits = 0x06; private const DataSetFlags1EncodingMask kPreservedDataSetFlags1UsedBits = (DataSetFlags1EncodingMask)0x07; private const DataSetFlags1EncodingMask kDataSetFlags1UsedBits = DataSetFlags1EncodingMask.MessageIsValid | DataSetFlags1EncodingMask.SequenceNumber | DataSetFlags1EncodingMask.Status | DataSetFlags1EncodingMask.ConfigurationVersionMajorVersion | DataSetFlags1EncodingMask.ConfigurationVersionMinorVersion | DataSetFlags1EncodingMask.DataSetFlags2; /// /// Constructor for /// public UadpDataSetMessage(ILogger? logger = null) : this(null!, logger) { } /// /// Constructor for /// public UadpDataSetMessage(DataSet dataSet, ILogger? logger = null) : base(logger!) { // If this bit is set to false, the rest of this DataSetMessage is considered invalid, and shall not be processed by the Subscriber. DataSetFlags1 |= DataSetFlags1EncodingMask.MessageIsValid; DataSet = dataSet; } /// /// Get UadpDataSetMessageContentMask /// The DataSetWriterMessageContentMask defines the flags for the content of the DataSetMessage header. /// The UADP message mapping specific flags are defined by the UadpDataSetMessageContentMask DataType. /// public UadpDataSetMessageContentMask DataSetMessageContentMask { get; private set; } /// /// Get DataSetFlags1 /// public DataSetFlags1EncodingMask DataSetFlags1 { get; private set; } /// /// Get DataSetFlags2 /// public DataSetFlags2EncodingMask DataSetFlags2 { get; private set; } /// /// Get and set the ConfiguredSize of this /// public ushort ConfiguredSize { get; set; } /// /// Get and set the DataSetOffset of this /// public ushort DataSetOffset { get; set; } /// /// Get and Set Pico seconds /// public ushort PicoSeconds { get; set; } /// /// Get and Set Decoded payload size (hold it here for now) /// public ushort PayloadSizeInStream { get; set; } /// /// Get and Set the startPosition in decoder /// public int StartPositionInStream { get; set; } /// /// Set DataSetFieldContentMask /// /// The new for this dataset public override void SetFieldContentMask(DataSetFieldContentMask fieldContentMask) { FieldContentMask = fieldContentMask; DataSetFlags1 &= kDataSetFlags1UsedBits; FieldTypeEncodingMask fieldType = FieldTypeEncodingMask.Reserved; if (FieldContentMask == DataSetFieldContentMask.None) { // 00 Variant Field Encoding fieldType = FieldTypeEncodingMask.Variant; } else if (((int)FieldContentMask & (int)DataSetFieldContentMask.RawData) != 0) { // 01 RawData Field Encoding fieldType = FieldTypeEncodingMask.RawData; } else if (((int)FieldContentMask & ((int)DataSetFieldContentMask.StatusCode | (int)DataSetFieldContentMask.SourceTimestamp | (int)DataSetFieldContentMask.ServerTimestamp | (int)DataSetFieldContentMask.SourcePicoSeconds | (int)DataSetFieldContentMask.ServerPicoSeconds)) != 0) { // 10 DataValue Field Encoding fieldType = FieldTypeEncodingMask.DataValue; } DataSetFlags1 |= (DataSetFlags1EncodingMask)((byte)fieldType << 1); } /// /// Set MessageContentMask /// public void SetMessageContentMask(UadpDataSetMessageContentMask messageContentMask) { DataSetMessageContentMask = messageContentMask; DataSetFlags1 &= kPreservedDataSetFlags1UsedBits; DataSetFlags2 = 0; if ((DataSetMessageContentMask & UadpDataSetMessageContentMask.SequenceNumber) != 0) { DataSetFlags1 |= DataSetFlags1EncodingMask.SequenceNumber; } if ((DataSetMessageContentMask & UadpDataSetMessageContentMask.Status) != 0) { DataSetFlags1 |= DataSetFlags1EncodingMask.Status; } if ((DataSetMessageContentMask & UadpDataSetMessageContentMask.MajorVersion) != 0) { DataSetFlags1 |= DataSetFlags1EncodingMask.ConfigurationVersionMajorVersion; } if ((DataSetMessageContentMask & UadpDataSetMessageContentMask.MinorVersion) != 0) { DataSetFlags1 |= DataSetFlags1EncodingMask.ConfigurationVersionMinorVersion; } // Bit range 0-3: UADP DataSetMessage type // 0000 Data Key Frame (by default for now) // 0001 Data Delta Frame // 0010 Event // 0011 Keep Alive if (DataSet != null && DataSet.IsDeltaFrame) { DataSetFlags1 |= DataSetFlags1EncodingMask.DataSetFlags2; DataSetFlags2 |= DataSetFlags2EncodingMask.DataDeltaFrame; } //Always Key frame is sent. if ((DataSetMessageContentMask & UadpDataSetMessageContentMask.Timestamp) != 0) { DataSetFlags1 |= DataSetFlags1EncodingMask.DataSetFlags2; DataSetFlags2 |= DataSetFlags2EncodingMask.Timestamp; } if ((DataSetMessageContentMask & UadpDataSetMessageContentMask.PicoSeconds) != 0) { DataSetFlags1 |= DataSetFlags1EncodingMask.DataSetFlags2; DataSetFlags2 |= DataSetFlags2EncodingMask.PicoSeconds; } } /// /// Encode dataset /// public void Encode(BinaryEncoder binaryEncoder) { StartPositionInStream = binaryEncoder.Position; if (DataSetOffset > 0 && StartPositionInStream < DataSetOffset) { StartPositionInStream = DataSetOffset; binaryEncoder.Position = DataSetOffset; } EncodeDataSetMessageHeader(binaryEncoder); if ((DataSetFlags2 & DataSetFlags2EncodingMask.DataDeltaFrame) == DataSetFlags2EncodingMask.DataDeltaFrame) { EncodeMessageDataDeltaFrame(binaryEncoder); } else { EncodeMessageDataKeyFrame(binaryEncoder); } PayloadSizeInStream = (ushort)(binaryEncoder.Position - StartPositionInStream); if (ConfiguredSize > 0 && PayloadSizeInStream < ConfiguredSize) { PayloadSizeInStream = ConfiguredSize; binaryEncoder.Position = StartPositionInStream + PayloadSizeInStream; } } /// /// Attempt to Decode dataset /// public void DecodePossibleDataSetReader( BinaryDecoder binaryDecoder, DataSetReaderDataType dataSetReader) { if (ExtensionObject.ToEncodeable(dataSetReader.MessageSettings) is UadpDataSetReaderMessageDataType messageSettings) { //StartPositionInStream is calculated but different from reader configuration dataset cannot be decoded if (StartPositionInStream != messageSettings.DataSetOffset) { if (StartPositionInStream == 0) { //use configured offset from reader StartPositionInStream = messageSettings.DataSetOffset; } else if (messageSettings.DataSetOffset != 0) { //configuration is different from real position in message, the dataset cannot be decoded return; } } else { StartPositionInStream = (int)binaryDecoder.BaseStream.Position; } } if (binaryDecoder.BaseStream.Length <= StartPositionInStream) { return; } binaryDecoder.BaseStream.Position = StartPositionInStream; DecodeDataSetMessageHeader(binaryDecoder); DecodeErrorReason = ValidateMetadataVersion( dataSetReader.DataSetMetaData.ConfigurationVersion); if (!IsMetadataMajorVersionChange) { if ((DataSetFlags2 & DataSetFlags2EncodingMask.DataDeltaFrame) == DataSetFlags2EncodingMask.DataDeltaFrame) { DataSet = DecodeMessageDataDeltaFrame(binaryDecoder, dataSetReader)!; } else { DataSet = DecodeMessageDataKeyFrame(binaryDecoder, dataSetReader)!; } } } /// /// Encode DataSet message header /// private void EncodeDataSetMessageHeader(BinaryEncoder encoder) { if ((DataSetFlags1 & DataSetFlags1EncodingMask.MessageIsValid) != 0) { encoder.WriteByte("DataSetFlags1", (byte)DataSetFlags1); } if ((DataSetFlags1 & DataSetFlags1EncodingMask.DataSetFlags2) != 0) { encoder.WriteByte("DataSetFlags2", (byte)DataSetFlags2); } if ((DataSetFlags1 & DataSetFlags1EncodingMask.SequenceNumber) != 0) { encoder.WriteUInt16("SequenceNumber", (ushort)SequenceNumber); } if ((DataSetFlags2 & DataSetFlags2EncodingMask.Timestamp) != 0) { encoder.WriteDateTime("Timestamp", Timestamp); } if ((DataSetFlags2 & DataSetFlags2EncodingMask.PicoSeconds) != 0) { encoder.WriteUInt16("Picoseconds", PicoSeconds); } if ((DataSetFlags1 & DataSetFlags1EncodingMask.Status) != 0) { // This is the high order 16 bits of the StatusCode DataType representing // the numeric value of the Severity and SubCode of the StatusCode DataType. encoder.WriteUInt16("Status", (ushort)(Status.Code >> 16)); } if ((DataSetFlags1 & DataSetFlags1EncodingMask.ConfigurationVersionMajorVersion) != 0) { encoder.WriteUInt32("ConfigurationMajorVersion", MetaDataVersion.MajorVersion); } if ((DataSetFlags1 & DataSetFlags1EncodingMask.ConfigurationVersionMinorVersion) != 0) { encoder.WriteUInt32("ConfigurationMinorVersion", MetaDataVersion.MinorVersion); } } /// /// Encode payload data /// /// private void EncodeMessageDataKeyFrame(BinaryEncoder binaryEncoder) { var fieldType = (FieldTypeEncodingMask)(((byte)DataSetFlags1 & kFieldTypeUsedBits) >> 1); switch (fieldType) { case FieldTypeEncodingMask.Variant: binaryEncoder.WriteUInt16("DataSetFieldCount", (ushort)DataSet.Fields!.Length); foreach (Field field in DataSet.Fields) { // 00 Variant type binaryEncoder.WriteVariant("Variant", field.Value.WrappedValue); } break; case FieldTypeEncodingMask.DataValue: binaryEncoder.WriteUInt16("DataSetFieldCount", (ushort)DataSet.Fields!.Length); foreach (Field field in DataSet.Fields) { // 10 DataValue type binaryEncoder.WriteDataValue("DataValue", field.Value); } break; case FieldTypeEncodingMask.RawData: // DataSetFieldCount is not persisted for RawData foreach (Field field in DataSet.Fields!) { EncodeFieldAsRawData(binaryEncoder, field, CultureInfo.InvariantCulture); } break; case FieldTypeEncodingMask.Reserved: // ignore break; default: throw ServiceResultException.Unexpected( $"Unexpected FieldDataTypeEncodingMask {fieldType}"); } } /// /// Encode payload data delta frame /// /// private void EncodeMessageDataDeltaFrame(BinaryEncoder binaryEncoder) { // calculate the number of fields that will be written int fieldCount = DataSet.Fields!.Count(f => f != null); // The field count is written for RadData encoding too unlike for KeyFrame message binaryEncoder.WriteUInt16("FieldCount", (ushort)fieldCount); var fieldType = (FieldTypeEncodingMask)(((byte)DataSetFlags1 & kFieldTypeUsedBits) >> 1); for (int i = 0; i < DataSet.Fields!.Length; i++) { Field field = DataSet.Fields[i]; if (field == null) { continue; // ignore null fields } // write field index binaryEncoder.WriteUInt16("FieldIndex", (ushort)i); switch (fieldType) { case FieldTypeEncodingMask.Variant: // 00 Variant type binaryEncoder.WriteVariant("FieldValue", field.Value.WrappedValue); break; case FieldTypeEncodingMask.DataValue: // 10 DataValue type binaryEncoder.WriteDataValue("FieldValue", field.Value); break; case FieldTypeEncodingMask.RawData: EncodeFieldAsRawData(binaryEncoder, field, CultureInfo.InvariantCulture); break; case FieldTypeEncodingMask.Reserved: // ignore break; default: throw ServiceResultException.Unexpected( $"Unexpected FieldDataTypeEncodingMask {fieldType}"); } } } /// /// Decode DataSet message header /// private void DecodeDataSetMessageHeader(BinaryDecoder decoder) { if ((DataSetFlags1 & DataSetFlags1EncodingMask.MessageIsValid) != 0) { DataSetFlags1 = (DataSetFlags1EncodingMask)decoder.ReadByte("DataSetFlags1"); } if ((DataSetFlags1 & DataSetFlags1EncodingMask.DataSetFlags2) != 0) { DataSetFlags2 = (DataSetFlags2EncodingMask)decoder.ReadByte("DataSetFlags2"); } if ((DataSetFlags1 & DataSetFlags1EncodingMask.SequenceNumber) != 0) { SequenceNumber = decoder.ReadUInt16("SequenceNumber"); } if ((DataSetFlags2 & DataSetFlags2EncodingMask.Timestamp) != 0) { Timestamp = decoder.ReadDateTime("Timestamp"); } if ((DataSetFlags2 & DataSetFlags2EncodingMask.PicoSeconds) != 0) { PicoSeconds = decoder.ReadUInt16("Picoseconds"); } if ((DataSetFlags1 & DataSetFlags1EncodingMask.Status) != 0) { // This is the high order 16 bits of the StatusCode DataType representing // the numeric value of the Severity and SubCode of the StatusCode DataType. ushort code = decoder.ReadUInt16("Status"); Status = ((uint)code) << 16; } uint minorVersion = kDefaultConfigMinorVersion; uint majorVersion = kDefaultConfigMajorVersion; if ((DataSetFlags1 & DataSetFlags1EncodingMask.ConfigurationVersionMajorVersion) != 0) { majorVersion = decoder.ReadUInt32("ConfigurationMajorVersion"); } if ((DataSetFlags1 & DataSetFlags1EncodingMask.ConfigurationVersionMinorVersion) != 0) { minorVersion = decoder.ReadUInt32("ConfigurationMinorVersion"); } MetaDataVersion = new ConfigurationVersionDataType { MinorVersion = minorVersion, MajorVersion = majorVersion }; } /// /// Decode field message data key frame from decoder and using a DataSetReader /// /// private DataSet? DecodeMessageDataKeyFrame( BinaryDecoder binaryDecoder, DataSetReaderDataType dataSetReader) { DataSetMetaDataType dataSetMetaData = dataSetReader.DataSetMetaData; try { ushort fieldCount = 0; var fieldType = (FieldTypeEncodingMask)(((byte)DataSetFlags1 & kFieldTypeUsedBits) >> 1); if (fieldType == FieldTypeEncodingMask.RawData) { if (dataSetMetaData != null) { // metadata should provide field count fieldCount = (ushort)dataSetMetaData.Fields.Count; } } else { fieldCount = binaryDecoder.ReadUInt16("DataSetFieldCount"); } // check configuration version var dataValues = new List(); switch (fieldType) { case FieldTypeEncodingMask.Variant: for (int i = 0; i < fieldCount; i++) { dataValues.Add(new DataValue(binaryDecoder.ReadVariant("Variant"))); } break; case FieldTypeEncodingMask.DataValue: for (int i = 0; i < fieldCount; i++) { dataValues.Add(binaryDecoder.ReadDataValue("DataValue")!); } break; case FieldTypeEncodingMask.RawData: if (dataSetMetaData != null) { for (int i = 0; i < fieldCount; i++) { FieldMetaData fieldMetaData = dataSetMetaData.Fields[i]; if (fieldMetaData != null) { object? decodedValue = DecodeRawData( binaryDecoder, fieldMetaData); #pragma warning disable CS0618 // Type or member is obsolete dataValues.Add(new DataValue(new Variant(decodedValue!))); #pragma warning restore CS0618 // Type or member is obsolete } } } // else the decoding is compromised for RawData type break; case FieldTypeEncodingMask.Reserved: // ignore break; default: throw ServiceResultException.Unexpected( $"Unexpected FieldDataTypeEncodingMask {fieldType}"); } var dataFields = new List(); for (int i = 0; i < dataValues.Count; i++) { var dataField = new Field { FieldMetaData = dataSetMetaData?.Fields[i], Value = dataValues[i] }; if (ExtensionObject.ToEncodeable(dataSetReader.SubscribedDataSet) is TargetVariablesDataType targetVariablesData && i < targetVariablesData.TargetVariables.Count) { // remember the target Attribute and target nodeId dataField.TargetAttribute = targetVariablesData.TargetVariables[i] .AttributeId; dataField.TargetNodeId = targetVariablesData.TargetVariables[i] .TargetNodeId; } dataFields.Add(dataField); } if (dataFields.Count == 0) { return null; //the dataset cannot be decoded } return new DataSet(dataSetMetaData?.Name) { DataSetMetaData = dataSetMetaData, Fields = [.. dataFields], DataSetWriterId = DataSetWriterId, SequenceNumber = SequenceNumber }; } catch (Exception ex) { m_logger.LogError(ex, "UadpDataSetMessage.DecodeMessageDataKeyFrame"); return null; } } /// /// Decode field message data delta frame from decoder and using a DataSetReader /// /// private DataSet? DecodeMessageDataDeltaFrame( BinaryDecoder binaryDecoder, DataSetReaderDataType dataSetReader) { DataSetMetaDataType dataSetMetaData = dataSetReader.DataSetMetaData; try { var fieldType = (FieldTypeEncodingMask)(((byte)DataSetFlags1 & kFieldTypeUsedBits) >> 1); if (dataSetMetaData != null) { // create dataFields collection var dataFields = new List(); for (int i = 0; i < dataSetMetaData.Fields.Count; i++) { var dataField = new Field { FieldMetaData = dataSetMetaData.Fields[i] }; if (ExtensionObject.ToEncodeable(dataSetReader.SubscribedDataSet) is TargetVariablesDataType targetVariablesData && i < targetVariablesData.TargetVariables.Count) { // remember the target Attribute and target nodeId dataField.TargetAttribute = targetVariablesData.TargetVariables[i] .AttributeId; dataField.TargetNodeId = targetVariablesData.TargetVariables[i] .TargetNodeId; } dataFields.Add(dataField); } // read number of fields encoded in this delta frame message ushort fieldCount = fieldCount = binaryDecoder.ReadUInt16("FieldCount"); for (int i = 0; i < fieldCount; i++) { ushort fieldIndex = binaryDecoder.ReadUInt16("FieldIndex"); // update value in dataFields switch (fieldType) { case FieldTypeEncodingMask.Variant: dataFields[fieldIndex].Value = new DataValue(binaryDecoder.ReadVariant("FieldValue")); break; case FieldTypeEncodingMask.DataValue: dataFields[fieldIndex].Value = binaryDecoder.ReadDataValue( "FieldValue"); break; case FieldTypeEncodingMask.RawData: FieldMetaData fieldMetaData = dataSetMetaData.Fields[fieldIndex]; if (fieldMetaData != null) { object? decodedValue = DecodeRawData( binaryDecoder, fieldMetaData); #pragma warning disable CS0618 // Type or member is obsolete dataFields[fieldIndex].Value = new DataValue(new Variant(decodedValue!)); #pragma warning restore CS0618 // Type or member is obsolete } break; case FieldTypeEncodingMask.Reserved: // ignore break; default: throw ServiceResultException.Unexpected( $"Unexpected FieldDataTypeEncodingMask {fieldType}"); } } return new DataSet(dataSetMetaData.Name) { DataSetMetaData = dataSetMetaData, Fields = [.. dataFields], IsDeltaFrame = true, DataSetWriterId = DataSetWriterId, SequenceNumber = SequenceNumber }; } } catch (Exception ex) { m_logger.LogError(ex, "UadpDataSetMessage.DecodeMessageDataDeltaFrame"); } return null; } /// /// Encodes field value as RawData /// /// private void EncodeFieldAsRawData( BinaryEncoder binaryEncoder, Field field, IFormatProvider formatProvider) { try { // 01 RawData Field Encoding Variant variant = field.Value.WrappedValue; if (variant.IsNull) { return; } // TODO: Need to convert? if (field.FieldMetaData!.ValueRank == ValueRanks.Scalar) { switch ((BuiltInType)field.FieldMetaData.BuiltInType) { case BuiltInType.Boolean: binaryEncoder.WriteBoolean( "Bool", (bool)variant); break; case BuiltInType.SByte: binaryEncoder.WriteSByte( "SByte", (sbyte)variant); break; case BuiltInType.Byte: binaryEncoder.WriteByte( "Byte", (byte)variant); break; case BuiltInType.Int16: binaryEncoder.WriteInt16( "Int16", (short)variant); break; case BuiltInType.UInt16: binaryEncoder.WriteUInt16( "UInt16", (ushort)variant); break; case BuiltInType.Int32: binaryEncoder.WriteInt32( "Int32", (int)variant); break; case BuiltInType.UInt32: binaryEncoder.WriteUInt32( "UInt32", (uint)variant); break; case BuiltInType.Int64: binaryEncoder.WriteInt64( "Int64", (long)variant); break; case BuiltInType.UInt64: binaryEncoder.WriteUInt64( "UInt64", (ulong)variant); break; case BuiltInType.Float: binaryEncoder.WriteFloat( "Float", (float)variant); break; case BuiltInType.Double: binaryEncoder.WriteDouble( "Double", (double)variant); break; case BuiltInType.DateTime: binaryEncoder.WriteDateTime( "DateTime", (DateTimeUtc)variant.ConvertToDateTime()); break; case BuiltInType.Guid: binaryEncoder.WriteGuid("GUID", (Uuid)variant); break; case BuiltInType.String: binaryEncoder.WriteString("String", (string)variant); break; case BuiltInType.ByteString: binaryEncoder.WriteByteString("ByteString", (ByteString)variant); break; case BuiltInType.QualifiedName: binaryEncoder.WriteQualifiedName( "QualifiedName", (QualifiedName)variant); break; case BuiltInType.LocalizedText: binaryEncoder.WriteLocalizedText( "LocalizedText", (LocalizedText)variant); break; case BuiltInType.NodeId: binaryEncoder.WriteNodeId( "NodeId", (NodeId)variant); break; case BuiltInType.ExpandedNodeId: binaryEncoder.WriteExpandedNodeId( "ExpandedNodeId", (ExpandedNodeId)variant); break; case BuiltInType.StatusCode: binaryEncoder.WriteStatusCode("StatusCode", (StatusCode)variant); break; case BuiltInType.XmlElement: binaryEncoder.WriteXmlElement( "XmlElement", (XmlElement)variant); break; case BuiltInType.Enumeration: binaryEncoder.WriteInt32( "Enumeration", (int)variant); break; case BuiltInType.ExtensionObject: binaryEncoder.WriteExtensionObject( "ExtensionObject", (ExtensionObject)variant); break; case BuiltInType.Null: case BuiltInType.DataValue: case BuiltInType.Variant: case BuiltInType.DiagnosticInfo: case BuiltInType.Number: case BuiltInType.Integer: case BuiltInType.UInteger: break; default: throw ServiceResultException.Unexpected( $"Unexpected BuiltInType {field.FieldMetaData.BuiltInType}"); } } else if (field.FieldMetaData.ValueRank >= ValueRanks.OneDimension) { binaryEncoder.WriteVariantValue(null, variant); } } catch (Exception ex) { m_logger.LogError(ex, "Error encoding field {Name}.", field.FieldMetaData!.Name); } } /// /// Decode RawData type (for SimpleTypeDescription!?) /// private object? DecodeRawData( BinaryDecoder binaryDecoder, FieldMetaData fieldMetaData) { if (fieldMetaData.BuiltInType != 0) // && fieldMetaData.DataType.Equals(new NodeId(fieldMetaData.BuiltInType))) { try { switch (fieldMetaData.ValueRank) { case ValueRanks.Scalar: return DecodeRawScalar(binaryDecoder, fieldMetaData.BuiltInType); case ValueRanks.OneDimension: case ValueRanks.TwoDimensions: return binaryDecoder.ReadVariantValue( null, TypeInfo.Create((BuiltInType)fieldMetaData.BuiltInType, fieldMetaData.ValueRank)); default: m_logger.LogInformation( "Decoding ValueRank = {ValueRank} not supported yet !!!", fieldMetaData.ValueRank); break; } } catch (Exception ex) { m_logger.LogError(ex, "Error reading element for RawData."); return StatusCodes.BadDecodingError; } } return null; } /// /// Decode a scalar type /// /// The decoded object /// private static object? DecodeRawScalar(BinaryDecoder binaryDecoder, byte builtInType) { switch ((BuiltInType)builtInType) { case BuiltInType.Boolean: return binaryDecoder.ReadBoolean(null); case BuiltInType.SByte: return binaryDecoder.ReadSByte(null); case BuiltInType.Byte: return binaryDecoder.ReadByte(null); case BuiltInType.Int16: return binaryDecoder.ReadInt16(null); case BuiltInType.UInt16: return binaryDecoder.ReadUInt16(null); case BuiltInType.Int32: return binaryDecoder.ReadInt32(null); case BuiltInType.UInt32: return binaryDecoder.ReadUInt32(null); case BuiltInType.Int64: return binaryDecoder.ReadInt64(null); case BuiltInType.UInt64: return binaryDecoder.ReadUInt64(null); case BuiltInType.Float: return binaryDecoder.ReadFloat(null); case BuiltInType.Double: return binaryDecoder.ReadDouble(null); case BuiltInType.String: return binaryDecoder.ReadString(null); case BuiltInType.DateTime: return binaryDecoder.ReadDateTime(null); case BuiltInType.Guid: return binaryDecoder.ReadGuid(null); case BuiltInType.ByteString: return binaryDecoder.ReadByteString(null); case BuiltInType.XmlElement: return binaryDecoder.ReadXmlElement(null); case BuiltInType.NodeId: return binaryDecoder.ReadNodeId(null); case BuiltInType.ExpandedNodeId: return binaryDecoder.ReadExpandedNodeId(null); case BuiltInType.StatusCode: return binaryDecoder.ReadStatusCode(null); case BuiltInType.QualifiedName: return binaryDecoder.ReadQualifiedName(null); case BuiltInType.LocalizedText: return binaryDecoder.ReadLocalizedText(null); case BuiltInType.DataValue: return binaryDecoder.ReadDataValue(null); case BuiltInType.Enumeration: return binaryDecoder.ReadInt32(null); case BuiltInType.Variant: return binaryDecoder.ReadVariant(null); case BuiltInType.ExtensionObject: return binaryDecoder.ReadExtensionObject(null); case BuiltInType.Null: case BuiltInType.DiagnosticInfo: case BuiltInType.Number: case BuiltInType.Integer: case BuiltInType.UInteger: return null; default: throw ServiceResultException.Unexpected( $"Unexpected BuiltInType {builtInType}"); } } } }