Repository navigation
P0 Workbench Reports: port ARIEC61850 fail-closed SCL semantic member binding #230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
masarray
wants to merge
27
commits into
main
Choose a base branch
from
feat/workbench-semantic-report-model-binding-p0-20261017
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
b3c2b45
feat(p0): port ARIEC61850 fail-closed exact SCL DataSet report binding
masarray 3afe9e9
feat(p0): project model-verified report leaves with fail-closed posit…
masarray 21433d4
feat(p0): bind report worker to generation-scoped canonical SCL seman…
masarray bb2a1e4
safety(p0): reject contradictory report references and ambiguous iden…
masarray 8d420ec
test(p0): exercise exact SCL-bound report leaves and six fail-closed …
masarray e4f0e5b
ui(p0): distinguish model-bound semantic report leaves from positiona…
masarray f29e8ba
test(p0): guard authoritative SCL semantic projection and negative bi…
masarray 307ae31
feat(p0): reuse canonical Quality/TimeQuality decoder for verified re…
masarray 7e026b5
ui(p0): expand verified q/t Quality and TimeQuality beneath semantic …
masarray 818f732
test(p0): verify typed quality/time evidence has native owner and inl…
masarray 7c156c3
fix(p0): reject ambiguous SCL fields sharing compatible MMS wire type
masarray 27b5223
fix(p0): bind missing optional DatSet from verified active RCB state
masarray c4cb126
test(p0): enforce MMS-kind ambiguity rejection and verified optional …
masarray 27fbd85
safety(p0): never use trusted SCL semantic model for a different conf…
masarray e26a459
test(p0): guard against applying opened SCL schema to other MMS endpoint
masarray 7158672
fix(p0): honor validated MMS DataRef aliases while enforcing exact ca…
masarray 06d1b79
test(p0): verify two local q/t evidence inspectors and validated MMS …
masarray 9b75c1b
safety(p0): validate exact MMS FCDA aliases and reject malformed 13-b…
masarray d15401c
test(p0): cover exact MMS DataRef alias, wrong FC/leaf and malformed …
masarray 8259835
test(p0): guard malformed IEC Quality rejection alongside alias norma…
masarray 7f7d28b
fix(p0): restore clean binder and apply exact alias, quality and Open…
masarray 5d45f22
test(p0): prove native opened SCL model and reject unverified model s…
masarray c915b1c
test(p0): pin Open SCL semantic source handling in convergence guard
masarray 6a76c08
fix(p0): compare native MMS DataSet ObjectName identity across SCL no…
masarray fc3ddf3
perf(p0): cap verified semantic detail to first 32 report members
masarray 307e252
test(p0): prove native MMS DataSet-name alias in semantic binding
masarray b55452b
test(p0): guard DataSet normalization and bounded verified detail budget
masarray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
183 changes: 183 additions & 0 deletions
183
apps/ied_simulator/src/BrowserReportSemanticBinding.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||
| #pragma once | ||
| #include "ariec61850/mms/live_model.hpp" | ||
| #include "ariec61850/mms/reporting.hpp" | ||
| #include <algorithm> | ||
| #include <cstddef> | ||
| #include <optional> | ||
| #include <string> | ||
| #include <string_view> | ||
| #include <vector> | ||
|
|
||
| // Native port of ARIEC61850 MmsSemanticReportValueProjector's fail-closed | ||
| // invariants. Read-only, no additional protocol stack or MMS association. | ||
| namespace arstack::iedsim { | ||
| namespace semantic_mms = ar::iec61850::mms; | ||
| struct VerifiedReportLeaf final { | ||
| std::string reference; | ||
| const semantic_mms::MmsDataValue* value{}; | ||
| }; | ||
| inline bool semanticInside(const std::string_view leaf, | ||
| const std::string_view root) { | ||
| return leaf.size() > root.size() && leaf.substr(0,root.size()) == root && | ||
| leaf[root.size()] == '.'; | ||
| } | ||
| inline semantic_mms::MmsDataKind semanticExpectedKind(const std::string_view basic) { | ||
| using K = semantic_mms::MmsDataKind; | ||
| if (basic == "BOOLEAN") return K::boolean; | ||
| if (basic == "Quality" || basic == "QUALITY") return K::bit_string; | ||
| if (basic == "Timestamp" || basic == "TIMESTAMP") return K::utc_time; | ||
| if (basic == "Enum" || basic == "ENUM" || basic == "INT32" || | ||
| basic == "INT8" || basic == "INT16") return K::integer; | ||
| if (basic == "FLOAT32" || basic == "FLOAT64") return K::floating_point; | ||
| return K::unknown; | ||
| } | ||
| inline bool semanticKindCompatible(const semantic_mms::MmsDataValue& value, | ||
| const std::string_view basic) { | ||
| using K = semantic_mms::MmsDataKind; | ||
| if (basic == "BOOLEAN") return value.kind() == K::boolean; | ||
| if (basic == "Quality" || basic == "QUALITY") { | ||
| if (value.kind() != K::bit_string) return false; | ||
| const auto& bits = value.raw_value(); | ||
| return bits.size() == 3U && bits[0] == 3U && | ||
| (bits[2] & 0x07U) == 0U; | ||
| } | ||
| if (basic == "Timestamp" || basic == "TIMESTAMP") return value.kind() == K::utc_time; | ||
| if (basic == "Enum" || basic == "ENUM" || basic == "INT32" || | ||
| basic == "INT8" || basic == "INT16") return value.kind() == K::integer; | ||
| if (basic == "FLOAT32" || basic == "FLOAT64") | ||
| return value.kind() == K::floating_point; | ||
| // Unknown/vendor type cannot authorize semantic labelling. | ||
| return false; | ||
| } | ||
| inline std::optional<std::vector<VerifiedReportLeaf>> bindFlatSclReportStructure( | ||
| const semantic_mms::MmsLiveModelDocument& model, | ||
| const std::string_view dataSet, | ||
| const semantic_mms::MmsReportValue& member) { | ||
| using namespace semantic_mms; | ||
| if (!member.value || member.value->kind() != MmsDataKind::structure) | ||
| return std::nullopt; | ||
| // SCL identity/model must be trusted and the FCDA directory must be | ||
| // uniquely bound to this DataSet and exact ordinal. Never infer by RptID. | ||
| // Canonical Open SCL and SCL-assisted contexts share the same existing | ||
| // MMS report runtime; the caller binds the selected model to this endpoint | ||
| // and the runtime has validated the frame against the live RCB/DataSet. | ||
| const bool openedScl = model.source == "OpenedSCL"; | ||
| const bool trustedSnapshot = model.source == "TrustedSclInitialSnapshot"; | ||
| if (model.identity.confidence != MmsLiveModelConfidence::exact || | ||
| (!openedScl && !trustedSnapshot) || dataSet.empty()) | ||
| return std::nullopt; | ||
| const MmsLiveDataSet* chosen = nullptr; | ||
| // Reuse the ObjectName normalization accepted by the native frame | ||
| // validator: dot and dollar forms must resolve to one DataSet identity. | ||
| try { | ||
| const auto requested = | ||
| MmsDataSetDirectoryCodec::parse_data_set_reference(std::string{dataSet}); | ||
| for (const auto& ds : model.data_sets) { | ||
| if (MmsDataSetDirectoryCodec::parse_data_set_reference(ds.reference) | ||
| != requested) | ||
| continue; | ||
| if (chosen) return std::nullopt; | ||
| chosen = &ds; | ||
| } | ||
| } catch (const std::exception&) { | ||
| return std::nullopt; | ||
| } | ||
| if (!chosen) return std::nullopt; | ||
| const MmsLiveDataSetMember* binding = nullptr; | ||
| for (const auto& fcda : chosen->members) { | ||
| if (fcda.index != member.data_set_index) continue; | ||
| if (binding) return std::nullopt; | ||
| binding = &fcda; | ||
| } | ||
| if (!binding || binding->confidence != MmsLiveModelConfidence::exact || | ||
| binding->reference.empty() || binding->functional_constraint.empty()) | ||
| return std::nullopt; | ||
| const auto& raw = member.data_reference; | ||
| const auto& dir = member.member; | ||
| // The native frame validator accepts IEC and MMS DataRef notation. | ||
| // Preserve those aliases only when they resolve to the exact same | ||
| // canonical object reference and functional constraint. | ||
| const auto matchesExactMember = [&](const std::string_view value) { | ||
| if (value.empty()) return false; | ||
| if (value == binding->reference) return true; | ||
| const auto slash = value.find('/'); | ||
| if (slash == std::string_view::npos || slash == 0U) return false; | ||
| const auto fcStart = value.find('$', slash + 1U); | ||
| if (fcStart == std::string_view::npos) return false; | ||
| const auto pathStart = value.find('$', fcStart + 1U); | ||
| if (pathStart == std::string_view::npos || | ||
| value.substr(fcStart + 1U, pathStart - fcStart - 1U) != | ||
| binding->functional_constraint) | ||
| return false; | ||
| std::string canonical{value.substr(0, fcStart)}; | ||
| canonical.push_back('.'); | ||
| for (std::size_t i = pathStart + 1U; i < value.size(); ++i) | ||
| canonical.push_back(value[i] == '$' ? '.' : value[i]); | ||
| return canonical == binding->reference; | ||
| }; | ||
| if (!raw.empty() && !matchesExactMember(raw)) return std::nullopt; | ||
| if (dir && !dir->user_reference.empty() && | ||
| !matchesExactMember(dir->user_reference)) return std::nullopt; | ||
| if (dir && !dir->mms_reference.empty() && | ||
| !matchesExactMember(dir->mms_reference)) return std::nullopt; | ||
| if (raw.empty() && (!dir || (dir->user_reference.empty() && | ||
| dir->mms_reference.empty()))) | ||
| return std::nullopt; | ||
| if (dir && !dir->functional_constraint.empty() && | ||
| dir->functional_constraint != binding->functional_constraint) | ||
| return std::nullopt; | ||
| // Flat ST/CF/SG structures only. Nested, array, repeated or vendor | ||
| // layouts have no proven ordered schema here and remain positional. | ||
| const MmsLiveDataObject* object = nullptr; | ||
| for (const auto& ld : model.logical_devices) | ||
| for (const auto& ln : ld.logical_nodes) | ||
| for (const auto& candidate : ln.data_objects) | ||
| if (binding->reference == candidate.reference) { | ||
| if (object) return std::nullopt; | ||
| object = &candidate; | ||
| } | ||
| if (!object || object->confidence != MmsLiveModelConfidence::exact) | ||
| return std::nullopt; | ||
| std::vector<const MmsLiveDataAttribute*> attributes; | ||
| for (const auto& attribute : object->attributes) { | ||
| if (attribute.functional_constraint != binding->functional_constraint || | ||
| !semanticInside(attribute.object_reference, binding->reference)) | ||
| continue; | ||
| if (attribute.source != | ||
| (openedScl ? "OpenedSCL" : "TrustedSCL") || | ||
| attribute.type_confidence != MmsLiveModelConfidence::exact || | ||
| attribute.functional_constraint_confidence != MmsLiveModelConfidence::exact) | ||
| return std::nullopt; | ||
| // Only one direct segment is admissible; nested structures demand | ||
| // matching a hierarchical SCL schema, not guesswork. | ||
| const auto suffix = std::string_view{attribute.object_reference} | ||
| .substr(binding->reference.size() + 1); | ||
| if (suffix.empty() || suffix.find('.') != std::string_view::npos) | ||
| return std::nullopt; | ||
| attributes.push_back(&attribute); | ||
| } | ||
| const auto& children = member.value->children(); | ||
| if (attributes.empty() || attributes.size() != children.size() || | ||
| children.size() > 16U) return std::nullopt; | ||
| // Positional order is not independently proven by matching count alone. | ||
| // Reject repeated scalar types, where a vendor permutation would look | ||
| // valid and produce deceptively named engineering values. | ||
| for (std::size_t i = 0; i < attributes.size(); ++i) | ||
| for (std::size_t j = i + 1; j < attributes.size(); ++j) | ||
| if (semanticExpectedKind(attributes[i]->scl_basic_type) == | ||
| semanticExpectedKind(attributes[j]->scl_basic_type)) | ||
| return std::nullopt; | ||
|
masarray marked this conversation as resolved.
|
||
| std::vector<VerifiedReportLeaf> result; | ||
| result.reserve(children.size()); | ||
| for (std::size_t i = 0; i < children.size(); ++i) { | ||
| if (!semanticKindCompatible(children[i], attributes[i]->scl_basic_type)) | ||
| return std::nullopt; | ||
| if (std::any_of(result.begin(), result.end(), [&](const auto& leaf){ | ||
| return leaf.reference == attributes[i]->object_reference; })) | ||
| return std::nullopt; | ||
| result.push_back({attributes[i]->object_reference, &children[i]}); | ||
| } | ||
| return result; | ||
| } | ||
| } // namespace arstack::iedsim | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.