fix(abcip): Task 13 review r1 - bounded listing pager, exclusive large reads, template bound, safe lease release

This commit is contained in:
Joseph Doherty
2026-10-07 17:07:05 -04:00
parent 78e60a5a75
commit b2b9aa04ed
5 changed files with 182 additions and 15 deletions
@@ -1,3 +1,5 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport;
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Cip;
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Logix;
@@ -17,7 +19,11 @@ internal sealed class NativeAbCipTemplateReader : IAbCipTemplateReader
/// <summary>Bytes of the template definition header not counted in the Read Template length.</summary>
private const int DefinitionHeaderBytes = 23;
/// <summary>Upper bound on a template's Read Template length; Logix templates are far smaller.</summary>
internal const long MaxTemplateBytes = 64 * 1024;
private readonly Func<AbCipTagCreateParams, (RequestScheduler Scheduler, ICipChannel Channel, Func<ValueTask> Release)> _acquire;
private readonly ILogger _logger;
/// <summary>Production constructor: leases from <paramref name="pool"/>.</summary>
/// <param name="pool">The connection pool (the factory passes <see cref="DeviceSessionPool.Shared"/>).</param>
@@ -32,9 +38,14 @@ internal sealed class NativeAbCipTemplateReader : IAbCipTemplateReader
/// <summary>Test seam: supplies the scheduler, channel and release directly.</summary>
/// <param name="acquire">Acquires the connection parts for a device.</param>
/// <param name="logger">Receives release failures; null discards them.</param>
internal NativeAbCipTemplateReader(
Func<AbCipTagCreateParams, (RequestScheduler Scheduler, ICipChannel Channel, Func<ValueTask> Release)> acquire) =>
Func<AbCipTagCreateParams, (RequestScheduler Scheduler, ICipChannel Channel, Func<ValueTask> Release)> acquire,
ILogger? logger = null)
{
_acquire = acquire;
_logger = logger ?? NullLogger.Instance;
}
/// <inheritdoc />
public async Task<byte[]> ReadAsync(
@@ -53,7 +64,7 @@ internal sealed class NativeAbCipTemplateReader : IAbCipTemplateReader
// The CIP-documented Read Template length: definition words * 4 - 23.
var total = checked((long)attrs.DefinitionWords * 4 - DefinitionHeaderBytes);
if (total < 0)
if (total < 0 || total > MaxTemplateBytes)
throw new AbTransportException(AbStatus.BadReply,
$"Template {id} reports an impossible definition size of {attrs.DefinitionWords} words.");
@@ -63,7 +74,7 @@ internal sealed class NativeAbCipTemplateReader : IAbCipTemplateReader
{
var chunk = (ushort)Math.Min(total - offset, channel.MaxRequestBytes - RequestOverhead);
var reply = await scheduler
.SendAsync(TemplateObjectServices.ReadTemplate(id, (uint)offset, chunk), cancellationToken)
.SendAsync(TemplateObjectServices.ReadTemplate(id, (uint)offset, chunk), cancellationToken, exclusive: true)
.ConfigureAwait(false);
if (reply.GeneralStatus is not (0x00 or 0x06))
throw new AbTransportException(reply.Status,
@@ -80,7 +91,7 @@ internal sealed class NativeAbCipTemplateReader : IAbCipTemplateReader
}
finally
{
NativeLease.ReleaseBounded(release);
NativeLease.ReleaseBounded(release, _logger);
}
}