Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Services/IedBrowserReportWireEvidence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public static IReadOnlyList<NativeBrowserReportRawEvidence> Extract(MmsReportFra
{
var reference = IedBrowserLiveValues.CanonicalReference(reported.MemberReference);
var fc = reported.Member?.FunctionalConstraint ?? "";
// DataRef is optional. If present it must agree with the verified
// DataSet member AND explicit FC, or the raw q/t is quarantined.
if (!DataReferenceAgreesWithMember(reported.DataReference, reference, fc))
continue;
var payload = reported.Value;
if (reported.FailureCode.HasValue || payload is null ||
string.IsNullOrWhiteSpace(reference) || string.IsNullOrWhiteSpace(fc))
Expand Down Expand Up @@ -57,6 +61,27 @@ public static IReadOnlyList<NativeBrowserReportRawEvidence> Extract(MmsReportFra
return result;
}


private static bool DataReferenceAgreesWithMember(
string? dataReference, string memberReference, string memberFc)
{
if (string.IsNullOrWhiteSpace(dataReference))
return true;
var wire = dataReference.Trim();
if (!string.Equals(IedBrowserLiveValues.CanonicalReference(wire),
memberReference, StringComparison.Ordinal))
return false;
// The canonical ledger deliberately drops the MMS $FC$ segment.
// Recheck this separately to reject cross-FC alias collisions.
var fcStart = wire.IndexOf('$');
if (fcStart < 0)
return true;
var fcEnd = wire.IndexOf('$', fcStart + 1);
return fcEnd > fcStart + 1 &&
string.Equals(wire[(fcStart + 1)..fcEnd], memberFc,
StringComparison.OrdinalIgnoreCase);
}

private static MmsQualityEvidence? DecodeQuality(MmsDataValue raw)
=> raw.Kind == MmsDataKind.BitString &&
MmsQualityDecoder.TryDecode(raw.RawValue.ToArray(), out var decoded)
Expand Down
79 changes: 79 additions & 0 deletions tests/ARSAS.Tests/M214ReportQualityDataRefIdentityTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using AR.Iec61850.Mms;
using ArIED61850Tester.Services;

namespace ARSAS.Tests;

public sealed class M214ReportQualityDataRefIdentityTests
{
private const string QualityReference = "LD/CSWI1.Pos.q";

private static MmsReportFrame Frame(
string? dataReference,
string memberReference = QualityReference)
=> new()
{
Values =
[
new MmsReportValue
{
Index = 0,
Member = new MmsDataSetDirectoryMember
{
UserReference = memberReference,
FunctionalConstraint = "ST"
},
DataReference = dataReference ?? "",
// IEC 61850 Quality: Overflow, OutOfRange, OldData and
// Inaccurate set, while Source remains process.
Value = MmsDataValue.BitString(3, new byte[] { 0x31, 0x40 })
}
]
};

[Theory]
[InlineData("LD/CSWI1.Pos.q")]
[InlineData("LD/CSWI1$ST$Pos$q")]
[InlineData(null)]
public void ExactOrAbsentOptionalDataRef_PreservesActualRawQuality(string? dataReference)
{
var raw = Assert.Single(IedBrowserReportWireEvidence.Extract(Frame(dataReference)));
Assert.Equal(QualityReference, raw.CarrierReference);
Assert.Equal("ST", raw.FunctionalConstraint);
var quality = Assert.IsType<ArIED61850Tester.Protocol.Mms.MmsQualityEvidence>(raw.QualityBits);
Assert.Equal("true", quality.FormatField("Overflow"));
Assert.Equal("true", quality.FormatField("OutOfRange"));
Assert.Equal("true", quality.FormatField("OldData"));
Assert.Equal("true", quality.FormatField("Inaccurate"));
Assert.Equal("process", quality.FormatField("Source"));
}

[Theory]
[InlineData("LD/CSWI1.Mod.q")]
[InlineData("OTHER/CSWI1.Pos.q")]
[InlineData("LD/CSWI2.Pos.q")]
[InlineData("LD/CSWI1$MX$Pos$q")]
public void ConflictingOnWireDataRef_CannotAttributeBitsToAnotherObject(
string contradictoryReference)
{
Assert.Empty(IedBrowserReportWireEvidence.Extract(
Frame(contradictoryReference)));
}

[Fact]
public void MissingMemberIdentity_CannotBeRepairedFromOnWireText()
{
var frame = new MmsReportFrame
{
Values =
[
new MmsReportValue
{
Index = 0,
DataReference = "LD/CSWI1$ST$Pos$q",
Value = MmsDataValue.BitString(3, new byte[] { 0x31, 0x40 })
}
]
};
Assert.Empty(IedBrowserReportWireEvidence.Extract(frame));
}
}
Loading