/* ======================================================================== * 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.IO; using Opc.Ua.Schema.Model; using Opc.Ua.Types; namespace Opc.Ua.SourceGeneration { /// /// Generates binary schema files from model designs. /// internal sealed class BinarySchemaGenerator : IGenerator { /// /// Initializes a new instance of the class. /// public BinarySchemaGenerator(IGeneratorContext context) { m_context = context ?? throw new ArgumentNullException(nameof(context)); } /// /// Validate schema output after generation. /// public bool ValidateOutput { get; set; } /// /// Generates the binary schema file for the supplied nodes. /// public IEnumerable Emit() { string namespacePrefix = m_context.ModelDesign.TargetNamespace.Prefix; string schemaFile = Path.Combine( m_context.OutputFolder, CoreUtils.Format("{0}.Types.bsd", namespacePrefix)); WriteTemplate_BinarySchema(schemaFile); if (ValidateOutput) { // Validate generated file var validator = new Schema.Binary.BinarySchemaValidator(m_context.FileSystem); validator.Validate(schemaFile); } return [schemaFile.AsTextFileResource(namespacePrefix)]; } public void WriteTemplate_BinarySchema(string fileName) { using TextWriter writer = m_context.FileSystem.CreateTextWriter(fileName); using var templateWriter = new TemplateWriter(writer); var template = new Template(templateWriter, BinarySchemaTemplates.File); string targetNamespace = m_context.ModelDesign.TargetNamespace.Value; template.AddReplacement(Tokens.DictionaryUri, targetNamespace); template.AddReplacement( Tokens.XmlnsS0ListOfNamespaces, m_context.ModelDesign.Namespaces, LoadTemplate_Imports); template.AddReplacement( Tokens.Imports, m_context.ModelDesign.Namespaces, LoadTemplate_Imports); template.AddReplacement( Tokens.BuiltInTypes, BinarySchemaTemplates.BuiltInTypes, [m_context.ModelDesign], LoadTemplate_DataType, WriteTemplate_DataType); template.AddReplacement( Tokens.ListOfTypes, BinarySchemaTemplates.OpaqueType, GetListOfTypes(), LoadTemplate_DataType, WriteTemplate_DataType); template.Render(); } private TemplateString LoadTemplate_Imports(ILoadContext context) { if (context.Target is not Namespace ns) { return null; } if (ns.Value == m_context.ModelDesign.TargetNamespace.Value) { return null; } if (context.Token == Tokens.XmlnsS0ListOfNamespaces) { if (ns.Value == Namespaces.OpcUa) { return null; } context.Out.WriteLine( """ xmlns:{0}="{1}" """, m_context.ModelDesign.Namespaces.GetXmlNamespacePrefix(ns.Value), ns.Value); return null; } context.Out.WriteLine( "", ns.Value, m_context.ModelDesign.Namespaces.GetNamespacePrefix(ns.Value)); return null; } private TemplateString LoadTemplate_DataType(ILoadContext context) { if (context.Target is IModelDesign design) { if (design.TargetNamespace.Value == Namespaces.OpcUa) { return context.TemplateString; } return null; } if (context.Target is not DataTypeDesign dataType) { return null; } #if TRUE // don't write built-in types already in the template. if (dataType.NumericId < 256 && dataType.SymbolicId.Namespace == Namespaces.OpcUa) { switch (dataType.NumericId) { case DataTypes.PermissionType: case DataTypes.AccessRestrictionType: case DataTypes.RolePermissionType: case DataTypes.StructureDefinition: case DataTypes.StructureField: case DataTypes.StructureType: case DataTypes.EnumDefinition: case DataTypes.EnumField: case DataTypes.DataTypeDefinition: case DataTypes.Enumeration: case DataTypes.Union: break; default: return null; } } #endif if (dataType.Purpose == DataTypePurpose.CodeGenerator) { return null; } BasicDataType basicType = dataType.BasicDataType; if (basicType == BasicDataType.UserDefined) { return BinarySchemaTemplates.ComplexType; } if (basicType == BasicDataType.Enumeration) { return BinarySchemaTemplates.EnumeratedType; } return BinarySchemaTemplates.OpaqueType; } private bool WriteTemplate_DataType(IWriteContext context) { if (context.Target is IModelDesign design) { if (design.TargetNamespace.Value == Namespaces.OpcUa) { return context.Template.Render(); } return false; } if (context.Target is not DataTypeDesign dataType) { return false; } context.Template.AddReplacement(Tokens.TypeName, dataType.SymbolicName.Name); if (dataType.BasicDataType == BasicDataType.UserDefined) { context.Template.AddReplacement(Tokens.BaseType, (dataType.BaseTypeNode as DataTypeDesign).GetBinaryDataType( m_context.ModelDesign.TargetNamespace.Value, m_context.ModelDesign.Namespaces)); } List fields = []; var parents = new Stack(); for (DataTypeDesign parent = dataType; parent != null; parent = parent.BaseTypeNode as DataTypeDesign) { if (parent.Fields != null) { parents.Push(parent); } } while (parents.Count > 0) { DataTypeDesign parent = parents.Pop(); foreach (Parameter field in parent.Fields) { if (m_context.ModelDesign.IsExcluded(field)) { continue; } if (ReferenceEquals(dataType, parent)) { fields.Add(field); continue; } fields.Add(new Parameter { DataType = field.DataType, DataTypeNode = field.DataTypeNode, Description = field.Description, Identifier = field.Identifier, IdentifierInName = field.IdentifierInName, IdentifierSpecified = field.IdentifierSpecified, IsInherited = true, Name = field.Name, Parent = field.Parent, ValueRank = field.ValueRank, ArrayDimensions = field.ArrayDimensions, AllowSubTypes = field.AllowSubTypes, IsOptional = field.IsOptional, BitMask = field.BitMask, DefaultValue = field.DefaultValue, ReleaseStatus = field.ReleaseStatus }); } } if (dataType.BasicDataType == BasicDataType.Enumeration) { uint lengthInBits = 32; bool isOptionSet = false; if (dataType.IsOptionSet) { isOptionSet = true; switch (dataType.BaseType.Name) { case "SByte": case "Byte": lengthInBits = 8; break; case "Int16": case "UInt16": lengthInBits = 16; break; case "Int32": case "UInt32": lengthInBits = 32; break; case "Int64": case "UInt64": lengthInBits = 64; break; } fields.Insert(0, new Parameter { Name = "None", Identifier = 0, IdentifierSpecified = true, DataType = fields[0].DataType, DataTypeNode = fields[0].DataTypeNode, Parent = fields[0].Parent }); } context.Template.AddReplacement(Tokens.LengthInBits, lengthInBits); context.Template.AddReplacement( Tokens.IsOptionSet, isOptionSet ? " IsOptionSet=\"true\"" : string.Empty); } context.Template.AddReplacement( Tokens.Documentation, [dataType], LoadTemplate_BinaryDocumentation); context.Template.AddReplacement( Tokens.ListOfFields, fields, LoadTemplate_Field); return context.Template.Render(); } private TemplateString LoadTemplate_Field(ILoadContext context) { if (context.Target is not Parameter field) { return null; } if (field.Parent is not DataTypeDesign dataType) { return null; } BasicDataType basicType = dataType.BasicDataType; string fieldDataType = field.DataTypeNode.GetBinaryDataType( m_context.ModelDesign.TargetNamespace.Value, m_context.ModelDesign.Namespaces); if (field.AllowSubTypes) { fieldDataType = "ua:ExtensionObject"; } if (basicType == BasicDataType.Enumeration) { context.Out.WriteLine( "", field.Name, field.Identifier); return null; } if (field.ValueRank != ValueRank.Scalar) { context.Out.WriteLine( "", field.Name); context.Out.WriteLine( "", field.Name, fieldDataType); return null; } if (field.IsInherited) { context.Out.WriteLine( "", field.Name, fieldDataType, (field.Parent as DataTypeDesign).GetBinaryDataType( m_context.ModelDesign.TargetNamespace.Value, m_context.ModelDesign.Namespaces)); } else { context.Out.WriteLine( "", field.Name, fieldDataType); } return null; } private TemplateString LoadTemplate_BinaryDocumentation(ILoadContext context) { if (context.Target is not DataTypeDesign dataType) { return null; } if (dataType.Description == null || dataType.Description.IsAutogenerated) { return null; } context.Out.WriteLine( "{0}", dataType.Description.Value); return context.TemplateString; } private IReadOnlyList GetListOfTypes() { return [.. m_context.ModelDesign.GetNodeDesigns()]; } private readonly IGeneratorContext m_context; } }