9cad9ed0fc
v2-ci / build (push) Failing after 41s
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>/<returns>/<inheritdoc> where missing and removes project bookkeeping IDs (task/tracking refs) from shipped code comments, so the docs read cleanly and CommentChecker is quiet except for known false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc). Doc/comment-only; no logic changed; solution builds clean.
89 lines
4.2 KiB
C#
89 lines
4.2 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
|
|
/// <summary>
|
|
/// Computes byte offsets for declared UDT members under Logix natural-alignment rules so
|
|
/// a single whole-UDT read can decode each member from one buffer without
|
|
/// re-reading per member. Declaration-driven — the caller supplies
|
|
/// <see cref="AbCipStructureMember"/> rows; this helper produces the offset each member
|
|
/// sits at in the parent tag's read buffer.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Alignment rules applied per Rockwell "Logix 5000 Data Access" manual + the
|
|
/// libplctag test fixtures: each member aligns to its natural boundary (SInt 1, Int 2,
|
|
/// DInt/Real/Dt 4, LInt/ULInt/LReal 8), padding inserted before the member as needed.
|
|
/// The total size is padded to the alignment of the largest member so arrays-of-UDT also
|
|
/// work at element stride — though this helper is used only on single instances today.</para>
|
|
///
|
|
/// <para><see cref="TryBuild"/> returns <c>null</c> on unsupported member types
|
|
/// (<see cref="AbCipDataType.Bool"/>, <see cref="AbCipDataType.String"/>,
|
|
/// <see cref="AbCipDataType.Structure"/>). Whole-UDT grouping opts out of those groups
|
|
/// and falls back to the per-tag read path — BOOL members are packed into a hidden host
|
|
/// byte at the top of the UDT under Logix, so their offset can't be computed from
|
|
/// declared-member order alone. The CIP Template Object reader produces a
|
|
/// <see cref="AbCipUdtShape"/> that carries real offsets for BOOL + nested structs; when
|
|
/// that shape is cached the driver can take the richer path instead.</para>
|
|
/// </remarks>
|
|
public static class AbCipUdtMemberLayout
|
|
{
|
|
/// <summary>
|
|
/// Try to compute member offsets for the supplied declared members. Returns <c>null</c>
|
|
/// if any member type is unsupported for declaration-only layout.
|
|
/// </summary>
|
|
/// <param name="members">The list of UDT member declarations.</param>
|
|
/// <returns>A dictionary of member offsets keyed by name, or <c>null</c> if any member type is unsupported.</returns>
|
|
public static IReadOnlyDictionary<string, int>? TryBuild(
|
|
IReadOnlyList<AbCipStructureMember> members)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(members);
|
|
if (members.Count == 0) return null;
|
|
|
|
var offsets = new Dictionary<string, int>(members.Count, StringComparer.OrdinalIgnoreCase);
|
|
var cursor = 0;
|
|
|
|
foreach (var member in members)
|
|
{
|
|
if (!TryGetSizeAlign(member.DataType, out var size, out var align))
|
|
return null;
|
|
|
|
// An array member can't be placed by declaration-only layout: the
|
|
// whole-UDT grouped read decodes one scalar per member at its offset and can't return
|
|
// an array, and advancing the cursor by the scalar size (not size * count) would
|
|
// mis-place every member after it. Opt the whole group out so array members fall back
|
|
// to the per-tag read path, which reads them as typed arrays.
|
|
if (member.IsArray || member.ElementCount > 1)
|
|
return null;
|
|
|
|
if (cursor % align != 0)
|
|
cursor += align - (cursor % align);
|
|
|
|
offsets[member.Name] = cursor;
|
|
cursor += size;
|
|
}
|
|
|
|
return offsets;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Natural size + alignment for a Logix atomic type. <c>false</c> for types excluded
|
|
/// from declaration-only grouping (Bool / String / Structure).
|
|
/// </summary>
|
|
private static bool TryGetSizeAlign(AbCipDataType type, out int size, out int align)
|
|
{
|
|
switch (type)
|
|
{
|
|
case AbCipDataType.SInt: case AbCipDataType.USInt:
|
|
size = 1; align = 1; return true;
|
|
case AbCipDataType.Int: case AbCipDataType.UInt:
|
|
size = 2; align = 2; return true;
|
|
case AbCipDataType.DInt: case AbCipDataType.UDInt:
|
|
case AbCipDataType.Real: case AbCipDataType.Dt:
|
|
size = 4; align = 4; return true;
|
|
case AbCipDataType.LInt: case AbCipDataType.ULInt:
|
|
case AbCipDataType.LReal:
|
|
size = 8; align = 8; return true;
|
|
default:
|
|
size = 0; align = 0; return false;
|
|
}
|
|
}
|
|
}
|