fix(ablegacy): refuse every libplctag write the #659 translation changes (fix round 2)
Review M1: round 1 refused only the rewritten status bits, so S:/O:/I: writes - which never worked on libplctag because tag creation failed - would have started reaching the PLC (outputs, status-file words) on the default transport. WriteRefusal now keys on LibplctagLegacyName.IsTranslated: file number supplied (S/O/I, incl. bit writes like O:0/3, refused ahead of the parent-word RMW) or status bit rewritten. Reads unaffected. Docs/comments now true; gate doc notes R bit-number wire evidence is non-discriminating (L1). 12 new cases fail with the round-1 predicate. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -46,17 +46,18 @@ internal static class LibplctagLegacyName
|
||||
public static string Translate(string canonical) => TranslateCore(canonical).Name;
|
||||
|
||||
/// <summary>
|
||||
/// True when <paramref name="canonical"/> is a status bit this translator rewrites to control word + bit
|
||||
/// (<c>R6:0.DN</c> → <c>R6:0.CON/13</c>; R <c>EN/EU/DN/EM/ER/UL/IN/FD</c>, counter <c>UA</c>). A WRITE to one is
|
||||
/// refused (<see cref="LibplctagLegacyTagRuntime.WriteRefusal"/>): before the translation libplctag could not
|
||||
/// even create such a tag, so a write never reached the PLC, and the rewrite must not turn it into a new masked
|
||||
/// write to a production controller's control word (the native transport refuses status-bit writes too).
|
||||
/// True when <see cref="Translate"/> changes <paramref name="canonical"/>: a file number supplied for
|
||||
/// <c>S:</c>/<c>O:</c>/<c>I:</c> (incl. bit forms such as <c>O:0/3</c>) or a status bit rewritten to control word +
|
||||
/// bit (<c>R6:0.DN</c> → <c>R6:0.CON/13</c>; R <c>EN/EU/DN/EM/ER/UL/IN/FD</c>, counter <c>UA</c>). A WRITE to such an
|
||||
/// address is refused (<see cref="LibplctagLegacyTagRuntime.WriteRefusal"/>): before #659 libplctag could not
|
||||
/// even create these tags, so no write ever reached the PLC, and the read fix must not turn libplctag — the
|
||||
/// default transport — into a new way to drive outputs, status-file words or control words.
|
||||
/// </summary>
|
||||
/// <param name="canonical">The canonical address.</param>
|
||||
/// <returns><see langword="true"/> for a rewritten status bit.</returns>
|
||||
public static bool IsRewrittenStatusBit(string canonical) => TranslateCore(canonical).RewrittenStatusBit;
|
||||
/// <returns><see langword="true"/> when the libplctag name differs from the canonical one.</returns>
|
||||
public static bool IsTranslated(string canonical) => TranslateCore(canonical).Changed;
|
||||
|
||||
private static (string Name, bool RewrittenStatusBit) TranslateCore(string canonical)
|
||||
private static (string Name, bool Changed) TranslateCore(string canonical)
|
||||
{
|
||||
// Family-neutral parse is correct: the driver hands the runtime AbLegacyAddress.ToLibplctagName(), already
|
||||
// decimal-normalised (PLC-5 octal I/O included) — the same reasoning as NativeAbLegacyTagRuntime.ToPcccAddress.
|
||||
@@ -65,6 +66,7 @@ internal static class LibplctagLegacyName
|
||||
var fileNumber = a.FileNumber ?? PcccFileType.ImplicitFileNumber(a.FileLetter);
|
||||
if (fileNumber is null) return (canonical, false);
|
||||
|
||||
var fileNumberSupplied = a.FileNumber is null; // S:/O:/I: — libplctag needs S2/O0/I1
|
||||
var name = $"{a.FileLetter}{fileNumber}:{a.WordNumber}";
|
||||
if (a.SubElement is { } sub)
|
||||
{
|
||||
@@ -91,6 +93,6 @@ internal static class LibplctagLegacyName
|
||||
}
|
||||
|
||||
if (a.BitIndex is int b) name += $"/{b}";
|
||||
return (name, false);
|
||||
return (name, fileNumberSupplied);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,18 +71,20 @@ internal sealed class LibplctagLegacyTagRuntime : IAbLegacyTagRuntime
|
||||
: RunReleasingOrphanAsync(() => _tag.WriteAsync(cancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// The writes the libplctag runtime refuses before the wire, or <see langword="null"/>: a status bit that
|
||||
/// <see cref="LibplctagLegacyName"/> rewrites to control word + bit (<c>R6:0.DN</c> → <c>R6:0.CON/13</c>, R
|
||||
/// <c>EN/EU/DN/EM/ER/UL/IN/FD</c>, counter <c>UA</c>). Before #659 libplctag could not create those tags at all,
|
||||
/// so a write to one never reached the controller; the read-side translation must not open a new masked write
|
||||
/// to a control word. T/C status bits libplctag itself parses (<c>T4:0.DN</c>, <c>C5:0.CU</c>…) keep their
|
||||
/// pre-existing behaviour — a masked bit write (documented hazard in <c>docs/drivers/AbLegacy.md</c>).
|
||||
/// The writes the libplctag runtime refuses before the wire, or <see langword="null"/>: any address whose
|
||||
/// libplctag name <see cref="LibplctagLegacyName"/> had to change (<see cref="LibplctagLegacyName.IsTranslated"/>)
|
||||
/// — file-number-less <c>S:</c>/<c>O:</c>/<c>I:</c> (incl. bit writes such as <c>O:0/3</c>, refused before the
|
||||
/// parent-word read-modify-write) and the status bits rewritten to control word + bit (<c>R6:0.DN</c>, counter
|
||||
/// <c>UA</c>). Before #659 libplctag could not create those tags at all, so no write to one ever reached the
|
||||
/// controller; the read fix must not widen the write surface of the default transport. T/C status bits
|
||||
/// libplctag parses itself (<c>T4:0.DN</c>, <c>C5:0.CU</c>…) keep their pre-existing behaviour — a masked bit
|
||||
/// write (documented hazard in <c>docs/drivers/AbLegacy.md</c>).
|
||||
/// </summary>
|
||||
/// <param name="p">The tag's create parameters.</param>
|
||||
/// <returns>The refusal reason, or <see langword="null"/>.</returns>
|
||||
internal static string? WriteRefusal(AbLegacyTagCreateParams p) =>
|
||||
LibplctagLegacyName.IsRewrittenStatusBit(p.TagName)
|
||||
? $"Writes to the status bit '{p.TagName}' are not supported on the libplctag transport (libplctag has no mnemonic for it; it is read as control word + bit)."
|
||||
LibplctagLegacyName.IsTranslated(p.TagName)
|
||||
? $"Writes to '{p.TagName}' are not supported on the libplctag transport (its name is translated for libplctag; reads only)."
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user