fix(galaxy): AdviseSupervisory before raw Write so writes commit
v2-ci / build (push) Failing after 45s
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
v2-ci / build (push) Failing after 45s
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
A plain MXAccess Write runs with no user login (WriteUserId is typically 0), and MXAccess only COMMITS such a write when the item is advised in supervisory mode. Without it the gateway's Write call doesn't throw (the reply looks OK) but the value never reaches the galaxy. GatewayGalaxyDataWriter now issues AdviseSupervisory (once per item handle) before each raw Write; SecuredWrite/ VerifiedWrite tags keep their own user-identity path. Live-verified end-to-end: an authorized write to a Galaxy equipment tag commits and PERSISTS across a fresh re-subscribe; an anonymous write is denied. (The sister ScadaBridge driver commits writes the other way — a configured non-zero WriteUserId + regular Advise; we have no galaxy login, so we use the supervisory context.)
This commit is contained in:
@@ -27,6 +27,9 @@ public sealed class GatewayGalaxyDataWriter : IGalaxyDataWriter
|
||||
private readonly ILogger _logger;
|
||||
private readonly ConcurrentDictionary<string, int> _itemHandles =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
// Item handles we've already AdviseSupervisory'd this session — supervisory advise is
|
||||
// idempotent but the round-trip isn't free, so do it once per handle (see EnsureSupervisoryAdvisedAsync).
|
||||
private readonly ConcurrentDictionary<int, byte> _supervisedHandles = new();
|
||||
|
||||
/// <summary>Initializes a new Galaxy data writer.</summary>
|
||||
/// <param name="session">The MXAccess gateway session.</param>
|
||||
@@ -77,9 +80,24 @@ public sealed class GatewayGalaxyDataWriter : IGalaxyDataWriter
|
||||
.ConfigureAwait(false);
|
||||
var mxValue = MxValueEncoder.Encode(request.Value);
|
||||
|
||||
var reply = NeedsSecuredWrite(classification)
|
||||
? await InvokeWriteSecuredAsync(session, serverHandle, itemHandle, mxValue, ct).ConfigureAwait(false)
|
||||
: await session.WriteRawAsync(serverHandle, itemHandle, mxValue, _writeUserId, ct).ConfigureAwait(false);
|
||||
MxCommandReply reply;
|
||||
if (NeedsSecuredWrite(classification))
|
||||
{
|
||||
// SecuredWrite/VerifiedWrite tags carry their own ArchestrA user identity
|
||||
// (current/verifier user), so they don't use the supervisory path.
|
||||
reply = await InvokeWriteSecuredAsync(session, serverHandle, itemHandle, mxValue, ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// A raw Write runs with NO user login (WriteUserId is typically 0), so MXAccess
|
||||
// only COMMITS the value when the item is advised in SUPERVISORY mode. Without it
|
||||
// the gateway's Write call doesn't throw (reply looks OK) but the value never
|
||||
// reaches the galaxy. AdviseSupervisory once per handle, then Write.
|
||||
await EnsureSupervisoryAdvisedAsync(session, serverHandle, itemHandle, ct).ConfigureAwait(false);
|
||||
reply = await session.WriteRawAsync(serverHandle, itemHandle, mxValue, _writeUserId, ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return TranslateReply(reply, request.FullReference);
|
||||
}
|
||||
@@ -110,6 +128,48 @@ public sealed class GatewayGalaxyDataWriter : IGalaxyDataWriter
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advise an item in MXAccess <c>AdviseSupervisory</c> mode. A plain <c>Write</c> runs
|
||||
/// under no user login (<see cref="_writeUserId"/> is typically 0); MXAccess only
|
||||
/// COMMITS such a write when the item is supervisory-advised. (A logged-in identity —
|
||||
/// i.e. a non-zero WriteUserId, as the ScadaBridge sister driver uses — would commit via
|
||||
/// a regular Advise instead; we don't log in, so we use the supervisory context.)
|
||||
/// The gateway client doesn't expose a typed method, so we build the <see cref="MxCommand"/>
|
||||
/// and route through <c>InvokeAsync</c> (same pattern as <see cref="InvokeWriteSecuredAsync"/>).
|
||||
/// Idempotent per handle for the session lifetime.
|
||||
/// </summary>
|
||||
private async Task EnsureSupervisoryAdvisedAsync(
|
||||
MxGatewaySession session, int serverHandle, int itemHandle, CancellationToken ct)
|
||||
{
|
||||
if (!_supervisedHandles.TryAdd(itemHandle, 0)) return;
|
||||
|
||||
var request = new MxCommandRequest
|
||||
{
|
||||
SessionId = session.SessionId,
|
||||
ClientCorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Command = new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.AdviseSupervisory,
|
||||
AdviseSupervisory = new AdviseSupervisoryCommand
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
ItemHandle = itemHandle,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var reply = await session.InvokeAsync(request, ct).ConfigureAwait(false);
|
||||
if (reply.ProtocolStatus is { } proto && proto.Code != ProtocolStatusCode.Ok)
|
||||
{
|
||||
// Supervisory advise failed — forget it so the next write retries, and let the
|
||||
// write proceed (it surfaces its own status via TranslateReply).
|
||||
_supervisedHandles.TryRemove(itemHandle, out _);
|
||||
_logger.LogWarning(
|
||||
"GalaxyDriver supervisory advise failed for item {ItemHandle}: {Code} {Message}",
|
||||
itemHandle, proto.Code, proto.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issue a WriteSecured command. The high-level session client doesn't expose
|
||||
/// <c>WriteSecuredAsync</c> as a typed method — we build the <see cref="MxCommand"/>
|
||||
|
||||
Reference in New Issue
Block a user