docs: backfill XML documentation across 756 files
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public
members surfaced by commentchecker — resolves 5,847 of 5,869 issues
(99.6%) across three /fixdocs passes.
This commit is contained in:
Joseph Doherty
2026-05-28 08:10:17 -04:00
parent f9fc7dd2e1
commit 64e3fbe035
756 changed files with 9876 additions and 96 deletions
@@ -44,6 +44,10 @@ internal sealed class PipeChannel : IAsyncDisposable
return pipe;
};
/// <summary>Initializes a new instance of the <see cref="PipeChannel"/> class.</summary>
/// <param name="options">Configuration options for the historian client.</param>
/// <param name="connect">Function to establish a connection stream.</param>
/// <param name="logger">Logger instance for diagnostics.</param>
public PipeChannel(
WonderwareHistorianClientOptions options,
Func<CancellationToken, Task<Stream>> connect,
@@ -54,12 +58,15 @@ internal sealed class PipeChannel : IAsyncDisposable
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>Gets a value indicating whether the channel is currently connected.</summary>
public bool IsConnected => _stream is not null;
/// <summary>
/// Connects + performs the Hello handshake. Returns when the sidecar has accepted the
/// hello. Throws on rejection (bad secret, version mismatch, or transport failure).
/// </summary>
/// <param name="ct">Cancellation token to stop the operation.</param>
/// <returns>A task representing the asynchronous connection operation.</returns>
public async Task ConnectAsync(CancellationToken ct)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -75,6 +82,13 @@ internal sealed class PipeChannel : IAsyncDisposable
/// Sends one request, waits for the matching reply. On transport failure, reconnects
/// once and retries — broader retry policy lives in the calling layer.
/// </summary>
/// <typeparam name="TRequest">The type of the request payload.</typeparam>
/// <typeparam name="TReply">The type of the reply payload.</typeparam>
/// <param name="requestKind">The message kind of the request.</param>
/// <param name="expectedReplyKind">The expected message kind of the reply.</param>
/// <param name="request">The request payload to send.</param>
/// <param name="cancellationToken">Cancellation token to stop the operation.</param>
/// <returns>A task that returns the reply payload.</returns>
public async Task<TReply> InvokeAsync<TRequest, TReply>(
MessageKind requestKind,
MessageKind expectedReplyKind,
@@ -169,6 +183,8 @@ internal sealed class PipeChannel : IAsyncDisposable
_stream = null;
}
/// <summary>Releases all resources associated with this channel.</summary>
/// <returns>A task representing the asynchronous disposal operation.</returns>
public ValueTask DisposeAsync()
{
if (_disposed) return ValueTask.CompletedTask;
@@ -8,6 +8,9 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Internal;
/// </summary>
internal static class QualityMapper
{
/// <summary>Maps an OPC DA quality byte to an OPC UA StatusCode.</summary>
/// <param name="q">The OPC DA quality byte value.</param>
/// <returns>An OPC UA StatusCode as a uint.</returns>
public static uint Map(byte q) => q switch
{
// Good family (192+)
@@ -26,6 +26,7 @@ public sealed class HistorianSampleDto
/// <summary>Raw OPC DA quality byte from the historian SDK (low 8 bits of OpcQuality).</summary>
[Key(1)] public byte Quality { get; set; }
/// <summary>Gets the UTC timestamp in ticks.</summary>
[Key(2)] public long TimestampUtcTicks { get; set; }
}
@@ -33,7 +34,9 @@ public sealed class HistorianSampleDto
[MessagePackObject]
public sealed class HistorianAggregateSampleDto
{
/// <summary>Gets or sets the aggregate value.</summary>
[Key(0)] public double? Value { get; set; }
/// <summary>Gets or sets the UTC timestamp in ticks.</summary>
[Key(1)] public long TimestampUtcTicks { get; set; }
}
@@ -41,11 +44,17 @@ public sealed class HistorianAggregateSampleDto
[MessagePackObject]
public sealed class HistorianEventDto
{
/// <summary>Gets or sets the event identifier.</summary>
[Key(0)] public string EventId { get; set; } = string.Empty;
/// <summary>Gets or sets the event source name.</summary>
[Key(1)] public string? Source { get; set; }
/// <summary>Gets or sets the event time in UTC ticks.</summary>
[Key(2)] public long EventTimeUtcTicks { get; set; }
/// <summary>Gets or sets the received time in UTC ticks.</summary>
[Key(3)] public long ReceivedTimeUtcTicks { get; set; }
/// <summary>Gets or sets the event display text.</summary>
[Key(4)] public string? DisplayText { get; set; }
/// <summary>Gets or sets the event severity.</summary>
[Key(5)] public ushort Severity { get; set; }
}
@@ -53,13 +62,21 @@ public sealed class HistorianEventDto
[MessagePackObject]
public sealed class AlarmHistorianEventDto
{
/// <summary>Gets or sets the event identifier.</summary>
[Key(0)] public string EventId { get; set; } = string.Empty;
/// <summary>Gets or sets the source name.</summary>
[Key(1)] public string SourceName { get; set; } = string.Empty;
/// <summary>Gets or sets the condition identifier.</summary>
[Key(2)] public string? ConditionId { get; set; }
/// <summary>Gets or sets the alarm type.</summary>
[Key(3)] public string AlarmType { get; set; } = string.Empty;
/// <summary>Gets or sets the alarm message.</summary>
[Key(4)] public string? Message { get; set; }
/// <summary>Gets or sets the alarm severity.</summary>
[Key(5)] public ushort Severity { get; set; }
/// <summary>Gets or sets the event time in UTC ticks.</summary>
[Key(6)] public long EventTimeUtcTicks { get; set; }
/// <summary>Gets or sets the acknowledgment comment.</summary>
[Key(7)] public string? AckComment { get; set; }
}
@@ -68,19 +85,28 @@ public sealed class AlarmHistorianEventDto
[MessagePackObject]
public sealed class ReadRawRequest
{
/// <summary>Gets or sets the tag name.</summary>
[Key(0)] public string TagName { get; set; } = string.Empty;
/// <summary>Gets or sets the start time in UTC ticks.</summary>
[Key(1)] public long StartUtcTicks { get; set; }
/// <summary>Gets or sets the end time in UTC ticks.</summary>
[Key(2)] public long EndUtcTicks { get; set; }
/// <summary>Gets or sets the maximum number of values to read.</summary>
[Key(3)] public int MaxValues { get; set; }
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(4)] public string CorrelationId { get; set; } = string.Empty;
}
[MessagePackObject]
public sealed class ReadRawReply
{
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(0)] public string CorrelationId { get; set; } = string.Empty;
/// <summary>Gets or sets a value indicating whether the operation succeeded.</summary>
[Key(1)] public bool Success { get; set; }
/// <summary>Gets or sets the error message if the operation failed.</summary>
[Key(2)] public string? Error { get; set; }
/// <summary>Gets or sets the historian samples.</summary>
[Key(3)] public HistorianSampleDto[] Samples { get; set; } = Array.Empty<HistorianSampleDto>();
}
@@ -89,9 +115,13 @@ public sealed class ReadRawReply
[MessagePackObject]
public sealed class ReadProcessedRequest
{
/// <summary>Gets or sets the tag name.</summary>
[Key(0)] public string TagName { get; set; } = string.Empty;
/// <summary>Gets or sets the start time in UTC ticks.</summary>
[Key(1)] public long StartUtcTicks { get; set; }
/// <summary>Gets or sets the end time in UTC ticks.</summary>
[Key(2)] public long EndUtcTicks { get; set; }
/// <summary>Gets or sets the interval in milliseconds.</summary>
[Key(3)] public double IntervalMs { get; set; }
/// <summary>
@@ -99,15 +129,20 @@ public sealed class ReadProcessedRequest
/// The .NET 10 client maps OPC UA aggregate enum → column.
/// </summary>
[Key(4)] public string AggregateColumn { get; set; } = string.Empty;
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(5)] public string CorrelationId { get; set; } = string.Empty;
}
[MessagePackObject]
public sealed class ReadProcessedReply
{
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(0)] public string CorrelationId { get; set; } = string.Empty;
/// <summary>Gets or sets a value indicating whether the operation succeeded.</summary>
[Key(1)] public bool Success { get; set; }
/// <summary>Gets or sets the error message if the operation failed.</summary>
[Key(2)] public string? Error { get; set; }
/// <summary>Gets or sets the aggregate sample buckets.</summary>
[Key(3)] public HistorianAggregateSampleDto[] Buckets { get; set; } = Array.Empty<HistorianAggregateSampleDto>();
}
@@ -116,17 +151,24 @@ public sealed class ReadProcessedReply
[MessagePackObject]
public sealed class ReadAtTimeRequest
{
/// <summary>Gets or sets the tag name.</summary>
[Key(0)] public string TagName { get; set; } = string.Empty;
/// <summary>Gets or sets the timestamps in UTC ticks.</summary>
[Key(1)] public long[] TimestampsUtcTicks { get; set; } = Array.Empty<long>();
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(2)] public string CorrelationId { get; set; } = string.Empty;
}
[MessagePackObject]
public sealed class ReadAtTimeReply
{
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(0)] public string CorrelationId { get; set; } = string.Empty;
/// <summary>Gets or sets a value indicating whether the operation succeeded.</summary>
[Key(1)] public bool Success { get; set; }
/// <summary>Gets or sets the error message if the operation failed.</summary>
[Key(2)] public string? Error { get; set; }
/// <summary>Gets or sets the historian samples.</summary>
[Key(3)] public HistorianSampleDto[] Samples { get; set; } = Array.Empty<HistorianSampleDto>();
}
@@ -135,19 +177,28 @@ public sealed class ReadAtTimeReply
[MessagePackObject]
public sealed class ReadEventsRequest
{
/// <summary>Gets or sets the source name.</summary>
[Key(0)] public string? SourceName { get; set; }
/// <summary>Gets or sets the start time in UTC ticks.</summary>
[Key(1)] public long StartUtcTicks { get; set; }
/// <summary>Gets or sets the end time in UTC ticks.</summary>
[Key(2)] public long EndUtcTicks { get; set; }
/// <summary>Gets or sets the maximum number of events to read.</summary>
[Key(3)] public int MaxEvents { get; set; }
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(4)] public string CorrelationId { get; set; } = string.Empty;
}
[MessagePackObject]
public sealed class ReadEventsReply
{
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(0)] public string CorrelationId { get; set; } = string.Empty;
/// <summary>Gets or sets a value indicating whether the operation succeeded.</summary>
[Key(1)] public bool Success { get; set; }
/// <summary>Gets or sets the error message if the operation failed.</summary>
[Key(2)] public string? Error { get; set; }
/// <summary>Gets or sets the historian events.</summary>
[Key(3)] public HistorianEventDto[] Events { get; set; } = Array.Empty<HistorianEventDto>();
}
@@ -156,15 +207,20 @@ public sealed class ReadEventsReply
[MessagePackObject]
public sealed class WriteAlarmEventsRequest
{
/// <summary>Gets or sets the alarm historian events to write.</summary>
[Key(0)] public AlarmHistorianEventDto[] Events { get; set; } = Array.Empty<AlarmHistorianEventDto>();
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(1)] public string CorrelationId { get; set; } = string.Empty;
}
[MessagePackObject]
public sealed class WriteAlarmEventsReply
{
/// <summary>Gets or sets the correlation identifier.</summary>
[Key(0)] public string CorrelationId { get; set; } = string.Empty;
/// <summary>Gets or sets a value indicating whether the operation succeeded.</summary>
[Key(1)] public bool Success { get; set; }
/// <summary>Gets or sets the error message if the operation failed.</summary>
[Key(2)] public string? Error { get; set; }
/// <summary>Per-event success flag, parallel to <see cref="WriteAlarmEventsRequest.Events"/>.</summary>
@@ -12,12 +12,18 @@ public sealed class FrameReader : IDisposable
private readonly Stream _stream;
private readonly bool _leaveOpen;
/// <summary>Initializes a new instance of the <see cref="FrameReader"/> class.</summary>
/// <param name="stream">The stream to read frames from.</param>
/// <param name="leaveOpen">True to leave the stream open after disposal; false to dispose it.</param>
public FrameReader(Stream stream, bool leaveOpen = false)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
_leaveOpen = leaveOpen;
}
/// <summary>Reads a single frame from the stream.</summary>
/// <param name="ct">A cancellation token.</param>
/// <returns>A tuple of the message kind and body bytes, or null at end-of-stream.</returns>
public async Task<(MessageKind Kind, byte[] Body)?> ReadFrameAsync(CancellationToken ct)
{
var lengthPrefix = new byte[Framing.LengthPrefixSize];
@@ -42,6 +48,10 @@ public sealed class FrameReader : IDisposable
return ((MessageKind)kindBuffer[0], body);
}
/// <summary>Deserializes a frame body from MessagePack binary format.</summary>
/// <typeparam name="T">The target type to deserialize the body into.</typeparam>
/// <param name="body">The frame body bytes to deserialize.</param>
/// <returns>The deserialized object of the specified type.</returns>
public static T Deserialize<T>(byte[] body) => MessagePackSerializer.Deserialize<T>(body);
private async Task<bool> ReadExactAsync(byte[] buffer, CancellationToken ct)
@@ -60,6 +70,7 @@ public sealed class FrameReader : IDisposable
return true;
}
/// <summary>Releases the stream resources if <c>leaveOpen</c> was false.</summary>
public void Dispose()
{
if (!_leaveOpen) _stream.Dispose();
@@ -12,12 +12,20 @@ public sealed class FrameWriter : IDisposable
private readonly SemaphoreSlim _gate = new(1, 1);
private readonly bool _leaveOpen;
/// <summary>Initializes a new instance of the FrameWriter class.</summary>
/// <param name="stream">The underlying stream to write frames to.</param>
/// <param name="leaveOpen">If true, the stream is not disposed when this writer is disposed.</param>
public FrameWriter(Stream stream, bool leaveOpen = false)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
_leaveOpen = leaveOpen;
}
/// <summary>Writes a length-prefixed, kind-tagged MessagePack frame to the stream.</summary>
/// <typeparam name="T">The type of the message to serialize.</typeparam>
/// <param name="kind">The frame message kind tag.</param>
/// <param name="message">The message object to serialize and write.</param>
/// <param name="ct">The cancellation token.</param>
public async Task WriteAsync<T>(MessageKind kind, T message, CancellationToken ct)
{
var body = MessagePackSerializer.Serialize(message, cancellationToken: ct);
@@ -43,6 +51,7 @@ public sealed class FrameWriter : IDisposable
finally { _gate.Release(); }
}
/// <summary>Disposes the writer and underlying stream (if not left open).</summary>
public void Dispose()
{
_gate.Dispose();
@@ -13,21 +13,32 @@ public sealed class Hello
public const int CurrentMajor = 1;
public const int CurrentMinor = 0;
/// <summary>Gets or sets the protocol major version.</summary>
[Key(0)] public int ProtocolMajor { get; set; } = CurrentMajor;
/// <summary>Gets or sets the protocol minor version.</summary>
[Key(1)] public int ProtocolMinor { get; set; } = CurrentMinor;
/// <summary>Gets or sets the peer name identifying the client.</summary>
[Key(2)] public string PeerName { get; set; } = string.Empty;
/// <summary>Per-process shared secret — verified against the value the supervisor passed at spawn time.</summary>
[Key(3)] public string SharedSecret { get; set; } = string.Empty;
}
/// <summary>
/// Acknowledgment response to a <see cref="Hello"/> frame. Indicates acceptance and the remote host name.
/// </summary>
[MessagePackObject]
public sealed class HelloAck
{
/// <summary>Gets or sets the protocol major version.</summary>
[Key(0)] public int ProtocolMajor { get; set; } = Hello.CurrentMajor;
/// <summary>Gets or sets the protocol minor version.</summary>
[Key(1)] public int ProtocolMinor { get; set; } = Hello.CurrentMinor;
/// <summary>Gets or sets a value indicating whether the connection was accepted.</summary>
[Key(2)] public bool Accepted { get; set; }
/// <summary>Gets or sets the rejection reason if the connection was not accepted.</summary>
[Key(3)] public string? RejectReason { get; set; }
/// <summary>Gets or sets the host name of the remote server.</summary>
[Key(4)] public string HostName { get; set; } = string.Empty;
}
@@ -36,12 +36,18 @@ public sealed class WonderwareHistorianClient : IHistorianDataSource, IAlarmHist
/// Creates a client over a real named-pipe connection. Tests that need an in-process
/// duplex pair use the <see cref="ForTests"/> factory.
/// </summary>
/// <param name="options">The client connection options.</param>
/// <param name="logger">Optional logger for diagnostic output.</param>
public WonderwareHistorianClient(WonderwareHistorianClientOptions options, ILogger<WonderwareHistorianClient>? logger = null)
: this(options, ct => PipeChannel.DefaultNamedPipeConnectFactory(options, ct), logger)
{
}
/// <summary>Test seam — inject an arbitrary connect callback.</summary>
/// <param name="options">The client connection options.</param>
/// <param name="connect">A callback that establishes the connection stream.</param>
/// <param name="logger">Optional logger for diagnostic output.</param>
/// <returns>A new WonderwareHistorianClient configured for testing.</returns>
public static WonderwareHistorianClient ForTests(
WonderwareHistorianClientOptions options,
Func<CancellationToken, Task<Stream>> connect,
@@ -60,6 +66,13 @@ public sealed class WonderwareHistorianClient : IHistorianDataSource, IAlarmHist
// ===== IHistorianDataSource =====
/// <summary>Asynchronously reads raw historical data for a tag within a time range.</summary>
/// <param name="fullReference">The full reference path of the tag to read.</param>
/// <param name="startUtc">The start time in UTC for the read range.</param>
/// <param name="endUtc">The end time in UTC for the read range.</param>
/// <param name="maxValuesPerNode">The maximum number of values to return.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that returns the historical read result.</returns>
public async Task<HistoryReadResult> ReadRawAsync(
string fullReference, DateTime startUtc, DateTime endUtc, uint maxValuesPerNode,
CancellationToken cancellationToken)
@@ -78,6 +91,14 @@ public sealed class WonderwareHistorianClient : IHistorianDataSource, IAlarmHist
return new HistoryReadResult(ToSnapshots(reply.Samples), ContinuationPoint: null);
}
/// <summary>Asynchronously reads processed historical data with aggregation for a tag within a time range.</summary>
/// <param name="fullReference">The full reference path of the tag to read.</param>
/// <param name="startUtc">The start time in UTC for the read range.</param>
/// <param name="endUtc">The end time in UTC for the read range.</param>
/// <param name="interval">The time interval for aggregation.</param>
/// <param name="aggregate">The type of aggregation to apply.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that returns the historical read result with aggregated data.</returns>
public async Task<HistoryReadResult> ReadProcessedAsync(
string fullReference, DateTime startUtc, DateTime endUtc, TimeSpan interval,
HistoryAggregateType aggregate, CancellationToken cancellationToken)
@@ -97,6 +118,11 @@ public sealed class WonderwareHistorianClient : IHistorianDataSource, IAlarmHist
return new HistoryReadResult(ToAggregateSnapshots(reply.Buckets), ContinuationPoint: null);
}
/// <summary>Asynchronously reads historical data at specific timestamps for a tag.</summary>
/// <param name="fullReference">The full reference path of the tag to read.</param>
/// <param name="timestampsUtc">The specific timestamps in UTC to read values for.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that returns the historical read result with values at the specified times.</returns>
public async Task<HistoryReadResult> ReadAtTimeAsync(
string fullReference, IReadOnlyList<DateTime> timestampsUtc, CancellationToken cancellationToken)
{
@@ -158,6 +184,13 @@ public sealed class WonderwareHistorianClient : IHistorianDataSource, IAlarmHist
return result;
}
/// <summary>Asynchronously reads historical events within a time range.</summary>
/// <param name="sourceName">The source name filter for events, or null to read all sources.</param>
/// <param name="startUtc">The start time in UTC for the read range.</param>
/// <param name="endUtc">The end time in UTC for the read range.</param>
/// <param name="maxEvents">The maximum number of events to return.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that returns the historical events result.</returns>
public async Task<HistoricalEventsResult> ReadEventsAsync(
string? sourceName, DateTime startUtc, DateTime endUtc, int maxEvents,
CancellationToken cancellationToken)
@@ -242,6 +275,9 @@ public sealed class WonderwareHistorianClient : IHistorianDataSource, IAlarmHist
/// for every event in the batch; the drain worker's backoff controls recovery.
/// </para>
/// </remarks>
/// <param name="batch">The batch of alarm historian events to write.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that returns per-event write outcomes.</returns>
public async Task<IReadOnlyList<HistorianWriteOutcome>> WriteBatchAsync(
IReadOnlyList<AlarmHistorianEvent> batch, CancellationToken cancellationToken)
{
@@ -485,6 +521,8 @@ public sealed class WonderwareHistorianClient : IHistorianDataSource, IAlarmHist
_ => throw new NotSupportedException($"Unknown HistoryAggregateType {aggregate}"),
};
/// <summary>Asynchronously disposes the client and its underlying pipe channel.</summary>
/// <returns>A task that completes when the client has been disposed.</returns>
public ValueTask DisposeAsync() => _channel.DisposeAsync();
/// <summary>
@@ -25,6 +25,9 @@ public sealed record WonderwareHistorianClientOptions(
TimeSpan? ConnectTimeout = null,
TimeSpan? CallTimeout = null)
{
/// <summary>Gets the effective connect timeout, using the default if not explicitly set.</summary>
public TimeSpan EffectiveConnectTimeout => ConnectTimeout ?? TimeSpan.FromSeconds(10);
/// <summary>Gets the effective call timeout, using the default if not explicitly set.</summary>
public TimeSpan EffectiveCallTimeout => CallTimeout ?? TimeSpan.FromSeconds(30);
}