fix(ablegacy): gate array read on isArray:true; 1-element arrays + assumption comments (review C-2/I-3)

This commit is contained in:
Joseph Doherty
2026-06-16 22:14:36 -04:00
parent 3bbe39c166
commit ce5d46be08
4 changed files with 162 additions and 38 deletions
@@ -29,15 +29,21 @@ public static class AbLegacyEquipmentTagParser
if (string.IsNullOrWhiteSpace(address)) return false;
var dataType = ReadEnum(root, "dataType", AbLegacyDataType.Int);
var deviceHostAddress = ReadString(root, "deviceHostAddress");
// Phase 4c #137 — thread the equipment tag's array element count. arrayLength is the
// authoritative count; isArray is the AdminUI's boolean toggle. A positive arrayLength
// (regardless of isArray) makes this an array tag. Clamp to the PCCC file maximum
// Phase 4c #137 — thread the equipment tag's array element count. The canonical
// foundation contract: a tag is an ARRAY ⟺ isArray:true. arrayLength (the element
// count, ≥1) is honoured ONLY when isArray is the JSON literal true, so a stale
// length behind a cleared / absent isArray never produces an orphan array tag that
// mismatches its scalar OPC UA node (review C-2). A 1-element array (isArray:true,
// arrayLength:1) is a valid [1] array. Clamp to the PCCC file maximum
// (AbLegacyArray.MaxElements = 256) so a fat-fingered count can never request a span
// larger than a single data file holds. Absent / non-positive → null (scalar).
// larger than a single data file holds. isArray:false / absent → null (scalar).
int? arrayLength = null;
var rawLength = ReadInt(root, "arrayLength");
if (rawLength > 0)
arrayLength = Math.Min(rawLength, AbLegacyArray.MaxElements);
if (IsArrayFlag(root))
{
var rawLength = ReadInt(root, "arrayLength");
if (rawLength >= 1)
arrayLength = Math.Min(rawLength, AbLegacyArray.MaxElements);
}
def = new AbLegacyTagDefinition(
Name: reference, DeviceHostAddress: deviceHostAddress, Address: address,
DataType: dataType, Writable: true, ArrayLength: arrayLength);
@@ -59,4 +65,8 @@ public static class AbLegacyEquipmentTagParser
private static int ReadInt(JsonElement o, string name)
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.Number
&& e.TryGetInt32(out var v) ? v : 0;
// True only when the `isArray` property is the JSON literal true. Absent or false → scalar.
private static bool IsArrayFlag(JsonElement o)
=> o.TryGetProperty("isArray", out var e) && e.ValueKind == JsonValueKind.True;
}