using System.Text;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
///
/// Decodes an IEC STRING(n) / WSTRING(n) buffer read at the PLC's own declared width.
/// The driver reads string symbols as raw bytes and decodes them here instead of letting the
/// Beckhoff marshaller pick an encoding, because that marshaller falls back to the HOST's ANSI code
/// page (CultureInfo.CurrentCulture.TextInfo.ANSICodePage) for a non-UTF-8 target — so a
/// TwinCAT 2 string would decode differently on a Polish (1250) or Cyrillic (1251) server.
///
///
/// Two rules, both ported from the ignitionoee TwinCAT driver and both found on a real PLC:
///
/// - A value ENDS at its first NUL — 0x00 for a STRING, an aligned 0x00 0x00
/// code unit for a WSTRING. Whatever follows is stale content a longer earlier value left in the
/// buffer (live on the Elma cleaner Z33856: work order 67504440 read back with a stale
/// "\0" + "1" tail).
/// - A STRING is single-byte Windows-1252 (umlauts, ° in alarm texts), unless
/// the target reports UTF-8 string data (TwinCAT 3.1 4026+ "UTF8 Encoding for STRING").
/// ISO-8859-1 is the fallback on a runtime without code-page support — identical outside
/// 0x80–0x9F.
///
///
internal static class TwinCATStringCodec
{
private static readonly Encoding Windows1252 = CreateWindows1252();
private static Encoding CreateWindows1252()
{
try
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
return Encoding.GetEncoding(1252);
}
catch (Exception)
{
return Encoding.Latin1;
}
}
/// Decodes an IEC STRING(n) buffer: ends at the first NUL, Windows-1252 unless .
/// The raw symbol bytes (n+1 for a STRING(n)).
/// True when the target reports UTF-8 string data.
/// The decoded value.
public static string DecodeString(ReadOnlySpan bytes, bool utf8)
{
var end = bytes.IndexOf((byte)0);
if (end >= 0) bytes = bytes[..end];
return (utf8 ? Encoding.UTF8 : Windows1252).GetString(bytes);
}
/// Decodes an IEC WSTRING(n) buffer (UTF-16LE): ends at the first aligned U+0000.
/// The raw symbol bytes (2(n+1) for a WSTRING(n)).
/// The decoded value.
public static string DecodeWString(ReadOnlySpan bytes)
{
var end = bytes.Length & ~1; // whole UTF-16 code units only — a trailing odd byte is ignored
for (var i = 0; i + 1 < end; i += 2)
{
if (bytes[i] == 0 && bytes[i + 1] == 0)
{
end = i;
break;
}
}
return Encoding.Unicode.GetString(bytes[..end]);
}
}