/* ======================================================================== * Copyright (c) 2005-2020 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.Linq; using System.Threading.Tasks; using System.Xml; using static Opc.Ua.Utils; namespace Opc.Ua.Client.ComplexTypes { /// /// Manages the custom types of a server for a client session. /// Loads the custom types into the type factory /// of a client session, to allow for decoding and encoding /// of custom enumeration types and structured types. /// /// /// Support for V1.03 dictionaries and all V1.04 data type definitions /// with the following known restrictions: /// - Support only for V1.03 structured types which can be mapped to the V1.04 /// structured type definition. Unsupported V1.03 types are ignored. /// - V1.04 OptionSet does not create the enumeration flags. /// public class ComplexTypeSystem { #region Constructors /// /// Initializes the type system with a session to load the custom types. /// public ComplexTypeSystem(Session session) { Initialize(session, new ComplexTypeBuilderFactory()); } /// /// Initializes the type system with a session to load the custom types /// and a customized type builder factory /// public ComplexTypeSystem( Session session, IComplexTypeFactory complexTypeBuilderFactory) { Initialize(session, complexTypeBuilderFactory); } private void Initialize( Session session, IComplexTypeFactory complexTypeBuilderFactory) { m_session = session; m_complexTypeBuilderFactory = complexTypeBuilderFactory; } #endregion #region Public Members /// /// Load a single custom type with subtypes. /// /// /// Uses inverse references on the server to find the super type(s). /// If the new structure contains a type dependency to a yet /// unknown type, it loads also the dependent type(s). /// For servers without DataTypeDefinition support all /// custom types are loaded. /// public async Task LoadType(ExpandedNodeId nodeId, bool subTypes = false, bool throwOnError = false) { try { // add fast path, if no subTypes are requested if (!subTypes) { var systemType = GetSystemType(nodeId); if (systemType != null) { return systemType; } } var subTypeNodes = LoadDataTypes(nodeId, subTypes, true); var subTypeNodesWithoutKnownTypes = RemoveKnownTypes(subTypeNodes); if (subTypeNodesWithoutKnownTypes.Count > 0) { IList serverEnumTypes = new List(); IList serverStructTypes = new List(); foreach (var node in subTypeNodesWithoutKnownTypes) { AddEnumerationOrStructureType(node, serverEnumTypes, serverStructTypes); } // load server types if (DisableDataTypeDefinition || !LoadBaseDataTypes(serverEnumTypes, serverStructTypes)) { if (!DisableDataTypeDictionary) { await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false).ConfigureAwait(false); } } } return GetSystemType(nodeId); } catch (Exception ex) { Utils.Trace(ex, "Failed to load the custom type {0}.", nodeId); if (throwOnError) { throw; } return null; } } /// /// Load all custom types of a namespace. /// /// /// If a new type in the namespace contains a type dependency to an /// unknown type in another namespace, it loads also the dependent type(s). /// For servers without DataTypeDefinition support all /// custom types are loaded. /// public async Task LoadNamespace(string nameSpace, bool throwOnError = false) { try { int index = m_session.NamespaceUris.GetIndex(nameSpace); if (index < 0) { throw new ServiceResultException($"Bad argument {nameSpace}. Namespace not found."); } ushort nameSpaceIndex = (ushort)index; var serverEnumTypes = LoadDataTypes(DataTypeIds.Enumeration); var serverStructTypes = LoadDataTypes(DataTypeIds.Structure, true); // filter for namespace serverEnumTypes = serverEnumTypes.Where(rd => rd.NodeId.NamespaceIndex == nameSpaceIndex).ToList(); serverStructTypes = serverStructTypes.Where(rd => rd.NodeId.NamespaceIndex == nameSpaceIndex).ToList(); // load types if (DisableDataTypeDefinition || !LoadBaseDataTypes(serverEnumTypes, serverStructTypes)) { if (DisableDataTypeDictionary) { return false; } return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false).ConfigureAwait(false); } return true; } catch (Exception ex) { Utils.Trace(ex, "Failed to load the custom type dictionary."); if (throwOnError) { throw; } return false; } } /// /// Load all custom types from a server into the session system type factory. /// /// /// The loader follows the following strategy: /// - Load all DataType nodes of the Enumeration subtypes. /// - Load all DataType nodes of the Structure subtypes. /// - Create all enumerated custom types using the DataTypeDefinion attribute, if available. /// - Create all remaining enumerated custom types using the EnumValues or EnumStrings property, if available. /// - Create all structured types using the DataTypeDefinion attribute, if available. /// if there are type definitions remaining /// - Load the binary schema dictionaries with type definitions. /// - Create all remaining enumerated custom types using the dictionaries. /// - Convert all structured types in the dictionaries to the DataTypeDefinion attribute, if possible. /// - Create all structured types from the dictionaries using the converted DataTypeDefinion attribute.. /// public async Task Load(bool onlyEnumTypes = false, bool throwOnError = false) { try { // load server types IList serverEnumTypes = LoadDataTypes(DataTypeIds.Enumeration); IList serverStructTypes = onlyEnumTypes ? new List() : LoadDataTypes(DataTypeIds.Structure, true); if (DisableDataTypeDefinition || !LoadBaseDataTypes(serverEnumTypes, serverStructTypes)) { if (DisableDataTypeDictionary) { return false; } return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, true).ConfigureAwait(false); } return true; } catch (Exception ex) { Utils.Trace(ex, "Failed to load the custom type dictionary."); if (throwOnError) { throw; } return false; } } /// /// Get the types defined in this type system. /// public Type[] GetDefinedTypes() { return m_complexTypeBuilderFactory.GetTypes(); } #endregion #region Internal Properties /// /// Disable the use of DataTypeDefinition to create the complex type definition. /// internal bool DisableDataTypeDefinition { get; set; } = false; /// /// Disable the use of DataType Dictinaries to create the complex type definition. /// internal bool DisableDataTypeDictionary { get; set; } = false; #endregion #region Private Members /// /// Load listed custom types from dictionaries /// into the sessions system type factory. /// /// /// Loads all custom types at this time to avoid /// complexity when resolving type dependencies. /// private async Task LoadDictionaryDataTypes( IList serverEnumTypes, IList serverStructTypes, bool fullTypeList ) { // build a type dictionary with all known new types var allEnumTypes = fullTypeList ? serverEnumTypes : LoadDataTypes(DataTypeIds.Enumeration); var typeDictionary = new Dictionary(); // strip known types from list serverEnumTypes = RemoveKnownTypes(allEnumTypes); // load the binary schema dictionaries from the server var typeSystem = await m_session.LoadDataTypeSystem().ConfigureAwait(false); // sort dictionaries with import dependencies to the end of the list var sortedTypeSystem = typeSystem.OrderBy(t => t.Value.TypeDictionary?.Import?.Length).ToList(); bool allTypesLoaded = true; // create custom types for all dictionaries foreach (var dictionaryId in sortedTypeSystem) { try { var dictionary = dictionaryId.Value; if (dictionary.TypeDictionary == null || dictionary.TypeDictionary.Items == null) { continue; } var targetDictionaryNamespace = dictionary.TypeDictionary.TargetNamespace; var targetNamespaceIndex = m_session.NamespaceUris.GetIndex(targetDictionaryNamespace); var structureList = new List(); var enumList = new List(); // split into enumeration and structure types and sort // types with dependencies to the end of the list. SplitAndSortDictionary(dictionary, structureList, enumList); // create assembly for all types in the same module var complexTypeBuilder = m_complexTypeBuilderFactory.Create( targetDictionaryNamespace, targetNamespaceIndex, dictionary.Name); // Add all unknown enumeration types in dictionary AddEnumTypes(complexTypeBuilder, typeDictionary, enumList, allEnumTypes, serverEnumTypes); // handle structures int lastStructureCount = 0; while (structureList.Count > 0 && structureList.Count != lastStructureCount) { lastStructureCount = structureList.Count; var retryStructureList = new List(); // build structured types foreach (var item in structureList) { if (item is Opc.Ua.Schema.Binary.StructuredType structuredObject) { // note: the BrowseName contains the actual Value string of the DataType node. var nodeId = dictionary.DataTypes.FirstOrDefault(d => d.Value.BrowseName.Name == item.Name).Value; if (nodeId == null) { Utils.Trace(TraceMasks.Error, $"Skip the type definition of {item.Name} because the data type node was not found."); continue; } // find the data type node and the binary encoding id ExpandedNodeId typeId; ExpandedNodeId binaryEncodingId; DataTypeNode dataTypeNode; bool newTypeDescription = BrowseTypeIdsForDictionaryComponent( ExpandedNodeId.ToNodeId(nodeId.NodeId, m_session.NamespaceUris), out typeId, out binaryEncodingId, out dataTypeNode); if (!newTypeDescription) { Utils.Trace(TraceMasks.Error, "Skip the type definition of {0} because the data type node was not found.", item.Name); continue; } if (GetSystemType(typeId) != null) { var qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); typeDictionary[qName] = ExpandedNodeId.ToNodeId(typeId, m_session.NamespaceUris); Utils.Trace(TraceMasks.Information, "Skip the type definition of {0} because the type already exists.", item.Name); continue; } // Use DataTypeDefinition attribute, if available (>=V1.04) StructureDefinition structureDefinition = null; if (!DisableDataTypeDefinition) { structureDefinition = GetStructureDefinition(dataTypeNode); } if (structureDefinition == null) { try { // convert the binary schema description to a StructureDefinition structureDefinition = structuredObject.ToStructureDefinition( binaryEncodingId, typeDictionary, m_session.NamespaceUris); } catch (DataTypeNotFoundException typeNotFoundException) { Utils.Trace(typeNotFoundException, "Skipped the type definition of {0}. Retry in next round.", item.Name); retryStructureList.Add(item); continue; } catch (DataTypeNotSupportedException typeNotSupportedException) { Utils.Trace(typeNotSupportedException, "Skipped the type definition of {0} because it is not supported.", item.Name); continue; } catch (ServiceResultException sre) { Utils.Trace(sre, "Skip the type definition of {0}.", item.Name); continue; } } Type complexType = null; if (structureDefinition != null) { var encodingIds = BrowseForEncodings(typeId, m_supportedEncodings, out binaryEncodingId, out ExpandedNodeId xmlEncodingId); try { // build the actual .Net structured type in assembly complexType = AddStructuredType( complexTypeBuilder, structureDefinition, dataTypeNode.BrowseName, typeId, binaryEncodingId, xmlEncodingId ); } catch (DataTypeNotFoundException typeNotFoundException) { Utils.Trace(typeNotFoundException, "Skipped the type definition of {0}. Retry in next round.", item.Name); retryStructureList.Add(item); continue; } catch (DataTypeNotSupportedException typeNotSupportedException) { Utils.Trace(typeNotSupportedException, "Skipped the type definition of {0} because it is not supported.", item.Name); continue; } // Add new type to factory if (complexType != null) { // match namespace and add new type to type factory foreach (var encodingId in encodingIds) { AddEncodeableType(encodingId, complexType); } AddEncodeableType(typeId, complexType); var qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); typeDictionary[qName] = ExpandedNodeId.ToNodeId(typeId, m_session.NamespaceUris); } } if (complexType == null) { retryStructureList.Add(item); Utils.Trace(TraceMasks.Error, "Skipped the type definition of {0}. Retry in next round.", item.Name); } } } structureList = retryStructureList; } allTypesLoaded = allTypesLoaded && structureList.Count == 0; } catch (ServiceResultException sre) { Utils.Trace(sre, "Unexpected error processing {0}.", dictionaryId.Value.Name); } } return allTypesLoaded; } /// /// Load all custom types with DataTypeDefinition into the type factory. /// /// true if all types were loaded, false otherwise private bool LoadBaseDataTypes( IList serverEnumTypes, IList serverStructTypes ) { bool repeatDataTypeLoad = false; IList enumTypesToDoList = new List(); IList structTypesToDoList = new List(); do { // strip known types serverEnumTypes = RemoveKnownTypes(serverEnumTypes); serverStructTypes = RemoveKnownTypes(serverStructTypes); repeatDataTypeLoad = false; try { enumTypesToDoList = LoadBaseEnumDataTypes(serverEnumTypes); structTypesToDoList = LoadBaseStructureDataTypes(serverStructTypes); } catch (DataTypeNotFoundException dtnfex) { // add missing type to list var dataTypeNode = m_session.NodeCache.Find(dtnfex.nodeId); if (dataTypeNode != null) { AddEnumerationOrStructureType(dataTypeNode, serverEnumTypes, serverStructTypes); repeatDataTypeLoad = true; } } } while (repeatDataTypeLoad); // all types loaded return enumTypesToDoList.Count == 0 && structTypesToDoList.Count == 0; } /// /// Load all custom types with DataTypeDefinition into the type factory. /// /// true if all types were loaded, false otherwise private IList LoadBaseEnumDataTypes( IList serverEnumTypes ) { // strip known types serverEnumTypes = RemoveKnownTypes(serverEnumTypes); // add new enum Types for all namespaces var enumTypesToDoList = new List(); int namespaceCount = m_session.NamespaceUris.Count; // create enumeration types for all namespaces for (uint i = 0; i < namespaceCount; i++) { IComplexTypeBuilder complexTypeBuilder = null; var enumTypes = serverEnumTypes.Where(node => node.NodeId.NamespaceIndex == i).ToList(); if (enumTypes.Count != 0) { if (complexTypeBuilder == null) { string targetNamespace = m_session.NamespaceUris.GetString(i); complexTypeBuilder = m_complexTypeBuilderFactory.Create( targetNamespace, (int)i); } foreach (var enumType in enumTypes) { var newType = AddEnumType(complexTypeBuilder, enumType as DataTypeNode); if (newType != null) { // match namespace and add to type factory AddEncodeableType(enumType.NodeId, newType); } else { enumTypesToDoList.Add(enumType); } } } } // all types loaded, return remaining return enumTypesToDoList; } /// /// Load all structure custom types with DataTypeDefinition into the type factory. /// /// true if all types were loaded, false otherwise private IList LoadBaseStructureDataTypes( IList serverStructTypes ) { // strip known types serverStructTypes = RemoveKnownTypes(serverStructTypes); // add new enum Types for all namespaces int namespaceCount = m_session.NamespaceUris.Count; bool retryAddStructType; var structTypesToDoList = new List(); var structTypesWorkList = serverStructTypes; // create structured types for all namespaces do { retryAddStructType = false; for (uint i = 0; i < namespaceCount; i++) { IComplexTypeBuilder complexTypeBuilder = null; var structTypes = structTypesWorkList.Where(node => node.NodeId.NamespaceIndex == i).ToList(); if (structTypes.Count != 0) { if (complexTypeBuilder == null) { string targetNamespace = m_session.NamespaceUris.GetString(i); complexTypeBuilder = m_complexTypeBuilderFactory.Create( targetNamespace, (int)i); } foreach (INode structType in structTypes) { Type newType = null; if (!(structType is DataTypeNode dataTypeNode)) { continue; } var structureDefinition = GetStructureDefinition(dataTypeNode); if (structureDefinition != null) { var encodingIds = BrowseForEncodings(structType.NodeId, m_supportedEncodings, out ExpandedNodeId binaryEncodingId, out ExpandedNodeId xmlEncodingId); try { newType = AddStructuredType( complexTypeBuilder, structureDefinition, dataTypeNode.BrowseName, structType.NodeId, binaryEncodingId, xmlEncodingId ); } catch (DataTypeNotFoundException dtnfex) { var typeMatch = structTypesWorkList.FirstOrDefault(n => n.NodeId == dtnfex.nodeId); if (typeMatch == null) { throw; } else { // known missing type, retry on next round Utils.Trace(dtnfex, "Skipped the type definition of {0}. Retry in next round.", dataTypeNode.BrowseName.Name); retryAddStructType = true; } } catch (DataTypeNotSupportedException dtnsex) { Utils.Trace(dtnsex, "Skipped the type definition of {0} because it is not supported.", dataTypeNode.BrowseName.Name); continue; } catch { // creating the new type failed, likely a missing dependency, retry later retryAddStructType = true; } if (newType != null) { foreach (var encodingId in encodingIds) { AddEncodeableType(encodingId, newType); } AddEncodeableType(structType.NodeId, newType); } } if (newType == null) { structTypesToDoList.Add(structType); } } } } // due to type dependencies, retry missing types until there is no more progress if (retryAddStructType && structTypesWorkList.Count != structTypesToDoList.Count) { structTypesWorkList = structTypesToDoList; structTypesToDoList = new List(); } } while (retryAddStructType); // all types loaded return structTypesToDoList; } /// /// Return the structure definition from a DataTypeDefinition /// private StructureDefinition GetStructureDefinition(DataTypeNode dataTypeNode) { if (dataTypeNode.DataTypeDefinition?.Body is StructureDefinition structureDefinition) { // Validate the DataTypeDefinition structure, // but not if the type is supported if (structureDefinition.Fields == null || structureDefinition.BaseDataType.IsNullNodeId || structureDefinition.BinaryEncodingId.IsNull) { return null; } // Validate the structure according to Part3, Table 36 foreach (var field in structureDefinition.Fields) { // validate if the DataTypeDefinition is correctly // filled out, some servers don't do it yet... if (field.BinaryEncodingId.IsNull || field.DataType.IsNullNodeId || field.TypeId.IsNull || field.Name == null) { return null; } if (!(field.ValueRank == -1 || field.ValueRank >= 1)) { return null; } if (structureDefinition.StructureType == StructureType.Structure && field.IsOptional) { return null; } } return structureDefinition; } return null; } /// /// Helper to ensure the expanded nodeId contains a valid namespaceUri. /// /// The expanded nodeId. /// The normalized expanded nodeId. private ExpandedNodeId NormalizeExpandedNodeId(ExpandedNodeId expandedNodeId) { var nodeId = ExpandedNodeId.ToNodeId(expandedNodeId, m_session.NamespaceUris); return NodeId.ToExpandedNodeId(nodeId, m_session.NamespaceUris); } /// /// Browse for the type and encoding id for a dictionary component. /// /// /// According to Part 5 Annex D, servers shall provide the bi-directional /// references between data types, data type encodings, data type description /// and data type dictionary. /// To find the typeId and encodingId for a dictionary type definition: /// i) inverse browse the description to get the encodingid /// ii) from the description inverse browse for encoding /// to get the subtype typeid /// iii) load the DataType node /// /// /// /// /// /// true if successful, false otherwise private bool BrowseTypeIdsForDictionaryComponent( NodeId nodeId, out ExpandedNodeId typeId, out ExpandedNodeId encodingId, out DataTypeNode dataTypeNode) { typeId = ExpandedNodeId.Null; encodingId = ExpandedNodeId.Null; dataTypeNode = null; var references = m_session.NodeCache.FindReferences( nodeId, ReferenceTypeIds.HasDescription, true, false ); if (references.Count == 1) { encodingId = references[0].NodeId; references = m_session.NodeCache.FindReferences( encodingId, ReferenceTypeIds.HasEncoding, true, false ); encodingId = NormalizeExpandedNodeId(encodingId); if (references.Count == 1) { typeId = references[0].NodeId; dataTypeNode = m_session.NodeCache.Find(typeId) as DataTypeNode; typeId = NormalizeExpandedNodeId(typeId); return true; } } return false; } /// /// Browse for the property. /// /// /// Browse for property (type description) of an enum datatype. /// /// /// private INode BrowseForSingleProperty( ExpandedNodeId nodeId) { var references = m_session.NodeCache.FindReferences( nodeId, ReferenceTypeIds.HasProperty, false, false ); return references.FirstOrDefault(); } /// /// Browse for the encodings of a type. /// /// /// Browse for binary encoding of a structure datatype. /// private IList BrowseForEncodings( ExpandedNodeId nodeId, string[] supportedEncodings, out ExpandedNodeId binaryEncodingId, out ExpandedNodeId xmlEncodingId) { var references = m_session.NodeCache.FindReferences( nodeId, ReferenceTypeIds.HasEncoding, false, false ); binaryEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultBinary)?.NodeId; binaryEncodingId = NormalizeExpandedNodeId(binaryEncodingId); xmlEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultXml)?.NodeId; xmlEncodingId = NormalizeExpandedNodeId(xmlEncodingId); return references.Where(r => supportedEncodings.Contains(r.BrowseName.Name)) .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris)).ToList(); } /// /// Load all subTypes and optionally nested subtypes of a type definition. /// Filter for all subtypes or only subtypes outside the default namespace. /// private IList LoadDataTypes( ExpandedNodeId dataType, bool nestedSubTypes = false, bool addRootNode = false, bool filterUATypes = true) { var result = new List(); var nodesToBrowse = new ExpandedNodeIdCollection { dataType }; if (addRootNode) { var rootNode = m_session.NodeCache.Find(dataType); if (!(rootNode is DataTypeNode)) { throw new ServiceResultException("Root Node is not a DataType node."); } result.Add(rootNode); } while (nodesToBrowse.Count > 0) { var nextNodesToBrowse = new ExpandedNodeIdCollection(); foreach (var node in nodesToBrowse) { var response = m_session.NodeCache.FindReferences( node, ReferenceTypeIds.HasSubtype, false, false); if (nestedSubTypes) { nextNodesToBrowse.AddRange(response.Select(r => r.NodeId).ToList()); } if (filterUATypes) { // filter out default namespace result.AddRange(response.Where(rd => rd.NodeId.NamespaceIndex != 0)); } else { result.AddRange(response); } } nodesToBrowse = nextNodesToBrowse; } return result; } /// /// Add data type to enumeration or structure base type list depending on supertype. /// private void AddEnumerationOrStructureType(INode dataTypeNode, IList serverEnumTypes, IList serverStructTypes) { NodeId superType = ExpandedNodeId.ToNodeId(dataTypeNode.NodeId, m_session.NamespaceUris); while (true) { superType = m_session.NodeCache.FindSuperType(superType); if (superType.IsNullNodeId) { throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"SuperType for {dataTypeNode.NodeId} not found."); } if (superType == DataTypeIds.Enumeration) { serverEnumTypes.Insert(0, dataTypeNode); break; } else if (superType == DataTypeIds.Structure) { serverStructTypes.Insert(0, dataTypeNode); break; } else if (TypeInfo.GetBuiltInType(superType) != BuiltInType.Null) { break; } } } /// /// Remove all known types in the type factory from a list of DataType nodes. /// private IList RemoveKnownTypes(IList nodeList) { return nodeList.Where( node => GetSystemType(node.NodeId) == null).ToList(); } /// /// Get the factory system type for an expanded node id. /// private Type GetSystemType(ExpandedNodeId nodeId) { if (!nodeId.IsAbsolute) { nodeId = NormalizeExpandedNodeId(nodeId); } return m_session.Factory.GetSystemType(nodeId); } /// /// Add an enum type defined in a binary schema dictionary. /// private void AddEnumTypes( IComplexTypeBuilder complexTypeBuilder, Dictionary typeDictionary, IList enumList, IList allEnumerationTypes, IList enumerationTypes ) { foreach (var item in enumList) { Type newType = null; DataTypeNode enumDescription = null; DataTypeNode enumType = enumerationTypes.FirstOrDefault(node => node.BrowseName.Name == item.Name && (node.BrowseName.NamespaceIndex == complexTypeBuilder.TargetNamespaceIndex || complexTypeBuilder.TargetNamespaceIndex == -1)) as DataTypeNode; if (enumType != null) { enumDescription = enumType; // try dictionary enum definition if (item is Schema.Binary.EnumeratedType enumeratedObject) { // 1. use Dictionary entry newType = complexTypeBuilder.AddEnumType(enumeratedObject); } if (newType == null) { if (m_session.NodeCache.Find(enumType.NodeId) is DataTypeNode dataTypeNode) { if (dataTypeNode.DataTypeDefinition != null) { // 2. use DataTypeDefinition newType = complexTypeBuilder.AddEnumType(enumType.BrowseName.Name, dataTypeNode.DataTypeDefinition); } else { // browse for EnumFields or EnumStrings property var property = BrowseForSingleProperty(enumType.NodeId); var enumArray = m_session.ReadValue( ExpandedNodeId.ToNodeId(property.NodeId, m_session.NamespaceUris)); if (enumArray.Value is ExtensionObject[] extensionObject) { // 3. use EnumValues newType = complexTypeBuilder.AddEnumType(enumType.BrowseName.Name, extensionObject); } else if (enumArray.Value is LocalizedText[] localizedText) { // 4. use EnumStrings newType = complexTypeBuilder.AddEnumType(enumType.BrowseName.Name, localizedText); } } } } } else { enumDescription = allEnumerationTypes.FirstOrDefault(node => node.BrowseName.Name == item.Name && (node.BrowseName.NamespaceIndex == complexTypeBuilder.TargetNamespaceIndex || complexTypeBuilder.TargetNamespaceIndex == -1)) as DataTypeNode; } if (enumDescription != null) { var qName = new XmlQualifiedName(item.Name, complexTypeBuilder.TargetNamespace); typeDictionary[qName] = enumDescription.NodeId; } if (newType != null) { // match namespace and add to type factory AddEncodeableType(enumType.NodeId, newType); } } } /// /// Helper to add new type with absolute ExpandedNodeId. /// private void AddEncodeableType(ExpandedNodeId nodeId, Type type) { if (NodeId.IsNull(nodeId) || type == null) { return; } var internalNodeId = NormalizeExpandedNodeId(nodeId); Utils.TraceDebug($"Adding Type {type.FullName} as: {internalNodeId}"); m_session.Factory.AddEncodeableType(internalNodeId, type); } /// /// Add an enum type defined in a DataType node. /// private Type AddEnumType( IComplexTypeBuilder complexTypeBuilder, DataTypeNode enumTypeNode ) { Type newType = null; if (enumTypeNode != null) { QualifiedName name = enumTypeNode.BrowseName; if (enumTypeNode.DataTypeDefinition != null) { // 1. use DataTypeDefinition newType = complexTypeBuilder.AddEnumType(name, enumTypeNode.DataTypeDefinition); } else { // browse for EnumFields or EnumStrings property var property = BrowseForSingleProperty(enumTypeNode.NodeId); if (property != null) { var enumArray = m_session.ReadValue( ExpandedNodeId.ToNodeId(property.NodeId, m_session.NamespaceUris)); if (enumArray.Value is ExtensionObject[] extensionObject) { // 2. use EnumValues newType = complexTypeBuilder.AddEnumType(name, extensionObject); } else if (enumArray.Value is LocalizedText[] localizedText) { // 3. use EnumStrings newType = complexTypeBuilder.AddEnumType(name, localizedText); } } } } return newType; } /// /// Add structured type to assembly with StructureDefinition. /// private Type AddStructuredType( IComplexTypeBuilder complexTypeBuilder, StructureDefinition structureDefinition, QualifiedName typeName, ExpandedNodeId complexTypeId, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId ) { // check all types var typeList = new List(); foreach (StructureField field in structureDefinition.Fields) { var newType = GetFieldType(field); if (newType == null) { throw new DataTypeNotFoundException(field.DataType); } typeList.Add(newType); } var fieldBuilder = complexTypeBuilder.AddStructuredType( typeName, structureDefinition ); fieldBuilder.AddTypeIdAttribute(complexTypeId, binaryEncodingId, xmlEncodingId); int order = 1; var typeListEnumerator = typeList.GetEnumerator(); foreach (StructureField field in structureDefinition.Fields) { typeListEnumerator.MoveNext(); fieldBuilder.AddField(field, typeListEnumerator.Current, order); order++; } return fieldBuilder.CreateType(); } /// /// Determine the type of a field in a StructureField definition. /// private Type GetFieldType(StructureField field) { Type collectionType = null; if (field.ValueRank != ValueRanks.Scalar && field.ValueRank != ValueRanks.OneDimension) { throw new DataTypeNotSupportedException(field.DataType, $"The ValueRank {field.ValueRank} is not supported."); } Type fieldType = field.DataType.NamespaceIndex == 0 ? Opc.Ua.TypeInfo.GetSystemType(field.DataType, m_session.Factory) : GetSystemType(field.DataType); if (fieldType == null) { var superType = GetBuiltInSuperType(field.DataType); if (superType?.IsNullNodeId == false) { field.DataType = superType; return GetFieldType(field); } return null; } if (field.DataType.NamespaceIndex == 0) { if (field.ValueRank == ValueRanks.OneDimension) { if (fieldType == typeof(Byte[])) { collectionType = typeof(ByteStringCollection); } else if (fieldType == typeof(Single)) { collectionType = typeof(FloatCollection); } else { var assemblyQualifiedName = typeof(StatusCode).Assembly; String collectionClassName = "Opc.Ua." + fieldType.Name + "Collection, " + assemblyQualifiedName; collectionType = Type.GetType(collectionClassName); } } } else { if (field.ValueRank == ValueRanks.OneDimension) { String collectionClassName = (fieldType.Namespace != null) ? fieldType.Namespace + "." : ""; collectionClassName += fieldType.Name + "Collection, " + fieldType.Assembly; collectionType = Type.GetType(collectionClassName); } } if (field.ValueRank == ValueRanks.OneDimension) { fieldType = collectionType ?? fieldType.MakeArrayType(); } return fieldType; } /// /// Find superType for a datatype. /// private NodeId GetBuiltInSuperType(NodeId dataType) { var superType = dataType; while (true) { superType = m_session.NodeCache.FindSuperType(superType); if (superType?.IsNullNodeId != false) { return null; } if (superType.NamespaceIndex == 0) { if (superType == DataTypeIds.Enumeration || superType == DataTypeIds.Structure) { return null; } break; } } return superType; } /// /// Split the dictionary types into a list of structures and enumerations. /// Sort the structures by dependencies, with structures with dependent /// types at the end of the list, so they can be added to the factory in order. /// private void SplitAndSortDictionary( DataDictionary dictionary, List structureList, List enumList ) { foreach (var item in dictionary.TypeDictionary.Items) { if (item is Opc.Ua.Schema.Binary.StructuredType structuredObject) { var dependentFields = structuredObject.Field.Where(f => f.TypeName.Namespace == dictionary.TypeDictionary.TargetNamespace); if (!dependentFields.Any()) { structureList.Insert(0, structuredObject); } else { structureList.Add(structuredObject); } } else if (item is Opc.Ua.Schema.Binary.EnumeratedType) { enumList.Add(item); } else if (item is Opc.Ua.Schema.Binary.OpaqueType) { // TODO: Opaque types not supported yet } else { throw new ServiceResultException(StatusCodes.BadUnexpectedError, $"Unexpected Type in binary schema: {item.GetType().Name}."); } } } #endregion #region Private Fields private Session m_session; private IComplexTypeFactory m_complexTypeBuilderFactory; private readonly string[] m_supportedEncodings = new string[] { BrowseNames.DefaultBinary, BrowseNames.DefaultXml, BrowseNames.DefaultJson }; #endregion } }//namespace