Files
UA-.NETStandard/tools/Opc.Ua.SourceGeneration.Core/Dependency/ModelDependencyV1.cs
romanett 9b034f8291 Enable nullable reference types centrally; migrate all unset and annotations-only projects (#4700)
# Description

Nullable reference types are now enabled once, in `common.props`, and
every project in the repository builds warning-clean with them.

**Configuration**
- `<Nullable>enable</Nullable>` lives in `common.props`. All per-project
`<Nullable>` settings are removed: 178 project files (enable,
annotations, and the `disable` in the three Aot test projects).
- 419 per-file `#nullable enable`/`restore` directives and the 4
`#nullable disable` directives are removed.
- Kept on purpose:
- The 5 `<auto-generated>` schema files (`UANodeSet.cs`, `UA Type
Dictionary.cs`, `OPCBinarySchema.cs`, `SecuredApplication.cs`,
`PInvokeHelper.cs`) keep `#nullable enable`. The compiler ignores the
project setting in generated code, so the directive is required there.
- `tests/Opc.Ua.Interop.LegacyPeer` keeps `disable`. It builds against
the 1.5.378 packages and does not import the repository props.
- `docs/DeveloperGuide.md` and `.github/copilot-instructions.md` now
describe the central setting.

**Migration** (the 43 unset and 13 annotations-only projects, roughly
10k diagnostics)
- **Production code: annotated at the declarations.** Covers
`tools/Opc.Ua.SourceGeneration.Core`, `src/Opc.Ua.Lds.Server` and the
LDS sample.
- Members, parameters and returns are nullable where null actually
flows.
- Null-check helpers carry `[NotNullWhen]` (`IsNull`, `IsValidXmlName`,
`IsMethodTypeDesign`).
- `CoreUtils.Format`, `Utils.Format`, `ITemplateWriter.WriteLine` take
`params object?[]`. `Template.AddReplacement` takes `string?` (rendering
already skips null replacements).
- `IRegisteredServerStore.RegisterAsync` returns
`Task<RegistrationEntry?>`: it returns null when a registration is
removed.
- Dereferences backed by an invariant (for example, the validated model)
use `!`, so runtime behaviour is unchanged.
- **Behavioural changes in production code** (small, deliberate):
- `NodeSetToModelDesign` throws `InvalidOperationException` when the
file is not a NodeSet. Before, it threw a NullReferenceException on the
next line.
- `ModelDesignValidator` starts its lookup tables empty instead of null.
A redundant `if (m_nodes == null) return;` guard is gone.
- **Test code: minimal annotations.**
  - `?` on values that can be null, `!` where the test asserts non-null.
  - `= null!` for fixture members assigned in SetUp.
- `TryGetStructure<T>(out T? x)` where the inferred `T?` broke the
`IEncodeable` constraint.
- A few redundant `== null` checks on structs (`NodeId`, `ArrayOf<T>`)
folded into the adjacent `IsNull`/`Count` checks.

**Notes for reviewers**
- The diff is large (~1,500 files) but almost entirely mechanical: `?`,
`!` and directive removal. The non-annotation changes are listed above.
- Known follow-up, not done here:
`IModelDesign.Namespaces/TargetNamespace/NamespaceUris` stay
non-nullable even though the validator returns null before it loads a
dictionary. Making them nullable would ripple through every generator
call site.

## Related Issues

- Fixes #4439

## Checklist

- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [ ] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

The "no new warnings" box covers net10.0 and net48: full non-incremental
builds of `UA.slnx`, `tools/SourceGeneration.slnx`,
`tools/MigrationAnalyzer.slnx` and the Aot test projects finished with 0
warnings and 0 errors under the repository's warnings-as-errors
settings. net9.0 and net8.0 also had no nullable diagnostics, but they
were not rebuilt after the final fixes. Tests have not been run locally
yet.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:48:13 +02:00

1544 lines
61 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2026 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 System.IO.Compression;
using System.Text;
using System.Xml;
namespace Opc.Ua.SourceGeneration.Dependency
{
/// <summary>
/// Kind of node carried in a <see cref="ModelDependencyV1"/>.
/// </summary>
public enum DependencyNodeKind : byte
{
/// <summary>
/// Unknown — invalid in a well-formed dependency payload.
/// </summary>
Unknown = 0,
/// <summary>
/// ObjectType.
/// </summary>
ObjectType = 1,
/// <summary>
/// VariableType.
/// </summary>
VariableType = 2,
/// <summary>
/// ReferenceType.
/// </summary>
ReferenceType = 3,
/// <summary>
/// DataType.
/// </summary>
DataType = 4,
/// <summary>
/// Method (when carried as standalone declaration).
/// </summary>
Method = 5
}
/// <summary>
/// Reduced view of a <c>DataTypeDesign</c> field carried in a dependency payload.
/// </summary>
public readonly record struct DependencyDataField
{
/// <summary>
/// Field name.
/// </summary>
public string Name { get; }
/// <summary>
/// DataType name (qualified).
/// </summary>
public string DataTypeName { get; }
/// <summary>
/// DataType namespace URI.
/// </summary>
public string DataTypeNamespace { get; }
/// <summary>Value rank (<see cref="ValueRanks"/>).</summary>
public int ValueRank { get; }
/// <summary>
/// True if the structure field is optional (encoding mask bit).
/// Carried in the structure field trailer.
/// </summary>
public bool IsOptional { get; init; }
/// <summary>
/// True if the structure field allows subtypes of its data type
/// (encoded as ExtensionObject). Carried in the structure field
/// trailer.
/// </summary>
public bool AllowSubTypes { get; init; }
/// <summary>
/// The declared array dimensions ("0,0,0"), or <c>null</c>.
/// Carried in the structure field trailer.
/// </summary>
public string? ArrayDimensions { get; init; }
/// <summary>
/// Constructor.
/// </summary>
public DependencyDataField(string name, string dataTypeName, string dataTypeNamespace, int valueRank)
{
Name = name ?? string.Empty;
DataTypeName = dataTypeName ?? string.Empty;
DataTypeNamespace = dataTypeNamespace ?? string.Empty;
ValueRank = valueRank;
}
}
/// <summary>
/// A method input or output argument carried in a dependency payload.
/// </summary>
public readonly record struct DependencyMethodArg
{
/// <summary>
/// Argument name.
/// </summary>
public string Name { get; }
/// <summary>
/// DataType name (qualified).
/// </summary>
public string DataTypeName { get; }
/// <summary>
/// DataType namespace URI.
/// </summary>
public string DataTypeNamespace { get; }
/// <summary>
/// Value rank.
/// </summary>
public int ValueRank { get; }
/// <summary>
/// Constructor.
/// </summary>
public DependencyMethodArg(string name, string dataTypeName, string dataTypeNamespace, int valueRank)
{
Name = name ?? string.Empty;
DataTypeName = dataTypeName ?? string.Empty;
DataTypeNamespace = dataTypeNamespace ?? string.Empty;
ValueRank = valueRank;
}
}
/// <summary>
/// Reduced view of an <c>InstanceDesign</c> child carried in a
/// dependency payload type. Carries enough metadata for downstream
/// generators to set <c>OveriddenNode</c> + <c>TypeDefinitionNode</c>
/// / <c>DataTypeNode</c> on consumer's re-declared inherited
/// members.
/// </summary>
public sealed class DependencyChild
{
/// <summary>
/// Browse name of the child.
/// </summary>
public string BrowseName { get; set; } = string.Empty;
/// <summary>
/// Namespace URI qualifying <see cref="BrowseName"/>. Empty when the
/// declaring type's own namespace qualifies it. A child re-declared
/// from a base type in another namespace keeps the namespace of its
/// original declaration here, so a consumer that instantiates the
/// derived type on its own still emits <c>0:EngineeringUnits</c>
/// rather than re-qualifying it into the declaring model.
/// </summary>
public string BrowseNameNamespace { get; set; } = string.Empty;
/// <summary>
/// Symbolic name (often equal to BrowseName).
/// </summary>
public string SymbolicName { get; set; } = string.Empty;
/// <summary>
/// TypeDefinition name (for all kinds). Empty when not declared.
/// </summary>
public string TypeDefinitionName { get; set; } = string.Empty;
/// <summary>
/// TypeDefinition namespace URI.
/// </summary>
public string TypeDefinitionNamespace { get; set; } = string.Empty;
/// <summary>
/// DataType name (variables only). Empty when not applicable.
/// </summary>
public string DataTypeName { get; set; } = string.Empty;
/// <summary>
/// DataType namespace URI (variables only).
/// </summary>
public string DataTypeNamespace { get; set; } = string.Empty;
/// <summary>Value rank (variables only; <c>Scalar = 0</c> per <c>Opc.Ua.ValueRanks</c>).</summary>
public int ValueRank { get; set; }
/// <summary>
/// Modelling rule (0=None, 1=Mandatory, 2=Optional, 3=OptionalPlaceholder, 4=MandatoryPlaceholder, 5=ExposesItsArray).
/// </summary>
public byte ModellingRule { get; set; }
/// <summary>
/// Instance kind: 1=Object 2=Variable 3=Property 4=Method.
/// </summary>
public byte InstanceKind { get; set; }
/// <summary>
/// Name of the reference type binding the child to its parent. Empty
/// when the kind default applies (<c>HasProperty</c> for a property,
/// <c>HasComponent</c> otherwise).
/// </summary>
public string ReferenceTypeName { get; set; } = string.Empty;
/// <summary>
/// Namespace URI of <see cref="ReferenceTypeName"/>.
/// </summary>
public string ReferenceTypeNamespace { get; set; } = string.Empty;
/// <summary>
/// ModelDesign access level value (variables only).
/// </summary>
public byte AccessLevel { get; set; }
/// <summary>
/// Whether <see cref="AccessLevel"/> was explicitly specified.
/// </summary>
public bool AccessLevelSpecified { get; set; }
/// <summary>
/// Exact OPC UA AccessLevel bitmask imported from NodeSet2.
/// </summary>
public uint? RawAccessLevel { get; set; }
/// <summary>
/// Exact OPC UA UserAccessLevel bitmask imported from NodeSet2.
/// </summary>
public uint? RawUserAccessLevel { get; set; }
/// <summary>
/// Minimum sampling interval (variables only).
/// </summary>
public int MinimumSamplingInterval { get; set; }
/// <summary>
/// Whether <see cref="MinimumSamplingInterval"/> was explicitly specified.
/// </summary>
public bool MinimumSamplingIntervalSpecified { get; set; }
/// <summary>
/// Whether historical data is collected (variables only).
/// </summary>
public bool Historizing { get; set; }
/// <summary>
/// Whether <see cref="Historizing"/> was explicitly specified.
/// </summary>
public bool HistorizingSpecified { get; set; }
/// <summary>
/// Serialized ModelDesign default value XML (variables only).
/// </summary>
public string? DefaultValueXml { get; set; }
/// <summary>
/// The namespace table the namespace indexes in
/// <see cref="DefaultValueXml"/> refer to in the producing model
/// (index 0 is the OPC UA namespace): the producer's design
/// namespaces, or the table of the NodeSet or dependency the value
/// was decoded from. Null when the payload does not carry it (older
/// producers); the consumer then resolves the indexes as before.
/// Carried in the value namespace trailer.
/// </summary>
public IReadOnlyList<string>? DefaultValueNamespaceUris { get; set; }
/// <summary>
/// Input arguments (methods only).
/// </summary>
public IReadOnlyList<DependencyMethodArg> InputArguments { get; set; } = [];
/// <summary>
/// Output arguments (methods only).
/// </summary>
public IReadOnlyList<DependencyMethodArg> OutputArguments { get; set; } = [];
/// <summary>
/// Qualified method state identity used by the producing assembly.
/// Empty for payloads without method identity and non-method children.
/// </summary>
public string MethodStateName { get; set; } = string.Empty;
/// <summary>
/// Namespace URI for <see cref="MethodStateName"/>.
/// </summary>
public string MethodStateNamespace { get; set; } = string.Empty;
/// <summary>
/// Qualified method declaration identity used on the wire.
/// Empty for payloads without method identity and non-method children.
/// </summary>
public string MethodDeclarationName { get; set; } = string.Empty;
/// <summary>
/// Namespace URI for <see cref="MethodDeclarationName"/>.
/// </summary>
public string MethodDeclarationNamespace { get; set; } = string.Empty;
/// <summary>
/// Numeric NodeId identifier for the effective method declaration.
/// </summary>
public uint MethodDeclarationNumericId { get; set; }
/// <summary>
/// String NodeId identifier for the effective method declaration.
/// </summary>
public string? MethodDeclarationStringId { get; set; }
}
/// <summary>
/// Reduced view of a node entry carried in a dependency payload. One entry per
/// node the producing assembly emits as a public type definition
/// (objects/instances are not carried; consumers re-derive
/// inheritance via <see cref="BaseTypeName"/>).
/// </summary>
public sealed class DependencyNode
{
/// <summary>
/// Symbolic name.
/// </summary>
public string SymbolicName { get; set; } = string.Empty;
/// <summary>
/// Symbolic namespace URI.
/// </summary>
public string SymbolicNamespace { get; set; } = string.Empty;
/// <summary>
/// Emitted C# class name (post-Type-suffix-stripping).
/// </summary>
public string ClassName { get; set; } = string.Empty;
/// <summary>
/// Node kind.
/// </summary>
public DependencyNodeKind Kind { get; set; }
/// <summary>
/// Base type name (null for root types).
/// </summary>
public string? BaseTypeName { get; set; }
/// <summary>
/// Base type namespace URI (null for root types).
/// </summary>
public string? BaseTypeNamespace { get; set; }
/// <summary>
/// Numeric NodeId (0 when not assigned).
/// </summary>
public uint NumericId { get; set; }
/// <summary>
/// Optional string NodeId.
/// </summary>
public string? StringId { get; set; }
/// <summary>
/// Optional Guid NodeId in the "D" format (null when not assigned).
/// </summary>
public string? GuidId { get; set; }
/// <summary>
/// Optional opaque NodeId as base64 (null when not assigned).
/// </summary>
public string? OpaqueId { get; set; }
/// <summary>
/// True when the type is abstract.
/// </summary>
public bool IsAbstract { get; set; }
/// <summary>
/// True when the entry represents an enumeration DataType.
/// </summary>
public bool IsEnumeration { get; set; }
/// <summary>
/// DataType restriction of a VariableType (null for every other kind).
/// A consumer needs it to decide whether a variable typed by this
/// VariableType requires a template parameter on its generated state
/// class; without it the consumer cannot tell State&lt;T&gt; from the
/// non-generic state and dereferences a null DataTypeNode.
/// </summary>
public string? DataTypeName { get; set; }
/// <summary>
/// Namespace URI of <see cref="DataTypeName"/>.
/// </summary>
public string? DataTypeNamespace { get; set; }
/// <summary>
/// ValueRank of a VariableType, or null when the VariableType does not
/// specify one (ValueRankSpecified = false upstream).
/// </summary>
public int? ValueRank { get; set; }
/// <summary>
/// DataType fields (empty for non-DataType kinds).
/// </summary>
public IReadOnlyList<DependencyDataField> Fields { get; set; } = [];
/// <summary>
/// Declared instance children (empty for DataType / no-child types).
/// </summary>
public IReadOnlyList<DependencyChild> Children { get; set; } = [];
}
/// <summary>
/// In-memory representation of a <c>ModelDependencyV1</c> payload.
/// </summary>
public sealed class ModelDependencyV1
{
/// <summary>
/// The magic byte sequence.
/// </summary>
public static readonly byte[] Magic = [0xAA, 0xC7];
/// <summary>
/// The version byte for V1.
/// </summary>
public const byte Version = 1;
/// <summary>
/// Compression scheme: 1 = Deflate.
/// </summary>
public const byte CompressionDeflate = 1;
private const byte kFluentAccessorsEmitted = 0x01;
private const byte kFluentAccessorsKnown = 0x40;
private const byte kMethodIdentityTrailer = 0x80;
private const byte kMethodIdentityTrailerVersion = 1;
private const byte kNodeIsAbstract = 0x01;
private const byte kNodeIsEnumeration = 0x02;
// The declaration trailer carries the DataType restriction of each
// VariableType and the BrowseName namespace and ReferenceType of each
// child, with a flag byte per node and per child.
private const byte kDeclarationTrailer = 0x04;
private const byte kDeclarationTrailerVersion = 1;
private const byte kDeclarationDataType = 0x01;
private const byte kDeclarationBrowseNameNamespace = 0x01;
private const byte kDeclarationReferenceType = 0x02;
private const byte kExtendedIdentifierTrailer = 0x20;
private const byte kExtendedIdentifierTrailerVersion = 1;
private const byte kStructureFieldTrailer = 0x10;
private const byte kStructureFieldTrailerVersion = 1;
private const byte kValueNamespaceTrailer = 0x08;
private const byte kValueNamespaceTrailerVersion = 1;
private const byte kFieldIsOptional = 0x01;
private const byte kFieldAllowSubTypes = 0x02;
private const byte kAccessLevelSpecified = 0x01;
private const byte kRawAccessLevel = 0x02;
private const byte kRawUserAccessLevel = 0x04;
private const byte kMinimumSamplingIntervalSpecified = 0x08;
private const byte kHistorizingSpecified = 0x10;
/// <summary>
/// Namespace URI that qualifies the standard reference types.
/// </summary>
public const string OpcUaNamespaceUri = "http://opcfoundation.org/UA/";
/// <summary>
/// Reference type a child of the given instance kind is bound with when
/// the declaring model does not name one. Mirrors the defaulting the
/// validator applies to a locally declared instance, so a child
/// materialised from a dependency payload is bound the same way as the
/// identical child declared in the consuming model.
/// </summary>
/// <param name="instanceKind">
/// Instance kind: 1=Object 2=Variable 3=Property 4=Method.
/// </param>
public static string GetDefaultReferenceTypeName(byte instanceKind)
{
return instanceKind == 3 ? "HasProperty" : "HasComponent";
}
/// <summary>
/// The model URI this dependency payload describes.
/// </summary>
public string ModelUri { get; set; } = string.Empty;
/// <summary>
/// Whether the producing assembly emitted the per-ObjectType typed
/// fluent accessor extension classes. <c>null</c> means the payload
/// does not declare whether accessors exist.
/// </summary>
public bool? FluentAccessorsEmitted { get; set; }
/// <summary>
/// Nodes in the dependency payload.
/// </summary>
public List<DependencyNode> Nodes { get; } = [];
/// <summary>
/// Serialises the dependency payload to a base64-encoded
/// Deflate-compressed payload suitable for embedding in
/// <c>ModelDependencyAttribute</c>.
/// </summary>
public string ToBase64Payload()
{
using var raw = new MemoryStream();
WriteUncompressed(raw);
raw.Position = 0;
using var output = new MemoryStream();
// Header is uncompressed; payload below is Deflate-compressed.
output.WriteByte(Magic[0]);
output.WriteByte(Magic[1]);
output.WriteByte(Version);
output.WriteByte(CompressionDeflate);
using (var deflate = new DeflateStream(output, CompressionLevel.Optimal, leaveOpen: true))
{
raw.CopyTo(deflate);
}
return Convert.ToBase64String(output.ToArray());
}
/// <summary>
/// Reads a dependency payload from a base64-encoded payload.
/// </summary>
/// <returns>Null when the payload version is unrecognised or the
/// magic does not match.</returns>
public static ModelDependencyV1? FromBase64Payload(string base64)
{
if (string.IsNullOrEmpty(base64))
{
return null;
}
byte[] bytes;
try
{
bytes = Convert.FromBase64String(base64);
}
catch (FormatException)
{
return null;
}
if (bytes.Length < 4)
{
return null;
}
if (bytes[0] != Magic[0] || bytes[1] != Magic[1])
{
return null;
}
if (bytes[2] != Version)
{
return null;
}
if (bytes[3] != CompressionDeflate)
{
return null;
}
using var input = new MemoryStream(bytes, index: 4, count: bytes.Length - 4, writable: false);
using var deflate = new DeflateStream(input, CompressionMode.Decompress, leaveOpen: false);
using var inflated = new MemoryStream();
deflate.CopyTo(inflated);
inflated.Position = 0;
var result = new ModelDependencyV1();
result.ReadUncompressed(inflated);
return result;
}
private void WriteUncompressed(Stream destination)
{
using var writer = new BinaryWriter(destination, Encoding.UTF8, leaveOpen: true);
WriteString(writer, ModelUri);
writer.Write(Nodes.Count);
foreach (DependencyNode node in Nodes)
{
WriteString(writer, node.SymbolicName);
WriteString(writer, node.SymbolicNamespace);
WriteString(writer, node.ClassName);
writer.Write((byte)node.Kind);
WriteNullableString(writer, node.BaseTypeName);
WriteNullableString(writer, node.BaseTypeNamespace);
writer.Write(node.NumericId);
WriteNullableString(writer, node.StringId);
byte flags = 0;
if (node.IsAbstract)
{
flags |= kNodeIsAbstract;
}
if (node.IsEnumeration)
{
flags |= kNodeIsEnumeration;
}
writer.Write(flags);
writer.Write(node.Fields.Count);
foreach (DependencyDataField field in node.Fields)
{
WriteString(writer, field.Name);
WriteString(writer, field.DataTypeName);
WriteString(writer, field.DataTypeNamespace);
writer.Write(field.ValueRank);
}
writer.Write(node.Children.Count);
foreach (DependencyChild child in node.Children)
{
WriteString(writer, child.BrowseName);
WriteString(writer, child.SymbolicName);
WriteString(writer, child.TypeDefinitionName);
WriteString(writer, child.TypeDefinitionNamespace);
WriteString(writer, child.DataTypeName);
WriteString(writer, child.DataTypeNamespace);
writer.Write(child.ValueRank);
writer.Write(child.ModellingRule);
writer.Write(child.InstanceKind);
byte variableFlags = 0;
if (child.AccessLevelSpecified)
{
variableFlags |= kAccessLevelSpecified;
}
if (child.RawAccessLevel.HasValue)
{
variableFlags |= kRawAccessLevel;
}
if (child.RawUserAccessLevel.HasValue)
{
variableFlags |= kRawUserAccessLevel;
}
if (child.MinimumSamplingIntervalSpecified)
{
variableFlags |= kMinimumSamplingIntervalSpecified;
}
if (child.HistorizingSpecified)
{
variableFlags |= kHistorizingSpecified;
}
writer.Write(variableFlags);
writer.Write(child.AccessLevel);
writer.Write(child.RawAccessLevel.GetValueOrDefault());
writer.Write(child.RawUserAccessLevel.GetValueOrDefault());
writer.Write(child.MinimumSamplingInterval);
writer.Write(child.Historizing);
WriteNullableString(writer, child.DefaultValueXml);
writer.Write(child.InputArguments.Count);
foreach (DependencyMethodArg a in child.InputArguments)
{
WriteString(writer, a.Name);
WriteString(writer, a.DataTypeName);
WriteString(writer, a.DataTypeNamespace);
writer.Write(a.ValueRank);
}
writer.Write(child.OutputArguments.Count);
foreach (DependencyMethodArg a in child.OutputArguments)
{
WriteString(writer, a.Name);
WriteString(writer, a.DataTypeName);
WriteString(writer, a.DataTypeNamespace);
writer.Write(a.ValueRank);
}
}
}
bool hasMethodIdentityTrailer = HasMethodIdentityTrailer();
bool hasStructureFieldTrailer = HasStructureFieldTrailer();
bool hasValueNamespaceTrailer = HasValueNamespaceTrailer();
bool hasDeclarationTrailer = HasDeclarationTrailer();
// The structure field, value namespace and declaration trailers
// are written after the extended identifier trailer, which is
// always written with them: a reader that predates them only reads
// the capabilities as "trailers present" (honouring the
// FluentAccessorsKnown bit) when the method identity or the
// extended identifier bit is set, and it stops reading after the
// trailers it knows - so it never sees the new ones.
bool hasExtendedIdentifierTrailer =
hasStructureFieldTrailer ||
hasValueNamespaceTrailer ||
hasDeclarationTrailer ||
HasExtendedIdentifierTrailer();
if (FluentAccessorsEmitted.HasValue ||
hasMethodIdentityTrailer ||
hasExtendedIdentifierTrailer)
{
byte capabilities = 0;
if (FluentAccessorsEmitted.HasValue)
{
capabilities |= kFluentAccessorsKnown;
if (FluentAccessorsEmitted.Value)
{
capabilities |= kFluentAccessorsEmitted;
}
}
if (hasMethodIdentityTrailer)
{
capabilities |= kMethodIdentityTrailer;
}
if (hasExtendedIdentifierTrailer)
{
capabilities |= kExtendedIdentifierTrailer;
}
if (hasStructureFieldTrailer)
{
capabilities |= kStructureFieldTrailer;
}
if (hasValueNamespaceTrailer)
{
capabilities |= kValueNamespaceTrailer;
}
if (hasDeclarationTrailer)
{
capabilities |= kDeclarationTrailer;
}
writer.Write(capabilities);
if (hasMethodIdentityTrailer)
{
WriteMethodIdentityTrailer(writer);
}
if (hasExtendedIdentifierTrailer)
{
WriteExtendedIdentifierTrailer(writer);
}
if (hasStructureFieldTrailer)
{
WriteStructureFieldTrailer(writer);
}
if (hasValueNamespaceTrailer)
{
WriteValueNamespaceTrailer(writer);
}
if (hasDeclarationTrailer)
{
WriteDeclarationTrailer(writer);
}
}
}
private void ReadUncompressed(Stream source)
{
using var reader = new BinaryReader(source, Encoding.UTF8, leaveOpen: true);
ModelUri = ReadString(reader);
int typeCount = reader.ReadInt32();
if (typeCount is < 0 or > 1_000_000)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid type count " + typeCount);
}
Nodes.Capacity = typeCount;
for (int i = 0; i < typeCount; i++)
{
var node = new DependencyNode
{
SymbolicName = ReadString(reader),
SymbolicNamespace = ReadString(reader),
ClassName = ReadString(reader),
Kind = (DependencyNodeKind)reader.ReadByte(),
BaseTypeName = ReadNullableString(reader),
BaseTypeNamespace = ReadNullableString(reader),
NumericId = reader.ReadUInt32(),
StringId = ReadNullableString(reader)
};
byte flags = reader.ReadByte();
node.IsAbstract = (flags & kNodeIsAbstract) != 0;
node.IsEnumeration = (flags & kNodeIsEnumeration) != 0;
int fieldCount = reader.ReadInt32();
if (fieldCount is < 0 or > 100_000)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid field count " + fieldCount);
}
if (fieldCount > 0)
{
var fields = new DependencyDataField[fieldCount];
for (int j = 0; j < fieldCount; j++)
{
fields[j] = new DependencyDataField(
ReadString(reader),
ReadString(reader),
ReadString(reader),
reader.ReadInt32());
}
node.Fields = fields;
}
int childCount = reader.ReadInt32();
if (childCount is < 0 or > 100_000)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid child count " + childCount);
}
if (childCount > 0)
{
var children = new DependencyChild[childCount];
for (int j = 0; j < childCount; j++)
{
var c = new DependencyChild
{
BrowseName = ReadString(reader),
SymbolicName = ReadString(reader),
TypeDefinitionName = ReadString(reader),
TypeDefinitionNamespace = ReadString(reader),
DataTypeName = ReadString(reader),
DataTypeNamespace = ReadString(reader),
ValueRank = reader.ReadInt32(),
ModellingRule = reader.ReadByte(),
InstanceKind = reader.ReadByte()
};
byte variableFlags = reader.ReadByte();
c.AccessLevelSpecified =
(variableFlags & kAccessLevelSpecified) != 0;
c.AccessLevel = reader.ReadByte();
uint rawAccessLevel = reader.ReadUInt32();
if ((variableFlags & kRawAccessLevel) != 0)
{
c.RawAccessLevel = rawAccessLevel;
}
uint rawUserAccessLevel = reader.ReadUInt32();
if ((variableFlags & kRawUserAccessLevel) != 0)
{
c.RawUserAccessLevel = rawUserAccessLevel;
}
c.MinimumSamplingInterval = reader.ReadInt32();
c.MinimumSamplingIntervalSpecified =
(variableFlags & kMinimumSamplingIntervalSpecified) != 0;
c.Historizing = reader.ReadBoolean();
c.HistorizingSpecified =
(variableFlags & kHistorizingSpecified) != 0;
c.DefaultValueXml = ReadNullableString(reader);
if (c.DefaultValueXml != null)
{
ValidateDefaultValueXml(c.DefaultValueXml);
}
int inCount = reader.ReadInt32();
if (inCount is < 0 or > 100)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid input arg count " + inCount);
}
if (inCount > 0)
{
var args = new DependencyMethodArg[inCount];
for (int k = 0; k < inCount; k++)
{
args[k] = new DependencyMethodArg(
ReadString(reader),
ReadString(reader),
ReadString(reader),
reader.ReadInt32());
}
c.InputArguments = args;
}
int outCount = reader.ReadInt32();
if (outCount is < 0 or > 100)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid output arg count " + outCount);
}
if (outCount > 0)
{
var args = new DependencyMethodArg[outCount];
for (int k = 0; k < outCount; k++)
{
args[k] = new DependencyMethodArg(
ReadString(reader),
ReadString(reader),
ReadString(reader),
reader.ReadInt32());
}
c.OutputArguments = args;
}
children[j] = c;
}
node.Children = children;
}
Nodes.Add(node);
}
if (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte capabilities = reader.ReadByte();
bool hasMethodIdentityTrailer =
(capabilities & kMethodIdentityTrailer) != 0;
bool hasExtendedIdentifierTrailer =
(capabilities & kExtendedIdentifierTrailer) != 0;
if (hasMethodIdentityTrailer || hasExtendedIdentifierTrailer)
{
FluentAccessorsEmitted =
(capabilities & kFluentAccessorsKnown) != 0
? (capabilities & kFluentAccessorsEmitted) != 0
: null;
}
else
{
FluentAccessorsEmitted =
(capabilities & kFluentAccessorsEmitted) != 0;
}
// Trailers carry no length prefix, so a trailer of a version
// this reader does not know cannot be skipped: its body is left
// unread and whatever follows it cannot be located. Stop there
// rather than parse the next trailer out of the middle of the
// unknown one - which either assigned garbage identifiers or
// threw and dropped the whole payload.
bool trailersReadable = true;
if (hasMethodIdentityTrailer &&
reader.BaseStream.Position < reader.BaseStream.Length)
{
trailersReadable = ReadMethodIdentityTrailer(reader);
}
if (trailersReadable &&
hasExtendedIdentifierTrailer &&
reader.BaseStream.Position < reader.BaseStream.Length)
{
trailersReadable = ReadExtendedIdentifierTrailer(reader);
}
// The later trailers are length prefixed: an unknown version
// is skipped and the ones after it stay readable.
if (trailersReadable &&
(capabilities & kStructureFieldTrailer) != 0 &&
reader.BaseStream.Position < reader.BaseStream.Length)
{
ReadStructureFieldTrailer(reader);
}
if (trailersReadable &&
(capabilities & kValueNamespaceTrailer) != 0 &&
reader.BaseStream.Position < reader.BaseStream.Length)
{
ReadValueNamespaceTrailer(reader);
}
if (trailersReadable &&
(capabilities & kDeclarationTrailer) != 0 &&
reader.BaseStream.Position < reader.BaseStream.Length)
{
ReadDeclarationTrailer(reader);
}
}
}
private bool HasDeclarationTrailer()
{
foreach (DependencyNode node in Nodes)
{
if (node.DataTypeName != null)
{
return true;
}
foreach (DependencyChild child in node.Children)
{
if (!string.IsNullOrEmpty(child.BrowseNameNamespace) ||
!string.IsNullOrEmpty(child.ReferenceTypeName))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Writes the declaration trailer: per node the DataType and ValueRank
/// of a VariableType, and per child of that node the namespace of its
/// BrowseName and the ReferenceType that binds it, each behind a flag
/// byte. The body is length prefixed so that a reader can skip a
/// later version.
/// </summary>
private void WriteDeclarationTrailer(BinaryWriter writer)
{
using var body = new MemoryStream();
using (var bodyWriter = new BinaryWriter(body, Encoding.UTF8, leaveOpen: true))
{
bodyWriter.Write(Nodes.Count);
foreach (DependencyNode node in Nodes)
{
bool hasDataType = node.DataTypeName != null;
bodyWriter.Write(hasDataType ? kDeclarationDataType : (byte)0);
if (hasDataType)
{
WriteString(bodyWriter, node.DataTypeName ?? string.Empty);
WriteString(bodyWriter, node.DataTypeNamespace ?? string.Empty);
bodyWriter.Write(node.ValueRank.HasValue);
bodyWriter.Write(node.ValueRank ?? 0);
}
bodyWriter.Write(node.Children.Count);
foreach (DependencyChild child in node.Children)
{
bool hasBrowseNameNamespace =
!string.IsNullOrEmpty(child.BrowseNameNamespace);
bool hasReferenceType =
!string.IsNullOrEmpty(child.ReferenceTypeName);
byte childFlags = 0;
if (hasBrowseNameNamespace)
{
childFlags |= kDeclarationBrowseNameNamespace;
}
if (hasReferenceType)
{
childFlags |= kDeclarationReferenceType;
}
bodyWriter.Write(childFlags);
if (hasBrowseNameNamespace)
{
WriteString(bodyWriter, child.BrowseNameNamespace);
}
if (hasReferenceType)
{
WriteString(bodyWriter, child.ReferenceTypeName);
WriteString(bodyWriter, child.ReferenceTypeNamespace);
}
}
}
}
writer.Write(kDeclarationTrailerVersion);
writer.Write((int)body.Length);
writer.Write(body.GetBuffer(), 0, (int)body.Length);
}
private void ReadDeclarationTrailer(BinaryReader reader)
{
byte trailerVersion = reader.ReadByte();
int length = reader.ReadInt32();
if (length < 0 || length > reader.BaseStream.Length - reader.BaseStream.Position)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid declaration trailer length " + length);
}
if (trailerVersion != kDeclarationTrailerVersion)
{
// Length prefixed: a later version is skipped as a whole.
reader.BaseStream.Seek(length, SeekOrigin.Current);
return;
}
int nodeCount = reader.ReadInt32();
if (nodeCount != Nodes.Count)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid declaration node count " + nodeCount);
}
for (int i = 0; i < nodeCount; i++)
{
DependencyNode node = Nodes[i];
byte nodeFlags = reader.ReadByte();
if ((nodeFlags & kDeclarationDataType) != 0)
{
node.DataTypeName = ReadString(reader);
node.DataTypeNamespace = ReadString(reader);
bool valueRankSpecified = reader.ReadBoolean();
int valueRank = reader.ReadInt32();
node.ValueRank = valueRankSpecified ? valueRank : null;
}
int childCount = reader.ReadInt32();
if (childCount != node.Children.Count)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid declaration child count " + childCount);
}
for (int j = 0; j < childCount; j++)
{
DependencyChild child = node.Children[j];
byte childFlags = reader.ReadByte();
if ((childFlags & kDeclarationBrowseNameNamespace) != 0)
{
child.BrowseNameNamespace = ReadString(reader);
}
if ((childFlags & kDeclarationReferenceType) != 0)
{
child.ReferenceTypeName = ReadString(reader);
child.ReferenceTypeNamespace = ReadString(reader);
}
}
}
}
private bool HasValueNamespaceTrailer()
{
foreach (DependencyNode node in Nodes)
{
foreach (DependencyChild child in node.Children)
{
if (child.DefaultValueNamespaceUris != null)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Writes the value namespace trailer: the distinct namespace tables
/// the default values of the children refer to, then per node and
/// child (in order) the index of the child's table, -1 for none. The
/// body is length prefixed so that a reader can skip a later version.
/// </summary>
private void WriteValueNamespaceTrailer(BinaryWriter writer)
{
var tables = new List<IReadOnlyList<string>>();
var tableIndexes = new Dictionary<string, int>(StringComparer.Ordinal);
using var body = new MemoryStream();
using (var bodyWriter = new BinaryWriter(body, Encoding.UTF8, leaveOpen: true))
{
var childTables = new List<int>();
foreach (DependencyNode node in Nodes)
{
foreach (DependencyChild child in node.Children)
{
IReadOnlyList<string>? uris = child.DefaultValueNamespaceUris;
if (uris == null)
{
childTables.Add(-1);
continue;
}
string key = GetTableKey(uris);
if (!tableIndexes.TryGetValue(key, out int index))
{
index = tables.Count;
tables.Add(uris);
tableIndexes.Add(key, index);
}
childTables.Add(index);
}
}
bodyWriter.Write(tables.Count);
foreach (IReadOnlyList<string> table in tables)
{
bodyWriter.Write(table.Count);
foreach (string uri in table)
{
WriteString(bodyWriter, uri);
}
}
int next = 0;
bodyWriter.Write(Nodes.Count);
foreach (DependencyNode node in Nodes)
{
bodyWriter.Write(node.Children.Count);
for (int j = 0; j < node.Children.Count; j++)
{
bodyWriter.Write(childTables[next++]);
}
}
}
writer.Write(kValueNamespaceTrailerVersion);
writer.Write((int)body.Length);
writer.Write(body.GetBuffer(), 0, (int)body.Length);
}
/// <summary>
/// An unambiguous key for a namespace table (length prefixed URIs).
/// </summary>
private static string GetTableKey(IReadOnlyList<string> uris)
{
var key = new StringBuilder();
foreach (string uri in uris)
{
string value = uri ?? string.Empty;
key.Append(value.Length.ToString(System.Globalization.CultureInfo.InvariantCulture))
.Append(':')
.Append(value);
}
return key.ToString();
}
private void ReadValueNamespaceTrailer(BinaryReader reader)
{
byte trailerVersion = reader.ReadByte();
int length = reader.ReadInt32();
if (length < 0 || length > reader.BaseStream.Length - reader.BaseStream.Position)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid value namespace trailer length " + length);
}
if (trailerVersion != kValueNamespaceTrailerVersion)
{
// Length prefixed: a later version is skipped as a whole.
reader.BaseStream.Seek(length, SeekOrigin.Current);
return;
}
int tableCount = reader.ReadInt32();
if (tableCount is < 0 or > 100_000)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid value namespace table count " + tableCount);
}
string[][] tables = new string[tableCount][];
for (int i = 0; i < tableCount; i++)
{
int uriCount = reader.ReadInt32();
if (uriCount is < 1 or > ushort.MaxValue + 1)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid value namespace table size " + uriCount);
}
string[] uris = new string[uriCount];
for (int k = 0; k < uriCount; k++)
{
uris[k] = ReadString(reader);
}
tables[i] = uris;
}
int nodeCount = reader.ReadInt32();
if (nodeCount != Nodes.Count)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid value namespace node count " + nodeCount);
}
for (int i = 0; i < nodeCount; i++)
{
DependencyNode node = Nodes[i];
int childCount = reader.ReadInt32();
if (childCount != node.Children.Count)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid value namespace child count " + childCount);
}
for (int j = 0; j < childCount; j++)
{
int index = reader.ReadInt32();
if (index < -1 || index >= tableCount)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid value namespace table index " + index);
}
node.Children[j].DefaultValueNamespaceUris =
index < 0 ? null : tables[index];
}
}
}
private bool HasStructureFieldTrailer()
{
foreach (DependencyNode node in Nodes)
{
foreach (DependencyDataField field in node.Fields)
{
if (field.IsOptional ||
field.AllowSubTypes ||
!string.IsNullOrEmpty(field.ArrayDimensions))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Writes the structure field trailer: the IsOptional, AllowSubTypes
/// and ArrayDimensions of every field, in node and field order. The
/// body is length prefixed so that a reader can skip a later version.
/// </summary>
private void WriteStructureFieldTrailer(BinaryWriter writer)
{
using var body = new MemoryStream();
using (var bodyWriter = new BinaryWriter(body, Encoding.UTF8, leaveOpen: true))
{
bodyWriter.Write(Nodes.Count);
foreach (DependencyNode node in Nodes)
{
bodyWriter.Write(node.Fields.Count);
foreach (DependencyDataField field in node.Fields)
{
byte fieldFlags = 0;
if (field.IsOptional)
{
fieldFlags |= kFieldIsOptional;
}
if (field.AllowSubTypes)
{
fieldFlags |= kFieldAllowSubTypes;
}
bodyWriter.Write(fieldFlags);
WriteNullableString(
bodyWriter,
string.IsNullOrEmpty(field.ArrayDimensions) ? null : field.ArrayDimensions);
}
}
}
writer.Write(kStructureFieldTrailerVersion);
writer.Write((int)body.Length);
writer.Write(body.GetBuffer(), 0, (int)body.Length);
}
private void ReadStructureFieldTrailer(BinaryReader reader)
{
byte trailerVersion = reader.ReadByte();
int length = reader.ReadInt32();
if (length < 0 || length > reader.BaseStream.Length - reader.BaseStream.Position)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid structure field trailer length " + length);
}
if (trailerVersion != kStructureFieldTrailerVersion)
{
// Length prefixed: a later version is skipped as a whole.
reader.BaseStream.Seek(length, SeekOrigin.Current);
return;
}
int nodeCount = reader.ReadInt32();
if (nodeCount != Nodes.Count)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid structure field node count " + nodeCount);
}
for (int i = 0; i < nodeCount; i++)
{
DependencyNode node = Nodes[i];
int fieldCount = reader.ReadInt32();
if (fieldCount != node.Fields.Count)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid structure field count " + fieldCount);
}
if (fieldCount == 0)
{
continue;
}
var fields = new DependencyDataField[fieldCount];
for (int j = 0; j < fieldCount; j++)
{
byte fieldFlags = reader.ReadByte();
fields[j] = node.Fields[j] with
{
IsOptional = (fieldFlags & kFieldIsOptional) != 0,
AllowSubTypes = (fieldFlags & kFieldAllowSubTypes) != 0,
ArrayDimensions = ReadNullableString(reader)
};
}
node.Fields = fields;
}
}
private bool HasExtendedIdentifierTrailer()
{
foreach (DependencyNode node in Nodes)
{
// Null, not empty, means "not assigned" for both: an empty
// GuidId has to be written or it reads back as null.
if (node.GuidId != null ||
node.OpaqueId != null)
{
return true;
}
}
return false;
}
private void WriteExtendedIdentifierTrailer(BinaryWriter writer)
{
writer.Write(kExtendedIdentifierTrailerVersion);
writer.Write(Nodes.Count);
foreach (DependencyNode node in Nodes)
{
WriteNullableString(writer, node.GuidId);
WriteNullableString(writer, node.OpaqueId);
}
}
/// <summary>
/// Reads the extended identifier trailer. Returns false when the
/// trailer has a version this reader does not know - its body is
/// then left unread and no later trailer can be located.
/// </summary>
private bool ReadExtendedIdentifierTrailer(BinaryReader reader)
{
byte trailerVersion = reader.ReadByte();
if (trailerVersion != kExtendedIdentifierTrailerVersion)
{
return false;
}
int nodeCount = reader.ReadInt32();
if (nodeCount != Nodes.Count)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid extended identifier node count " + nodeCount);
}
for (int i = 0; i < nodeCount; i++)
{
Nodes[i].GuidId = ReadNullableString(reader);
Nodes[i].OpaqueId = ReadNullableString(reader);
}
return true;
}
private bool HasMethodIdentityTrailer()
{
foreach (DependencyNode node in Nodes)
{
foreach (DependencyChild child in node.Children)
{
// Every field the trailer carries, or a child whose only
// method identity is a namespace does not round-trip.
if (!string.IsNullOrEmpty(child.MethodStateName) ||
!string.IsNullOrEmpty(child.MethodStateNamespace) ||
!string.IsNullOrEmpty(child.MethodDeclarationName) ||
!string.IsNullOrEmpty(child.MethodDeclarationNamespace) ||
child.MethodDeclarationNumericId != 0 ||
!string.IsNullOrEmpty(child.MethodDeclarationStringId))
{
return true;
}
}
}
return false;
}
private void WriteMethodIdentityTrailer(BinaryWriter writer)
{
writer.Write(kMethodIdentityTrailerVersion);
writer.Write(Nodes.Count);
foreach (DependencyNode node in Nodes)
{
writer.Write(node.Children.Count);
foreach (DependencyChild child in node.Children)
{
WriteString(writer, child.MethodStateName);
WriteString(writer, child.MethodStateNamespace);
WriteString(writer, child.MethodDeclarationName);
WriteString(writer, child.MethodDeclarationNamespace);
writer.Write(child.MethodDeclarationNumericId);
WriteNullableString(writer, child.MethodDeclarationStringId);
}
}
}
/// <summary>
/// Reads the method identity trailer. Returns false when the trailer
/// has a version this reader does not know - its body is then left
/// unread and no later trailer can be located.
/// </summary>
private bool ReadMethodIdentityTrailer(BinaryReader reader)
{
byte trailerVersion = reader.ReadByte();
if (trailerVersion != kMethodIdentityTrailerVersion)
{
return false;
}
int nodeCount = reader.ReadInt32();
if (nodeCount != Nodes.Count)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid method identity node count " + nodeCount);
}
for (int i = 0; i < nodeCount; i++)
{
DependencyNode node = Nodes[i];
int childCount = reader.ReadInt32();
if (childCount != node.Children.Count)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid method identity child count " + childCount);
}
for (int j = 0; j < childCount; j++)
{
DependencyChild child = node.Children[j];
child.MethodStateName = ReadString(reader);
child.MethodStateNamespace = ReadString(reader);
child.MethodDeclarationName = ReadString(reader);
child.MethodDeclarationNamespace = ReadString(reader);
child.MethodDeclarationNumericId = reader.ReadUInt32();
child.MethodDeclarationStringId = ReadNullableString(reader);
}
}
return true;
}
private static void WriteString(BinaryWriter writer, string value)
{
writer.Write(value ?? string.Empty);
}
private static void ValidateDefaultValueXml(string value)
{
try
{
using var textReader = new StringReader(value);
using XmlReader reader = XmlReader.Create(
textReader,
CoreUtils.DefaultXmlReaderSettings());
var document = new XmlDocument();
document.Load(reader);
if (document.DocumentElement == null)
{
throw new XmlException("Default value XML has no document element.");
}
}
catch (XmlException ex)
{
throw new InvalidDataException(
"ModelDependencyV1: invalid default value XML.",
ex);
}
}
private static void WriteNullableString(BinaryWriter writer, string? value)
{
if (value == null)
{
writer.Write((byte)0);
}
else
{
writer.Write((byte)1);
writer.Write(value);
}
}
private static string ReadString(BinaryReader reader)
{
return reader.ReadString();
}
private static string? ReadNullableString(BinaryReader reader)
{
byte marker = reader.ReadByte();
return marker == 0 ? null : reader.ReadString();
}
}
}