Auto: ablegacy-6 — ST string verification + length guard

Verifies libplctag's GetString/SetString round-trips ST file strings (1-word
length prefix + 82 ASCII bytes) end-to-end through the driver, and adds a
client-side length guard so over-82-char writes return BadOutOfRange instead
of being silently truncated by libplctag.

- LibplctagLegacyTagRuntime.EncodeValue: throws ArgumentOutOfRangeException
  for >82-char String writes (StFileMaxStringLength constant).
- AbLegacyDriver.WriteAsync: catches ArgumentOutOfRangeException and maps to
  BadOutOfRange.
- AbLegacyStringEncodingTests: 16 unit tests covering empty / 41-char /
  82-char / embedded-NUL / non-ASCII reads + writes; over-length writes
  return BadOutOfRange and never call WriteAsync; both Slc500 and Plc5
  family paths exercised.

Closes #249

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-25 19:18:55 -04:00
parent 2fc71d288e
commit 0044603902
3 changed files with 267 additions and 1 deletions

View File

@@ -237,6 +237,13 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
{
results[i] = new WriteResult(AbLegacyStatusMapper.BadOutOfRange);
}
catch (ArgumentOutOfRangeException)
{
// ST-file string writes exceeding the 82-byte fixed element. Surfaces from
// LibplctagLegacyTagRuntime.EncodeValue's length guard; mapped to BadOutOfRange so
// the OPC UA client sees a clean rejection rather than a silent truncation.
results[i] = new WriteResult(AbLegacyStatusMapper.BadOutOfRange);
}
catch (Exception ex)
{
results[i] = new WriteResult(AbLegacyStatusMapper.BadCommunicationError);

View File

@@ -12,6 +12,15 @@ internal sealed class LibplctagLegacyTagRuntime : IAbLegacyTagRuntime
{
private readonly Tag _tag;
/// <summary>
/// Maximum payload length for an ST (string) file element on SLC / MicroLogix / PLC-5.
/// The on-wire layout is a 1-word length prefix followed by 82 ASCII bytes — libplctag's
/// <c>SetString</c> handles the framing internally, but it does NOT validate length, so a
/// 93-byte source string would silently truncate. We reject up-front so the OPC UA client
/// gets a clean <c>BadOutOfRange</c> rather than a corrupted PLC value.
/// </summary>
internal const int StFileMaxStringLength = 82;
public LibplctagLegacyTagRuntime(AbLegacyTagCreateParams p)
{
_tag = new Tag
@@ -87,7 +96,14 @@ internal sealed class LibplctagLegacyTagRuntime : IAbLegacyTagRuntime
_tag.SetFloat32(0, Convert.ToSingle(value));
break;
case AbLegacyDataType.String:
_tag.SetString(0, Convert.ToString(value) ?? string.Empty);
{
var s = Convert.ToString(value) ?? string.Empty;
if (s.Length > StFileMaxStringLength)
throw new ArgumentOutOfRangeException(
nameof(value),
$"ST string write exceeds {StFileMaxStringLength}-byte file element capacity (was {s.Length}).");
_tag.SetString(0, s);
}
break;
case AbLegacyDataType.TimerElement:
case AbLegacyDataType.CounterElement: