fix: reject unsupported upstream matrix payloads per item
This commit is contained in:
@@ -8,7 +8,12 @@ public static class UpstreamValueMapping
|
||||
/// <summary>Materializes scalar and one-dimensional array values without flattening matrices.</summary>
|
||||
/// <param name="value">The upstream value.</param>
|
||||
/// <returns>The independent CLR payload.</returns>
|
||||
public static object? ToClr(Variant value) => Detach(value.AsBoxedObject(Variant.BoxingBehavior.Legacy));
|
||||
public static object? ToClr(Variant value)
|
||||
{
|
||||
if (value.TypeInfo.IsMatrix)
|
||||
throw new NotSupportedException("Matrix payloads are not supported by the OPC UA driver.");
|
||||
return Detach(value.AsBoxedObject(Variant.BoxingBehavior.Legacy));
|
||||
}
|
||||
|
||||
private static object? Detach(object? value) => value switch
|
||||
{
|
||||
|
||||
@@ -548,7 +548,7 @@ public sealed class OpcUaClientDriver : IDriver, ITagDiscovery, IReadable, IWrit
|
||||
// G-2b: resolve any secret:-prefixed Password / UserCertificatePassword through the
|
||||
// shared secret store ONCE, here in the async connect flow, just before the credentials
|
||||
// are consumed. _options stays the raw config (with secret: refs) — only this
|
||||
// connect-scoped local carries plaintext, and only for the duration of the connect.
|
||||
// session identity carries the resolved credential while activation/signing needs it.
|
||||
// Resolving on every InitializeAsync (a full reconnect calls it) picks up rotations.
|
||||
// Both credential consumers — the Username password below and the Certificate password
|
||||
// in BuildCertificateIdentity — flow through BuildUserIdentity(resolvedOptions), so a
|
||||
@@ -1474,11 +1474,7 @@ public sealed class OpcUaClientDriver : IDriver, ITagDiscovery, IReadable, IWrit
|
||||
// Preserve the upstream StatusCode verbatim — including Bad codes per
|
||||
// §8's cascading-quality rule. Also preserve SourceTimestamp so downstream
|
||||
// clients can detect stale upstream data.
|
||||
results[r] = new DataValueSnapshot(
|
||||
Value: UpstreamValueMapping.ToClr(dv.WrappedValue),
|
||||
StatusCode: dv.StatusCode.Code,
|
||||
SourceTimestampUtc: dv.SourceTimestamp == DateTime.MinValue ? null : dv.SourceTimestamp.ToDateTime(),
|
||||
ServerTimestampUtc: dv.ServerTimestamp == DateTime.MinValue ? now : dv.ServerTimestamp.ToDateTime());
|
||||
results[r] = ToSnapshot(dv, now);
|
||||
}
|
||||
RecordDeviceAnswered(generation, now);
|
||||
}
|
||||
@@ -2104,6 +2100,19 @@ public sealed class OpcUaClientDriver : IDriver, ITagDiscovery, IReadable, IWrit
|
||||
PublishNotification(handle, RefOf(item), dv);
|
||||
}
|
||||
|
||||
private static DataValueSnapshot ToSnapshot(DataValue value, DateTime now)
|
||||
{
|
||||
// Matrix payloads have no driver-domain contract. SDK 2's Legacy boxing materializes
|
||||
// them into CLR matrices, so refuse that unsupported shape before boxing. 1D arrays
|
||||
// retain their deployed pass-through behavior, including scalar-shaped authored tags.
|
||||
var unsupportedShape = value.WrappedValue.TypeInfo.IsMatrix;
|
||||
return new DataValueSnapshot(
|
||||
Value: unsupportedShape ? null : UpstreamValueMapping.ToClr(value.WrappedValue),
|
||||
StatusCode: unsupportedShape ? StatusCodes.BadTypeMismatch.Code : value.StatusCode.Code,
|
||||
SourceTimestampUtc: value.SourceTimestamp == DateTimeUtc.MinValue ? null : value.SourceTimestamp.ToDateTime(),
|
||||
ServerTimestampUtc: value.ServerTimestamp == DateTimeUtc.MinValue ? now : value.ServerTimestamp.ToDateTime());
|
||||
}
|
||||
|
||||
private static string RefOf(MonitoredItem item) => (item.Handle as string) ?? item.DisplayName ?? string.Empty;
|
||||
|
||||
private void PublishNotification(OpcUaSubscriptionHandle handle, string fullRef, DataValue dv)
|
||||
@@ -2113,11 +2122,7 @@ public sealed class OpcUaClientDriver : IDriver, ITagDiscovery, IReadable, IWrit
|
||||
// a delivered notification is liveness evidence — the subscription path never calls ReadAsync, so
|
||||
// without this a busy subscription-only driver's LastSuccessfulRead stayed at its connect time.
|
||||
RecordLivenessEvidence();
|
||||
var snapshot = new DataValueSnapshot(
|
||||
Value: UpstreamValueMapping.ToClr(dv.WrappedValue),
|
||||
StatusCode: dv.StatusCode.Code,
|
||||
SourceTimestampUtc: dv.SourceTimestamp == DateTime.MinValue ? null : dv.SourceTimestamp.ToDateTime(),
|
||||
ServerTimestampUtc: dv.ServerTimestamp == DateTime.MinValue ? DateTime.UtcNow : dv.ServerTimestamp.ToDateTime());
|
||||
var snapshot = ToSnapshot(dv, DateTime.UtcNow);
|
||||
// Review of 3b88056f: the replay check and the publish are one critical section shared with the recovery and
|
||||
// trailing reads, so an older value can never be delivered after a newer one (see _deliveryLock).
|
||||
lock (_deliveryLock)
|
||||
|
||||
Reference in New Issue
Block a user