/* ======================================================================== * 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.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Xml; using Microsoft.Extensions.Logging; using Newtonsoft.Json; #pragma warning disable CS0618 // Type or member is obsolete namespace Opc.Ua.PubSub.Encoding { /// /// Reads objects from a JSON stream. /// internal class PubSubJsonDecoder : IDecoder { /// /// The name of the Root array if the json is defined as an array /// public const string RootArrayName = "___root_array___"; /// /// If TRUE then the NamespaceUris and ServerUris tables are updated with new URIs read from the JSON stream. /// public bool UpdateNamespaceTable { get; set; } private JsonTextReader m_reader; private readonly ILogger m_logger; private readonly Dictionary m_root; private readonly Stack m_stack; private ushort[]? m_namespaceMappings; private ushort[]? m_serverMappings; private uint m_nestingLevel; /// /// JSON encoded value of: ā€œ9999-12-31T23:59:59Zā€ /// private readonly DateTime m_dateTimeMaxJsonValue = new(3155378975990000000); private enum JTokenNullObject { Undefined = 0, Object = 1, Array = 2 } /// /// Create a JSON decoder to decode a string. /// /// The JSON encoded string. /// The service message context to use. public PubSubJsonDecoder(string json, IServiceMessageContext context) { Context = context ?? throw new ArgumentNullException(nameof(context)); m_logger = context.Telemetry.CreateLogger(); m_nestingLevel = 0; m_reader = new JsonTextReader(new StringReader(json)); m_root = ReadObject(); m_stack = new Stack(); m_stack.Push(m_root); } /// /// Create a JSON decoder to decode a from a . /// /// The system type of the encoded JSON stream. /// The text reader. /// The service message context to use. public PubSubJsonDecoder(Type systemType, JsonTextReader reader, IServiceMessageContext context) { Context = context; m_logger = context.Telemetry.CreateLogger(); m_nestingLevel = 0; m_reader = reader; m_root = ReadObject(); m_stack = new Stack(); m_stack.Push(m_root); } /// /// Decodes a message from a buffer. /// /// The type of the message to read public static T DecodeMessage( byte[] buffer, IServiceMessageContext context) where T : IEncodeable { return DecodeMessage(new ArraySegment(buffer), context); } /// /// Decodes a message from a buffer. /// /// /// is null. /// /// The type of the message to read public static T DecodeMessage( ArraySegment buffer, IServiceMessageContext context) where T : IEncodeable { if (context == null) { throw new ArgumentNullException(nameof(context)); } // check that the max message size was not exceeded. if (context.MaxMessageSize > 0 && context.MaxMessageSize < buffer.Count) { throw ServiceResultException.Create( StatusCodes.BadEncodingLimitsExceeded, "MaxMessageSize {0} < {1}", context.MaxMessageSize, buffer.Count); } using var decoder = new PubSubJsonDecoder( System.Text.Encoding.UTF8.GetString(buffer.Array!, buffer.Offset, buffer.Count), context); return decoder.DecodeMessage(); } /// public T DecodeMessage() where T : IEncodeable { ArrayOf namespaceUris = ReadStringArray("NamespaceUris"); ArrayOf serverUris = ReadStringArray("ServerUris"); if (!namespaceUris.IsEmpty || !serverUris.IsEmpty) { NamespaceTable namespaces = namespaceUris.IsEmpty ? Context.NamespaceUris : new NamespaceTable(namespaceUris.ToArray()!); StringTable servers = serverUris.IsEmpty ? Context.ServerUris : new StringTable(serverUris.ToArray()!); SetMappingTables(namespaces, servers); } // read the node id. NodeId typeId = ReadNodeId("TypeId"); // convert to absolute node id. var absoluteId = NodeId.ToExpandedNodeId(typeId, Context.NamespaceUris); // Read the message. return ReadEncodeable("Body", absoluteId); } /// public void SetMappingTables(NamespaceTable namespaceUris, StringTable serverUris) { m_namespaceMappings = null; if (namespaceUris != null && Context.NamespaceUris != null) { ushort[] namespaceMappings = new ushort[namespaceUris.Count]; for (uint ii = 0; ii < namespaceUris.Count; ii++) { string uri = namespaceUris.GetString(ii)!; if (UpdateNamespaceTable) { namespaceMappings[ii] = Context.NamespaceUris.GetIndexOrAppend(uri); } else { int index = Context.NamespaceUris.GetIndex(namespaceUris.GetString(ii)!); namespaceMappings[ii] = index >= 0 ? (ushort)index : ushort.MaxValue; } } m_namespaceMappings = namespaceMappings; } m_serverMappings = null; if (serverUris != null && Context.ServerUris != null) { ushort[] serverMappings = new ushort[serverUris.Count]; for (uint ii = 0; ii < serverUris.Count; ii++) { string uri = serverUris.GetString(ii)!; if (UpdateNamespaceTable) { serverMappings[ii] = Context.ServerUris.GetIndexOrAppend(uri); } else { int index = Context.ServerUris.GetIndex(serverUris.GetString(ii)!); serverMappings[ii] = index >= 0 ? (ushort)index : ushort.MaxValue; } } m_serverMappings = serverMappings; } } /// public void Close() { m_reader.Close(); } /// /// Closes the stream used for reading. /// public void Close(bool checkEof) { if (checkEof && m_reader.TokenType != JsonToken.EndObject) { while (m_reader.Read() && m_reader.TokenType != JsonToken.EndObject) { } } m_reader.Close(); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// An overrideable version of the Dispose. /// protected virtual void Dispose(bool disposing) { if (disposing) { (m_reader as IDisposable)?.Dispose(); m_reader = null!; } } /// public EncodingType EncodingType => EncodingType.Json; /// public bool HasField(string? fieldName) { if (string.IsNullOrEmpty(fieldName) || m_stack.Count == 0) { return true; } return m_stack.Peek() is Dictionary context && context.ContainsKey(fieldName!); } /// public IServiceMessageContext Context { get; } /// public void PushNamespace(string namespaceUri) { } /// public void PopNamespace() { } /// public bool ReadField(string? fieldName, out object token) { token = null!; if (string.IsNullOrEmpty(fieldName)) { token = m_stack.Peek(); return true; } return (m_stack.Peek() is Dictionary context) && context.TryGetValue(fieldName!, out token!); } /// public bool ReadBoolean(string? fieldName) { if (!ReadField(fieldName, out object token)) { return false; } bool? value = token as bool?; if (value == null) { return false; } return (bool)token; } /// public sbyte ReadSByte(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } long? value = token as long?; if (value == null) { return 0; } if (value is < sbyte.MinValue or > sbyte.MaxValue) { return 0; } return (sbyte)value; } /// public byte ReadByte(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } long? value = token as long?; if (value == null) { return 0; } if (value is < byte.MinValue or > byte.MaxValue) { return 0; } return (byte)value; } /// public short ReadInt16(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } long? value = token as long?; if (value == null) { return 0; } if (value is < short.MinValue or > short.MaxValue) { return 0; } return (short)value; } /// public ushort ReadUInt16(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } long? value = token as long?; if (value == null) { return 0; } if (value is < ushort.MinValue or > ushort.MaxValue) { return 0; } return (ushort)value; } /// public int ReadInt32(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } long? value = token as long?; if (value == null) { return ReadEnumeratedString(token, int.TryParse); } if (value is < int.MinValue or > int.MaxValue) { return 0; } return (int)value; } /// public uint ReadUInt32(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } long? value = token as long?; if (value == null) { return ReadEnumeratedString(token, uint.TryParse); } if (value is < uint.MinValue or > uint.MaxValue) { return 0; } return (uint)value; } /// public long ReadInt64(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } long? value = token as long?; if (value == null) { if (token is not string text || !long.TryParse( text, NumberStyles.Integer, CultureInfo.InvariantCulture, out long number)) { return 0; } return number; } return (long)value; } /// public ulong ReadUInt64(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } long? value = token as long?; if (value == null) { if (token is not string text || !ulong.TryParse( text, NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong number)) { return 0; } return number; } if (value < 0) { return 0; } return (ulong)value; } /// public float ReadFloat(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } double? value = token as double?; if (value == null) { string? text = token as string; if (text == null || !float.TryParse( text, NumberStyles.Float, CultureInfo.InvariantCulture, out float number)) { if (text != null) { if (string.Equals(text, "Infinity", StringComparison.OrdinalIgnoreCase)) { return float.PositiveInfinity; } else if (string.Equals( text, "-Infinity", StringComparison.OrdinalIgnoreCase)) { return float.NegativeInfinity; } else if (string.Equals(text, "NaN", StringComparison.OrdinalIgnoreCase)) { return float.NaN; } } long? integer = token as long?; if (integer == null) { return 0; } return (float)integer; } return number; } float floatValue = (float)value; if (floatValue is >= float.MinValue and <= float.MaxValue) { return (float)value; } return 0; } /// public double ReadDouble(string? fieldName) { if (!ReadField(fieldName, out object token)) { return 0; } double? value = token as double?; if (value == null) { string? text = token as string; if (text == null || !double.TryParse( text, NumberStyles.Float, CultureInfo.InvariantCulture, out double number)) { if (text != null) { if (string.Equals(text, "Infinity", StringComparison.OrdinalIgnoreCase)) { return double.PositiveInfinity; } else if (string.Equals( text, "-Infinity", StringComparison.OrdinalIgnoreCase)) { return double.NegativeInfinity; } else if (string.Equals(text, "NaN", StringComparison.OrdinalIgnoreCase)) { return double.NaN; } } long? integer = token as long?; if (integer == null) { return 0; } return (double)integer; } return number; } return (double)value; } /// public string? ReadString(string? fieldName) { if (!ReadField(fieldName, out object token)) { return null; } if (token is not string value) { return null; } if (Context.MaxStringLength > 0 && Context.MaxStringLength < value.Length) { throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded); } return value; } /// public DateTimeUtc ReadDateTime(string? fieldName) { if (!ReadField(fieldName, out object token)) { return DateTimeUtc.MinValue; } var value = token as DateTime?; if (value != null) { return value.Value >= m_dateTimeMaxJsonValue ? DateTimeUtc.MaxValue : value.Value; } if (token is string text) { try { var result = XmlConvert.ToDateTime(text, XmlDateTimeSerializationMode.Utc); return result >= m_dateTimeMaxJsonValue ? DateTimeUtc.MaxValue : result; } catch (FormatException fe) { throw ServiceResultException.Create( StatusCodes.BadDecodingError, "Failed to decode DateTime: {0}", fe.Message); } } return DateTimeUtc.MinValue; } /// public Uuid ReadGuid(string? fieldName) { if (!ReadField(fieldName, out object token)) { return Uuid.Empty; } if (token is not string value) { return Uuid.Empty; } try { return Uuid.Parse(value); } catch (FormatException fe) { throw ServiceResultException.Create( StatusCodes.BadDecodingError, "Failed to create Guid: {0}", fe.Message); } } /// public ByteString ReadByteString(string? fieldName) { if (!ReadField(fieldName, out object token)) { return default; } if (token is JTokenNullObject) { return default; } if (token is not string value) { return ByteString.Empty; } byte[] bytes = SafeConvertFromBase64String(value); if (Context.MaxByteStringLength > 0 && Context.MaxByteStringLength < bytes.Length) { throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded); } return ByteString.From(bytes); } /// public XmlElement ReadXmlElement(string? fieldName) { if (!ReadField(fieldName, out object token) || token is not string value) { return XmlElement.Empty; } return (XmlElement)value; } /// public NodeId ReadNodeId(string? fieldName) { if (!ReadField(fieldName, out object token)) { return NodeId.Null; } if (token is string text) { NodeId nodeId; try { nodeId = NodeId.Parse( Context, text, new NodeIdParsingOptions { UpdateTables = UpdateNamespaceTable, NamespaceMappings = m_namespaceMappings, ServerMappings = m_serverMappings }); } catch { // fallback on error. this allows the application to sort out the problem. nodeId = new NodeId(text, 0); } return nodeId; } if (token is not Dictionary value) { return NodeId.Null; } IdType idType = IdType.Numeric; ushort namespaceIndex = 0; try { m_stack.Push(value); if (value.ContainsKey("IdType")) { idType = (IdType)ReadInt32("IdType"); } if (ReadField("Namespace", out object namespaceToken)) { long? index = namespaceToken as long?; if (index == null) { if (namespaceToken is string namespaceUri) { namespaceIndex = ToNamespaceIndex(namespaceUri); } } else if (index.Value is >= 0 and < ushort.MaxValue) { namespaceIndex = ToNamespaceIndex(index.Value); } } if (value.ContainsKey("Id")) { switch (idType) { case IdType.Numeric: return new NodeId(ReadUInt32("Id"), namespaceIndex); case IdType.Opaque: return new NodeId(ReadByteString("Id"), namespaceIndex); case IdType.String: return new NodeId(ReadString("Id")!, namespaceIndex); case IdType.Guid: return new NodeId(ReadGuid("Id"), namespaceIndex); default: throw ServiceResultException.Unexpected( "Unexpected IdType value: {0}", idType); } } return DefaultNodeId(idType, namespaceIndex); } finally { m_stack.Pop(); } } /// public ExpandedNodeId ReadExpandedNodeId(string? fieldName) { if (!ReadField(fieldName, out object token)) { return ExpandedNodeId.Null; } if (token is string text) { try { return ExpandedNodeId.Parse( Context, text, new NodeIdParsingOptions { UpdateTables = UpdateNamespaceTable, NamespaceMappings = m_namespaceMappings, ServerMappings = m_serverMappings }); } catch { // fallback on error. this allows the application to sort out the problem. _ = new NodeId(text, 0); } } if (token is not Dictionary value) { return ExpandedNodeId.Null; } IdType idType = IdType.Numeric; ushort namespaceIndex = 0; string? namespaceUri = null; uint serverIndex = 0; try { m_stack.Push(value); if (value.ContainsKey("IdType")) { idType = (IdType)ReadInt32("IdType"); } if (ReadField("Namespace", out object namespaceToken)) { long? index = namespaceToken as long?; if (index == null) { namespaceUri = namespaceToken as string; } else if (index.Value is >= 0 and < ushort.MaxValue) { namespaceIndex = ToNamespaceIndex(index.Value); } } if (ReadField("ServerUri", out object serverUriToken)) { long? index = serverUriToken as long?; if (index == null) { serverIndex = ToServerIndex((string)serverUriToken); } else if (index.Value is >= 0 and < uint.MaxValue) { serverIndex = ToServerIndex(index.Value); } } if (namespaceUri != null) { namespaceIndex = ToNamespaceIndex(namespaceUri); if (ushort.MaxValue != namespaceIndex) { namespaceUri = null; } else { namespaceIndex = 0; } } if (value.ContainsKey("Id")) { switch (idType) { case IdType.Numeric: return new ExpandedNodeId( ReadUInt32("Id"), namespaceIndex, namespaceUri, serverIndex); case IdType.Opaque: return new ExpandedNodeId( ReadByteString("Id"), namespaceIndex, namespaceUri, serverIndex); case IdType.String: return new ExpandedNodeId( ReadString("Id")!, namespaceIndex, namespaceUri, serverIndex); case IdType.Guid: return new ExpandedNodeId( ReadGuid("Id"), namespaceIndex, namespaceUri, serverIndex); default: throw ServiceResultException.Unexpected( "Unexpected IdType value: {0}", idType); } } return new ExpandedNodeId( DefaultNodeId(idType, namespaceIndex), namespaceUri, serverIndex); } finally { m_stack.Pop(); } } /// public StatusCode ReadStatusCode(string? fieldName) { if (!ReadField(fieldName, out object token)) { // the status code was not found return StatusCodes.Good; } if (token is long code) { return (StatusCode)(uint)code; } bool wasPush = PushStructure(fieldName); try { // try to read the non reversible Code if (ReadField("Code", out token)) { return (StatusCode)ReadUInt32("Code"); } // read the uint code return ReadUInt32(null); } finally { if (wasPush) { Pop(); } } } /// public DiagnosticInfo? ReadDiagnosticInfo(string? fieldName) { return ReadDiagnosticInfo(fieldName, 0); } /// public QualifiedName ReadQualifiedName(string? fieldName) { if (!ReadField(fieldName, out object token)) { return QualifiedName.Null; } if (token is string text) { QualifiedName qn; try { qn = QualifiedName.Parse(Context, text, UpdateNamespaceTable); if (qn.NamespaceIndex != 0) { ushort ns = ToNamespaceIndex(qn.NamespaceIndex); if (ns != qn.NamespaceIndex) { qn = new QualifiedName(qn.Name, ns); } } } catch (Exception) { // fallback on error. this allows the application to sort out the problem. qn = new QualifiedName(text, 0); } return qn; } if (token is not Dictionary value) { return QualifiedName.Null; } ushort namespaceIndex = 0; string? name = null; try { m_stack.Push(value); if (value.ContainsKey("Name")) { name = ReadString("Name"); } if (ReadField("Uri", out object namespaceToken)) { long? index = namespaceToken as long?; if (index == null) { if (namespaceToken is string namespaceUri) { namespaceIndex = ToNamespaceIndex(namespaceUri); } } else if (index.Value is >= 0 and < ushort.MaxValue) { namespaceIndex = ToNamespaceIndex(index.Value); } } } finally { m_stack.Pop(); } return new QualifiedName(name, namespaceIndex); } /// public LocalizedText ReadLocalizedText(string? fieldName) { if (!ReadField(fieldName, out object token)) { return LocalizedText.Null; } string? locale = null; string? text = null; if (token is not Dictionary value) { // read non reversible encoding text = token as string; if (text != null) { return new LocalizedText(text); } return LocalizedText.Null; } try { m_stack.Push(value); if (value.ContainsKey("Locale")) { locale = ReadString("Locale"); } if (value.ContainsKey("Text")) { text = ReadString("Text"); } } finally { m_stack.Pop(); } return new LocalizedText(locale, text); } private Variant ReadVariantFromObject( string valueName, BuiltInType builtInType, Dictionary value) { if (value.TryGetValue(valueName, out object? innerValue)) { if (innerValue is List elements) { if (elements.Any(e => e is List innerInner)) { var elements2 = new List(); var dimensions = new List(); Type? systemType = null; ExpandedNodeId encodeableTypeId = default; if (builtInType is BuiltInType.Enumeration or BuiltInType.Variant or BuiltInType.Null) { DetermineIEncodeableSystemType(ref systemType, encodeableTypeId); } // decode structured matrix ReadMatrixPart( valueName, elements, builtInType, ref elements2, ref dimensions, 0, systemType, encodeableTypeId); switch (builtInType) { case BuiltInType.Boolean: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.SByte: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.Byte: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.Int16: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.UInt16: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.Int32: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.UInt32: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.Int64: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.UInt64: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.Float: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.Double: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.String: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.DateTime: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.Guid: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.ByteString: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.XmlElement: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.NodeId: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.ExpandedNodeId: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.StatusCode: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.QualifiedName: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.LocalizedText: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.DataValue: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.Enumeration: return Variant.From(EnumValue.From(elements2.Cast().ToMatrixOf([.. dimensions]))); case BuiltInType.Variant: { if (DetermineIEncodeableSystemType(ref systemType, encodeableTypeId)) { var newElements = Array.CreateInstance(systemType!, elements.Count); for (int i = 0; i < elements.Count; i++) { newElements.SetValue( Convert.ChangeType( elements[i], systemType!, CultureInfo.InvariantCulture), i); } return new Variant(new Matrix(newElements, builtInType, [.. dimensions])); } return elements2.Cast().ToMatrixOf([.. dimensions]); } case BuiltInType.ExtensionObject: return elements2.Cast().ToMatrixOf([.. dimensions]); case BuiltInType.Null: case BuiltInType.Number: case BuiltInType.Integer: case BuiltInType.UInteger: if (DetermineIEncodeableSystemType(ref systemType, encodeableTypeId)) { var newElements = Array.CreateInstance(systemType!, elements.Count); for (int i = 0; i < elements.Count; i++) { newElements.SetValue( Convert.ChangeType( elements[i], systemType!, CultureInfo.InvariantCulture), i); } return new Variant(new Matrix(newElements, builtInType, [.. dimensions])); } throw ServiceResultException.Create( StatusCodes.BadDecodingError, "Cannot decode unknown type in Array object with BuiltInType: {0}.", builtInType); default: throw ServiceResultException.Unexpected( $"Unexpected BuiltInType {builtInType}"); } } Variant array = ReadVariantArrayBody(valueName, builtInType); if (value.ContainsKey("Dimensions")) { int[] dimensions = ReadInt32Array("Dimensions").ToArray()!; if (array.Value is not Array arrayValue) { throw new ServiceResultException( StatusCodes.BadDecodingError, "Variant array body is missing or not an array."); } try { return new Variant( new Matrix(arrayValue, builtInType, dimensions)); } catch (ArgumentException e) { throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded, e); } catch (Exception e) { throw new ServiceResultException(StatusCodes.BadDecodingError, e); } } return array; } return ReadVariantBody(valueName, builtInType); } return Variant.Null; } /// public Variant ReadVariant(string? fieldName) { if (!ReadField(fieldName, out object token)) { return Variant.Null; } if (token is not Dictionary value) { return Variant.Null; } CheckAndIncrementNestingLevel(); try { m_stack.Push(value); BuiltInType builtInType = value.ContainsKey("UaType") ? (BuiltInType)ReadByte("UaType") : (BuiltInType)ReadByte("Type"); if (value.ContainsKey("Value")) { return ReadVariantFromObject("Value", builtInType, value); } return ReadVariantFromObject("Body", builtInType, value); } finally { m_nestingLevel--; m_stack.Pop(); } } /// public DataValue ReadDataValue(string? fieldName) { if (!ReadField(fieldName, out object token)) { return default; } if (token is not Dictionary value) { return default; } var dv = new DataValue(); try { m_stack.Push(value); if (value.ContainsKey("UaType")) { var builtInType = (BuiltInType)ReadByte("UaType"); dv = dv.WithWrappedValue(ReadVariantFromObject("Value", builtInType, value)); } else { dv = dv.WithWrappedValue(ReadVariant("Value")); } dv = dv.WithStatus(ReadStatusCode("StatusCode")) .WithSourceTimestamp(ReadDateTime("SourceTimestamp")); dv = dv.WithSourcePicoseconds( dv.SourceTimestamp != DateTimeUtc.MinValue ? ReadUInt16("SourcePicoseconds") : (ushort)0); dv = dv.WithServerTimestamp(ReadDateTime("ServerTimestamp")); dv = dv.WithServerPicoseconds( dv.ServerTimestamp != DateTimeUtc.MinValue ? ReadUInt16("ServerPicoseconds") : (ushort)0); } finally { m_stack.Pop(); } return dv; } /// public ExtensionObject ReadExtensionObject(string? fieldName) { ExtensionObject extension = ExtensionObject.Null; if (!ReadField(fieldName, out object token)) { return extension; } if ((token is not Dictionary value) || (value.Count == 0)) { return extension; } try { m_stack.Push(value); bool inlineValues = true; ExpandedNodeId typeId = ReadExpandedNodeId("UaTypeId"); if (typeId.IsNull) { typeId = ReadExpandedNodeId("TypeId"); inlineValues = false; } ExpandedNodeId absoluteId = typeId.IsAbsolute ? typeId : NodeId.ToExpandedNodeId(typeId.InnerNodeId, Context.NamespaceUris); if (!typeId.IsNull && absoluteId.IsNull) { m_logger.LogWarning( "Cannot de-serialized extension objects if the NamespaceUri is not in the NamespaceTable: Type = {Type}", typeId); } else { typeId = absoluteId; } ExtensionObjectEncoding encoding = 0; string encodingFieldName = inlineValues ? "UaEncoding" : "Encoding"; encoding = (ExtensionObjectEncoding)ReadByte(encodingFieldName); if (value.ContainsKey(encodingFieldName)) { encoding = (ExtensionObjectEncoding)ReadByte(encodingFieldName); if (encoding == ExtensionObjectEncoding.None) { return extension; } } if (encoding == ExtensionObjectEncoding.Binary) { ByteString bytes = ReadByteString(inlineValues ? "UaBody" : "Body"); return new ExtensionObject(typeId, bytes); } if (encoding == ExtensionObjectEncoding.Xml) { XmlElement xml = ReadXmlElement(inlineValues ? "UaBody" : "Body"); if (xml.IsEmpty) { return extension; } return new ExtensionObject(typeId, xml); } if (encoding == ExtensionObjectEncoding.Json) { string? json = ReadString(inlineValues ? "UaBody" : "Body"); if (string.IsNullOrEmpty(json)) { return extension; } return new ExtensionObject(typeId, json!); } Type? systemType = Context.Factory.GetSystemType(typeId); if (systemType != null) { IEncodeable? encodeable = null; if (inlineValues) { encodeable = Activator.CreateInstance(systemType) as IEncodeable ?? throw new ServiceResultException( StatusCodes.BadDecodingError, Utils.Format( "Type does not support IEncodeable interface: '{0}'", systemType.FullName!)); encodeable.Decode(this); } else { encodeable = ReadEncodeable("Body", systemType, typeId); if (encodeable == null) { return extension; } } return new ExtensionObject(typeId, encodeable); } using var ostrm = new MemoryStream(); using (var stream = new StreamWriter(ostrm)) using (var writer = new JsonTextWriter(stream)) { EncodeAsJson(writer, token); } // Close the writer before retrieving the data return new ExtensionObject(typeId, ByteString.From(ostrm.ToArray())); } finally { m_stack.Pop(); } } /// public IEncodeable? ReadEncodeable( string? fieldName, Type systemType, ExpandedNodeId encodeableTypeId = default) { if (systemType == null) { throw new ArgumentNullException(nameof(systemType)); } if (!ReadField(fieldName, out object token)) { return null; } if (Activator.CreateInstance(systemType) is not IEncodeable value) { throw new ServiceResultException( StatusCodes.BadDecodingError, Utils.Format( "Type does not support IEncodeable interface: '{0}'", systemType.FullName!)); } if (!encodeableTypeId.IsNull) { // set type identifier for custom complex data types before decode. if (value is IComplexTypeInstance complexTypeInstance) { complexTypeInstance.TypeId = encodeableTypeId; } } CheckAndIncrementNestingLevel(); try { m_stack.Push(token); value.Decode(this); } finally { m_stack.Pop(); m_nestingLevel--; } return value; } /// public Enum ReadEnumerated(string? fieldName, Type enumType) { if (enumType == null) { throw new ArgumentNullException(nameof(enumType)); } if (!ReadField(fieldName, out object token)) { return (Enum)Enum.ToObject(enumType, 0); } if (token is long code) { return (Enum)Enum.ToObject(enumType, code); } if (token is string text) { int index = text.LastIndexOf('_'); if (index > 0 && long.TryParse(text[(index + 1)..], out code)) { return (Enum)Enum.ToObject(enumType, code); } } return (Enum)Enum.ToObject(enumType, 0); } /// public ArrayOf ReadBooleanArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadBoolean(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadSByteArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadSByte(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadByteArray(string? fieldName) { var values = new List(); string? value = ReadString(fieldName); if (value != null) { return SafeConvertFromBase64String(value); } if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadByte(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadInt16Array(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadInt16(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadUInt16Array(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadUInt16(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadInt32Array(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadInt32(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadUInt32Array(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadUInt32(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadInt64Array(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadInt64(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadUInt64Array(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadUInt64(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadFloatArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadFloat(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadDoubleArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadDouble(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadStringArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadString(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadDateTimeArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values.Add(ReadDateTime(null)); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadGuidArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); Uuid element = ReadGuid(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadByteStringArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); ByteString element = ReadByteString(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadXmlElementArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); XmlElement element = ReadXmlElement(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadNodeIdArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); NodeId element = ReadNodeId(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadExpandedNodeIdArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); ExpandedNodeId element = ReadExpandedNodeId(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadStatusCodeArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); StatusCode element = ReadStatusCode(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadDiagnosticInfoArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); DiagnosticInfo? element = ReadDiagnosticInfo(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadQualifiedNameArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); QualifiedName element = ReadQualifiedName(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadLocalizedTextArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); LocalizedText element = ReadLocalizedText(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadVariantArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); Variant element = ReadVariant(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadDataValueArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); DataValue element = ReadDataValue(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public ArrayOf ReadExtensionObjectArray(string? fieldName) { var values = new List(); if (!ReadArrayField(fieldName, out List token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); ExtensionObject element = ReadExtensionObject(null); values.Add(element); } finally { m_stack.Pop(); } } return values; } /// public Array ReadEncodeableArray( string? fieldName, Type systemType, ExpandedNodeId encodeableTypeId = default) { if (systemType == null) { throw new ArgumentNullException(nameof(systemType)); } if (!ReadArrayField(fieldName, out List token)) { return Array.CreateInstance(systemType, 0); } var values = Array.CreateInstance(systemType, token.Count); for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); IEncodeable? element = ReadEncodeable(null, systemType, encodeableTypeId); values.SetValue(element, ii); } finally { m_stack.Pop(); } } return values; } /// public Array ReadEnumeratedArray(string? fieldName, Type enumType) { if (enumType == null) { throw new ArgumentNullException(nameof(enumType)); } if (!ReadArrayField(fieldName, out List token)) { return Array.CreateInstance(enumType, 0); } var values = Array.CreateInstance(enumType, token.Count); for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); Enum? element = ReadEnumerated(null, enumType); values.SetValue(element, ii); } finally { m_stack.Pop(); } } return values; } /// public T ReadEncodeable(string? fieldName, ExpandedNodeId encodeableTypeId) where T : IEncodeable { return (T)ReadEncodeable(fieldName, typeof(T), encodeableTypeId)!; } /// public T ReadEncodeable(string? fieldName) where T : IEncodeable, new() { return (T)ReadEncodeable(fieldName, typeof(T))!; } /// public T ReadEncodeableAsExtensionObject(string? fieldName) where T : IEncodeable { return ReadExtensionObject(fieldName).TryGetValue(out T? value) ? value! : default!; } /// public T ReadEnumerated(string? fieldName) where T : struct, Enum { return (T)ReadEnumerated(fieldName, typeof(T)); } /// public EnumValue ReadEnumerated(string? fieldName) { if (!ReadField(fieldName, out object token)) { return default; } if (token is long code) { return (EnumValue)(int)code; } if (token is string text) { int index = text.LastIndexOf('_'); if (index > 0 && long.TryParse(text[(index + 1)..], out code)) { return new EnumValue((int)code, text[..index]); } } return default; } /// public ArrayOf ReadEncodeableArray(string? fieldName) where T : IEncodeable, new() { return ArrayOf.From(ReadEncodeableArray(fieldName, typeof(T))); } /// public ArrayOf ReadEncodeableArray(string? fieldName, ExpandedNodeId encodeableTypeId) where T : IEncodeable { return ArrayOf.From(ReadEncodeableArray(fieldName, typeof(T), encodeableTypeId)); } /// public ArrayOf ReadEncodeableArrayAsExtensionObjects(string? fieldName) where T : IEncodeable { ArrayOf array = ReadExtensionObjectArray(fieldName); return array.GetStructuresOf(); } /// public MatrixOf ReadEncodeableMatrix(string? fieldName, ExpandedNodeId encodeableTypeId) where T : IEncodeable { return default; } /// public ArrayOf ReadEnumeratedArray(string? fieldName) where T : struct, Enum { return ArrayOf.From(ReadEnumeratedArray(fieldName, typeof(T))); } /// public ArrayOf ReadEnumeratedArray(string? fieldName) { if (!ReadArrayField(fieldName, out List token)) { return default; } var values = new EnumValue[token.Count]; for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); values[ii] = ReadEnumerated(null); } finally { m_stack.Pop(); } } return values; } /// public Variant ReadVariantValue(string? fieldName, TypeInfo typeInfo) { return default; } /// public Array? ReadArray( string fieldName, int valueRank, BuiltInType builtInType, Type? systemType = null, ExpandedNodeId encodeableTypeId = default) { if (valueRank == ValueRanks.OneDimension) { switch (builtInType) { case BuiltInType.Boolean: return ReadBooleanArray(fieldName).ToArray(); case BuiltInType.SByte: return ReadSByteArray(fieldName).ToArray(); case BuiltInType.Byte: return ReadByteArray(fieldName).ToArray(); case BuiltInType.Int16: return ReadInt16Array(fieldName).ToArray(); case BuiltInType.UInt16: return ReadUInt16Array(fieldName).ToArray(); case BuiltInType.Enumeration: DetermineIEncodeableSystemType(ref systemType, encodeableTypeId); if (systemType?.IsEnum == true) { return ReadEnumeratedArray(fieldName, systemType); } goto case BuiltInType.Int32; case BuiltInType.Int32: return ReadInt32Array(fieldName).ToArray(); case BuiltInType.UInt32: return ReadUInt32Array(fieldName).ToArray(); case BuiltInType.Int64: return ReadInt64Array(fieldName).ToArray(); case BuiltInType.UInt64: return ReadUInt64Array(fieldName).ToArray(); case BuiltInType.Float: return ReadFloatArray(fieldName).ToArray(); case BuiltInType.Double: return ReadDoubleArray(fieldName).ToArray(); case BuiltInType.String: return ReadStringArray(fieldName).ToArray(); case BuiltInType.DateTime: return ReadDateTimeArray(fieldName).ToArray(); case BuiltInType.Guid: return ReadGuidArray(fieldName).ToArray(); case BuiltInType.ByteString: return ReadByteStringArray(fieldName).ToArray(); case BuiltInType.XmlElement: return ReadXmlElementArray(fieldName).ToArray(); case BuiltInType.NodeId: return ReadNodeIdArray(fieldName).ToArray(); case BuiltInType.ExpandedNodeId: return ReadExpandedNodeIdArray(fieldName).ToArray(); case BuiltInType.StatusCode: return ReadStatusCodeArray(fieldName).ToArray(); case BuiltInType.QualifiedName: return ReadQualifiedNameArray(fieldName).ToArray(); case BuiltInType.LocalizedText: return ReadLocalizedTextArray(fieldName).ToArray(); case BuiltInType.DataValue: return ReadDataValueArray(fieldName).ToArray(); case BuiltInType.Variant: if (DetermineIEncodeableSystemType(ref systemType, encodeableTypeId)) { return ReadEncodeableArray(fieldName, systemType!, encodeableTypeId); } return ReadVariantArray(fieldName).ToArray(); case BuiltInType.ExtensionObject: return ReadExtensionObjectArray(fieldName).ToArray(); case BuiltInType.DiagnosticInfo: return ReadDiagnosticInfoArray(fieldName).ToArray(); case BuiltInType.Null: case BuiltInType.Number: case BuiltInType.Integer: case BuiltInType.UInteger: if (DetermineIEncodeableSystemType(ref systemType, encodeableTypeId)) { return ReadEncodeableArray(fieldName, systemType!, encodeableTypeId); } throw new ServiceResultException( StatusCodes.BadDecodingError, Utils.Format( "Cannot decode unknown type in Array object with BuiltInType: {0}.", builtInType)); default: throw ServiceResultException.Unexpected($"Unexpected BuiltInType {builtInType}"); } } else if (valueRank >= ValueRanks.TwoDimensions) { if (!ReadField(fieldName, out object token)) { return null; } if (token is Dictionary value) { m_stack.Push(value); int[] dimensions2; if (value.ContainsKey("Dimensions")) { dimensions2 = ReadInt32Array("Dimensions").ToArray()!; } else { dimensions2 = []; } Array? array2 = ReadArray("Array", 1, builtInType, systemType, encodeableTypeId); m_stack.Pop(); try { var matrix2 = new Matrix(array2!, builtInType, dimensions2!); return matrix2.ToArray(); } catch (ArgumentException e) { throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded, e); } catch (Exception e) { throw new ServiceResultException(StatusCodes.BadDecodingError, e); } } if (token is not List array) { return null; } var elements = new List(); var dimensions = new List(); if (builtInType is BuiltInType.Enumeration or BuiltInType.Variant or BuiltInType.Null) { DetermineIEncodeableSystemType(ref systemType, encodeableTypeId); } ReadMatrixPart( fieldName, array, builtInType, ref elements, ref dimensions, 0, systemType, encodeableTypeId); if (dimensions.Count == 0) { // for an empty element create the empty dimension array dimensions = new int[valueRank].ToList(); } else if (dimensions.Count < ValueRanks.TwoDimensions) { throw ServiceResultException.Create( StatusCodes.BadDecodingError, "The ValueRank {0} of the decoded array doesn't match the desired ValueRank {1}.", dimensions.Count, valueRank); } Matrix matrix; try { switch (builtInType) { case BuiltInType.Boolean: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.SByte: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.Byte: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.Int16: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.UInt16: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.Int32: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.UInt32: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.Int64: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.UInt64: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.Float: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.Double: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.String: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.DateTime: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.Guid: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.ByteString: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.XmlElement: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.NodeId: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.ExpandedNodeId: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.StatusCode: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.QualifiedName: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.LocalizedText: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.DataValue: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.Enumeration: { if (systemType?.IsEnum == true) { var newElements = Array.CreateInstance(systemType!, elements.Count); int ii = 0; foreach (object element in elements) { newElements.SetValue( Convert.ChangeType( element, systemType!, CultureInfo.InvariantCulture), ii++); } matrix = new Matrix(newElements, builtInType, [.. dimensions]); } else { matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); } break; } case BuiltInType.Variant: { if (DetermineIEncodeableSystemType(ref systemType, encodeableTypeId)) { var newElements = Array.CreateInstance(systemType!, elements.Count); for (int i = 0; i < elements.Count; i++) { newElements.SetValue( Convert.ChangeType( elements[i], systemType!, CultureInfo.InvariantCulture), i); } matrix = new Matrix(newElements, builtInType, [.. dimensions]); break; } matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; } case BuiltInType.ExtensionObject: matrix = new Matrix( elements.Cast().ToArray(), builtInType, [.. dimensions]); break; case BuiltInType.Null: case BuiltInType.Number: case BuiltInType.Integer: case BuiltInType.UInteger: if (DetermineIEncodeableSystemType(ref systemType, encodeableTypeId)) { var newElements = Array.CreateInstance(systemType!, elements.Count); for (int i = 0; i < elements.Count; i++) { newElements.SetValue( Convert.ChangeType( elements[i], systemType!, CultureInfo.InvariantCulture), i); } matrix = new Matrix(newElements, builtInType, [.. dimensions]); break; } throw ServiceResultException.Create( StatusCodes.BadDecodingError, "Cannot decode unknown type in Array object with BuiltInType: {0}.", builtInType); case BuiltInType.DiagnosticInfo: throw ServiceResultException.Create( StatusCodes.BadDecodingError, "Cannot decode unknown type in Array object with BuiltInType: {0}.", builtInType); default: throw ServiceResultException.Unexpected( $"Unexpected BuiltInType {builtInType}"); } } catch (ArgumentException e) { throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded, e); } catch (Exception e) { throw new ServiceResultException(StatusCodes.BadDecodingError, e); } return matrix.ToArray(); } return null; } /// public uint ReadSwitchField(IList switches, out string? fieldName) { fieldName = null; if (m_stack.Peek() is Dictionary context) { long index = -1; if (context.ContainsKey("SwitchField")) { index = ReadUInt32("SwitchField"); } if (switches == null) { return 0; } if (index >= switches.Count) { return (uint)index; } if (index >= 0) { if (!context.ContainsKey("Value")) { fieldName = switches[(int)(index - 1)]; } else { fieldName = "Value"; } return (uint)index; } foreach (KeyValuePair ii in context) { if (ii.Key == "UaTypeId") { continue; } index = switches.IndexOf(ii.Key); if (index >= 0) { fieldName = ii.Key; return (uint)(index + 1); } } } return 0; } /// public uint ReadEncodingMask(IList masks) { if (m_stack.Peek() is Dictionary context) { if (context.ContainsKey("EncodingMask")) { return ReadUInt32("EncodingMask"); } uint mask = 0; if (masks == null) { return 0; } foreach (string fieldName in masks) { if (context.ContainsKey(fieldName)) { int index = masks.IndexOf(fieldName); if (index >= 0) { mask |= (uint)(1 << index); } } } return mask; } return 0; } /// /// Push the specified structure on the Read Stack /// /// The name of the object that shall be placed on the Read Stack /// true if successful public bool PushStructure(string? fieldName) { if (!ReadField(fieldName, out object token)) { return false; } if (token != null) { m_stack.Push(token); return true; } return false; } /// public bool PushArray(string? fieldName, int index) { if (!ReadArrayField(fieldName, out List token)) { return false; } if (index < token.Count) { m_stack.Push(token[index]); return true; } return false; } /// public void Pop() { m_stack.Pop(); } private ushort ToNamespaceIndex(string uri) { int index = Context.NamespaceUris.GetIndex(uri); if (index < 0) { if (!UpdateNamespaceTable) { return ushort.MaxValue; } index = Context.NamespaceUris.GetIndexOrAppend(uri); } return (ushort)index; } private ushort ToNamespaceIndex(long index) { if (m_namespaceMappings == null || index <= 0) { return (ushort)index; } if (index < 0 || index >= m_namespaceMappings.Length) { throw new ServiceResultException( StatusCodes.BadDecodingError, $"No mapping for NamespaceIndex={index}."); } return m_namespaceMappings[index]; } private ushort ToServerIndex(string uri) { int index = Context.ServerUris.GetIndex(uri); if (index < 0) { if (!UpdateNamespaceTable) { return ushort.MaxValue; } index = Context.ServerUris.GetIndexOrAppend(uri); } return (ushort)index; } private ushort ToServerIndex(long index) { if (m_serverMappings == null || index <= 0) { return (ushort)index; } if (index < 0 || index >= m_serverMappings.Length) { throw new ServiceResultException( StatusCodes.BadDecodingError, $"No mapping for ServerIndex(={index}."); } return m_serverMappings[index]; } /// /// Helper to provide the TryParse method when reading an enumerated string. /// /// private delegate bool TryParseHandler( string s, NumberStyles numberStyles, CultureInfo cultureInfo, out T result); /// /// Helper to read an enumerated string in an extension object. /// /// The number type which was encoded. /// The parsed number or 0. private static T ReadEnumeratedString(object token, TryParseHandler handler) where T : struct { T number = default; if (token is string text) { bool retry = false; do { if (handler?.Invoke( text, NumberStyles.Integer, CultureInfo.InvariantCulture, out number) == false) { int lastIndex = text.LastIndexOf('_'); if (lastIndex != -1) { text = text[(lastIndex + 1)..]; retry = true; } } } while (retry); } return number; } /// /// Reads a DiagnosticInfo from the stream. /// Limits the InnerDiagnosticInfos to the specified depth. /// /// private DiagnosticInfo? ReadDiagnosticInfo(string? fieldName, int depth) { if (!ReadField(fieldName, out object token)) { return null; } if (token is not Dictionary value) { return null; } if (depth >= DiagnosticInfo.MaxInnerDepth) { throw ServiceResultException.Create( StatusCodes.BadEncodingLimitsExceeded, "Maximum nesting level of InnerDiagnosticInfo was exceeded"); } CheckAndIncrementNestingLevel(); try { m_stack.Push(value); var di = new DiagnosticInfo(); bool hasDiagnosticInfo = false; if (value.ContainsKey("SymbolicId")) { di.SymbolicId = ReadInt32("SymbolicId"); hasDiagnosticInfo = true; } if (value.ContainsKey("NamespaceUri")) { di.NamespaceUri = ReadInt32("NamespaceUri"); hasDiagnosticInfo = true; } if (value.ContainsKey("Locale")) { di.Locale = ReadInt32("Locale"); hasDiagnosticInfo = true; } if (value.ContainsKey("LocalizedText")) { di.LocalizedText = ReadInt32("LocalizedText"); hasDiagnosticInfo = true; } if (value.ContainsKey("AdditionalInfo")) { di.AdditionalInfo = ReadString("AdditionalInfo"); hasDiagnosticInfo = true; } if (value.ContainsKey("InnerStatusCode")) { di.InnerStatusCode = ReadStatusCode("InnerStatusCode"); hasDiagnosticInfo = true; } if (value.ContainsKey("InnerDiagnosticInfo") && depth < DiagnosticInfo.MaxInnerDepth) { di.InnerDiagnosticInfo = ReadDiagnosticInfo("InnerDiagnosticInfo", depth + 1); hasDiagnosticInfo = true; } return hasDiagnosticInfo ? di : null; } finally { m_nestingLevel--; m_stack.Pop(); } } /// /// Get the system type from the type factory if not specified by caller. /// /// The reference to the system type, or null /// The encodeable type id of the system type. /// If the system type is assignable to private bool DetermineIEncodeableSystemType( ref Type? systemType, ExpandedNodeId encodeableTypeId) { if (!encodeableTypeId.IsNull && systemType == null) { systemType = Context.Factory.GetSystemType(encodeableTypeId); } return typeof(IEncodeable).IsAssignableFrom(systemType); } /// /// Read the body of a Variant as a BuiltInType /// /// private Variant ReadVariantBody(string? fieldName, BuiltInType type) { switch (type) { case BuiltInType.Boolean: return new Variant(ReadBoolean(fieldName)); case BuiltInType.SByte: return new Variant(ReadSByte(fieldName)); case BuiltInType.Byte: return new Variant(ReadByte(fieldName)); case BuiltInType.Int16: return new Variant(ReadInt16(fieldName)); case BuiltInType.UInt16: return new Variant(ReadUInt16(fieldName)); case BuiltInType.Int32: return new Variant(ReadInt32(fieldName)); case BuiltInType.UInt32: return new Variant(ReadUInt32(fieldName)); case BuiltInType.Int64: return new Variant(ReadInt64(fieldName)); case BuiltInType.UInt64: return new Variant(ReadUInt64(fieldName)); case BuiltInType.Float: return new Variant(ReadFloat(fieldName)); case BuiltInType.Double: return new Variant(ReadDouble(fieldName)); case BuiltInType.String: return new Variant(ReadString(fieldName)!); case BuiltInType.ByteString: return new Variant(ReadByteString(fieldName)); case BuiltInType.DateTime: return new Variant(ReadDateTime(fieldName)); case BuiltInType.Guid: return new Variant(ReadGuid(fieldName)); case BuiltInType.NodeId: return new Variant(ReadNodeId(fieldName)); case BuiltInType.ExpandedNodeId: return new Variant(ReadExpandedNodeId(fieldName)); case BuiltInType.QualifiedName: return new Variant(ReadQualifiedName(fieldName)); case BuiltInType.LocalizedText: return new Variant(ReadLocalizedText(fieldName)); case BuiltInType.StatusCode: return new Variant(ReadStatusCode(fieldName)); case BuiltInType.XmlElement: return new Variant(ReadXmlElement(fieldName)); case BuiltInType.ExtensionObject: return new Variant(ReadExtensionObject(fieldName)); case BuiltInType.Variant: return new Variant(ReadVariant(fieldName)); case BuiltInType.DataValue: return new Variant(ReadDataValue(fieldName)); case BuiltInType.DiagnosticInfo: case BuiltInType.Null: case BuiltInType.Number: case BuiltInType.Integer: case BuiltInType.UInteger: case BuiltInType.Enumeration: return Variant.Null; default: throw ServiceResultException.Unexpected($"Unexpected BuiltInType {type}"); } } /// /// Read the Body of a Variant as an Array /// /// private Variant ReadVariantArrayBody(string? fieldName, BuiltInType type) { switch (type) { case BuiltInType.Boolean: return new Variant(ReadBooleanArray(fieldName)); case BuiltInType.SByte: return new Variant(ReadSByteArray(fieldName)); case BuiltInType.Byte: return new Variant(ReadByteArray(fieldName)); case BuiltInType.Int16: return new Variant(ReadInt16Array(fieldName)); case BuiltInType.UInt16: return new Variant(ReadUInt16Array(fieldName)); case BuiltInType.Int32: return new Variant(ReadInt32Array(fieldName)); case BuiltInType.UInt32: return new Variant(ReadUInt32Array(fieldName)); case BuiltInType.Int64: return new Variant(ReadInt64Array(fieldName)); case BuiltInType.UInt64: return new Variant(ReadUInt64Array(fieldName)); case BuiltInType.Float: return new Variant(ReadFloatArray(fieldName)); case BuiltInType.Double: return new Variant(ReadDoubleArray(fieldName)); case BuiltInType.String: return new Variant((ArrayOf)ReadStringArray(fieldName)!); case BuiltInType.ByteString: return new Variant(ReadByteStringArray(fieldName)); case BuiltInType.DateTime: return new Variant(ReadDateTimeArray(fieldName)); case BuiltInType.Guid: return new Variant(ReadGuidArray(fieldName)); case BuiltInType.NodeId: return new Variant(ReadNodeIdArray(fieldName)); case BuiltInType.ExpandedNodeId: return new Variant(ReadExpandedNodeIdArray(fieldName)); case BuiltInType.QualifiedName: return new Variant(ReadQualifiedNameArray(fieldName)); case BuiltInType.LocalizedText: return new Variant(ReadLocalizedTextArray(fieldName)); case BuiltInType.StatusCode: return new Variant(ReadStatusCodeArray(fieldName)); case BuiltInType.XmlElement: return new Variant(ReadXmlElementArray(fieldName)); case BuiltInType.ExtensionObject: return new Variant(ReadExtensionObjectArray(fieldName)); case BuiltInType.Variant: return new Variant(ReadVariantArray(fieldName)); case BuiltInType.DataValue: return new Variant(ReadDataValueArray(fieldName)); case BuiltInType.DiagnosticInfo: case BuiltInType.Null: case BuiltInType.Number: case BuiltInType.Integer: case BuiltInType.UInteger: case BuiltInType.Enumeration: return Variant.Null; default: throw ServiceResultException.Unexpected($"Unexpected BuiltInType {type}"); } } /// /// Reads the content of an Array from json stream /// private List ReadArray() { CheckAndIncrementNestingLevel(); try { var elements = new List(); while (m_reader.Read() && m_reader.TokenType != JsonToken.EndArray) { switch (m_reader.TokenType) { case JsonToken.Comment: break; case JsonToken.Null: elements.Add(JTokenNullObject.Array); break; case JsonToken.Date: case JsonToken.Boolean: case JsonToken.Integer: case JsonToken.Float: case JsonToken.String: elements.Add(m_reader.Value!); break; case JsonToken.StartArray: elements.Add(ReadArray()); break; case JsonToken.StartObject: elements.Add(ReadObject()); break; case JsonToken.None: case JsonToken.StartConstructor: case JsonToken.PropertyName: case JsonToken.Raw: case JsonToken.Undefined: case JsonToken.EndObject: case JsonToken.EndArray: case JsonToken.EndConstructor: case JsonToken.Bytes: break; default: Debug.Fail($"Unexpected token type in array: {m_reader.TokenType}"); break; } } return elements; } finally { m_nestingLevel--; } } /// /// Reads an object from the json stream /// /// private Dictionary ReadObject() { var fields = new Dictionary(); try { while (m_reader.Read() && m_reader.TokenType != JsonToken.EndObject) { if (m_reader.TokenType == JsonToken.StartArray) { fields[RootArrayName] = ReadArray(); } else if (m_reader.TokenType == JsonToken.PropertyName) { string name = (string)m_reader.Value!; if (m_reader.Read() && m_reader.TokenType != JsonToken.EndObject) { switch (m_reader.TokenType) { case JsonToken.Comment: break; case JsonToken.Null: fields[name!] = JTokenNullObject.Object; break; case JsonToken.Date: case JsonToken.Bytes: case JsonToken.Boolean: case JsonToken.Integer: case JsonToken.Float: case JsonToken.String: fields[name!] = m_reader.Value!; break; case JsonToken.StartArray: fields[name!] = ReadArray(); break; case JsonToken.StartObject: fields[name!] = ReadObject(); break; case JsonToken.None: case JsonToken.StartConstructor: case JsonToken.PropertyName: case JsonToken.Raw: case JsonToken.Undefined: case JsonToken.EndObject: case JsonToken.EndArray: case JsonToken.EndConstructor: break; default: Debug.Fail($"Unexpected token type in array: {m_reader.TokenType}"); break; } } } } } catch (JsonReaderException jre) { throw ServiceResultException.Create( StatusCodes.BadDecodingError, "Error reading JSON object: {0}", jre.Message); } return fields; } /// /// Read the Matrix part (simple array or array of arrays) /// private void ReadMatrixPart( string? fieldName, List? currentArray, BuiltInType builtInType, ref List elements, ref List dimensions, int level, Type? systemType, ExpandedNodeId encodeableTypeId) { CheckAndIncrementNestingLevel(); try { if (currentArray?.Count > 0) { bool hasInnerArray = false; for (int ii = 0; ii < currentArray.Count; ii++) { if (ii == 0 && dimensions.Count <= level) { // remember dimension length dimensions.Add(currentArray.Count); } if (currentArray[ii] is List) { hasInnerArray = true; PushArray(fieldName, ii); ReadMatrixPart( null, currentArray[ii] as List, builtInType, ref elements, ref dimensions, level + 1, systemType, encodeableTypeId); Pop(); } else { break; // do not continue reading array of array } } if (!hasInnerArray) { // read array from one dimension Array? part = ReadArray( null!, ValueRanks.OneDimension, builtInType, systemType, encodeableTypeId); if (part != null && part.Length > 0) { // add part elements to final list foreach (object item in part) { elements.Add(item); } } } } } finally { m_nestingLevel--; } } /// /// Get Default value for NodeId for diferent IdTypes /// /// new NodeId /// private static NodeId DefaultNodeId(IdType idType, ushort namespaceIndex) { switch (idType) { case IdType.Numeric: return new NodeId(0U, namespaceIndex); case IdType.Opaque: return new NodeId(ByteString.Empty, namespaceIndex); case IdType.String: return new NodeId(string.Empty, namespaceIndex); case IdType.Guid: return new NodeId(Guid.Empty, namespaceIndex); default: throw ServiceResultException.Unexpected( "Unexpected IdType value: {0}", idType); } } private void EncodeAsJson(JsonTextWriter writer, object value) { try { if (value is Dictionary map) { EncodeAsJson(writer, map); return; } if (value is List list) { writer.WriteStartArray(); foreach (object element in list) { EncodeAsJson(writer, element); } writer.WriteEndArray(); return; } writer.WriteValue(value); } catch (JsonWriterException jwe) { throw ServiceResultException.Create( StatusCodes.BadDecodingError, "Unable to encode ExtensionObject Body as Json: {0}", jwe.Message); } } private void EncodeAsJson(JsonTextWriter writer, Dictionary value) { writer.WriteStartObject(); foreach (KeyValuePair field in value) { writer.WritePropertyName(field.Key); EncodeAsJson(writer, field.Value); } writer.WriteEndObject(); } private bool ReadArrayField(string? fieldName, out List array) { array = null!; if (!ReadField(fieldName, out object token)) { return false; } array = (token as List)!; if (array == null) { return false; } if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < array.Count) { throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded); } return true; } /// /// Safe Convert function which throws a BadDecodingError if unsuccessful. /// /// private static byte[] SafeConvertFromBase64String(string s) { try { return Convert.FromBase64String(s); } catch (FormatException fe) { throw ServiceResultException.Create( StatusCodes.BadDecodingError, "Error decoding base64 string: {0}", fe.Message); } } /// /// Test and increment the nesting level. /// /// private void CheckAndIncrementNestingLevel() { if (m_nestingLevel > Context.MaxEncodingNestingLevels) { throw ServiceResultException.Create( StatusCodes.BadEncodingLimitsExceeded, "Maximum nesting level of {0} was exceeded", Context.MaxEncodingNestingLevels); } m_nestingLevel++; } } }