Skip to content
Merged
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ add_library(ariec61850_core
src/mms/live_discovery.cpp
src/mms/rcb_selection.cpp
src/mms/rcb_failover.cpp
src/mms/static_report_session.cpp
src/sampled_values/pdu_codec.cpp
src/sampled_values/frame_codec.cpp
src/sampled_values/quality.cpp
Expand Down
19 changes: 19 additions & 0 deletions REPORTING_PROFILE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ may rank another candidate. Once any mutation is attempted, automatic switching
is disabled; an uncertain write must be cleaned up and diagnosed on the original
RCB instead of risking a second remote mutation.

## Static DataSet report session

`MmsStaticReportSessionRuntime` composes the static pool selector, bounded
pre-claim contention/failover, populated DataSet-directory evidence, and the
persistent subscription runtime. `prepare()` is read-only and may rerank after
a busy or flapping candidate. `start()` uses the RCB's existing static binding;
the runtime rejects any request to rewrite `DatSet`.

The guarded `ariec61850_static_rcb_trial` host tool exposes this lifecycle for
authorized lab validation. Its default mode performs discovery and pre-claim
Reads only. Armed mode enables the selected free BRCB/URCB, optionally requests
GI, observes InformationReports, and cleans up only state acquired by the
runtime. See [docs/STATIC_RCB_LIVE_TRIAL.md](docs/STATIC_RCB_LIVE_TRIAL.md).

The current guarded single-profile run used a populated static URCB: three
pre-claim Reads remained stable, GI produced one decoded report with zero decode
failures, and disable/reservation release completed without deferred cleanup.
This is not yet long-duration or multi-vendor acceptance.

## Phase 4C.1 live-model integration

Read-only discovery includes DataSet directory and RCB state evidence in `live-ied-model-v1`.
Expand Down
1 change: 1 addition & 0 deletions docs/FEATURE_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Status terms are intentionally conservative:
| Reporting | Bound/Unbound runtime semantics | Live-proven | One current snapshot observed 6 Bound and 280 Unbound. This is mutable runtime state, not a permanent device capability claim. |
| Reporting | Dynamic RCB candidate planner | Live-proven | Read-only ranking selected an empty dynamic BRCB candidate on current endpoint. |
| Reporting | Conservative operational availability | Live-proven | Separates dynamic-slot eligibility from populated/static report availability. |
| Reporting | Static DataSet + RCB session | Live-proven / guarded single profile | Reusable runtime selected a populated static URCB after BRCB-only planning correctly found no eligible candidate. Three pre-claim Reads stayed stable; the armed trial reserved/enabled it, requested GI, preserved the existing binding, decoded 1 report with 0 failures, then disabled/released it with cleanup verified. `DatSet` rewrites are rejected. |
| Reporting | Read-only contention pre-claim probe | Live-proven | Three repeated probes on `OCR7SR12CTRL/BI6GGIO1.urcbA01` remained free/stable and returned `StableProceed`; no claim/write was performed. |
| Reporting | Bounded smart pre-claim failover | Offline-tested / stable live probe | Immediately before mutation, repeated read-only probes reject busy/flapping candidates, add them to the command-local exclusion set, and rerank the next candidate. A stable live candidate passed three probes; a live contended switch remains pending. Mutation failures never trigger automatic switching. |
| Reporting | RCB claim/reservation/enable | Live-proven / guarded lab | Explicitly armed Dynamic RCB trial bound and enabled one BRCB, requested GI, then disabled and unbound it with cleanup verified. Live discovery itself remains read-only. |
Expand Down
80 changes: 80 additions & 0 deletions docs/STATIC_RCB_LIVE_TRIAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Static DataSet / RCB Live Trial

`ariec61850_static_rcb_trial` validates the client-side static reporting path
without changing the IED's DataSet model. It discovers populated DataSet
directories and their bound RCBs, applies Smart RCB ranking, then performs
bounded repeated pre-claim Reads immediately before any optional activation.

## Safety boundary

The default command is read-only. It sends association, discovery, DataSet
directory, RCB-state, and contention-probe Reads only. Armed execution requires
the exact token `IEC61850-LAB-STATIC-RCB`.

An armed trial may reserve a selected free URCB, write `RptEna=true`, and issue
`GI=true`. Shutdown writes only the inverse state touched by that runtime:
`RptEna=false` and, for a runtime-reserved URCB, `Resv=false`.

The static trial never:

- writes the RCB `DatSet` attribute;
- defines or deletes a DataSet;
- disables or takes over an RCB observed as busy;
- switches to another RCB after any mutation begins.

If a candidate becomes enabled, reserved, or flaps during the pre-claim window,
the command excludes it and reranks another eligible static candidate within a
hard attempt bound.

## Build

```powershell
.\scripts\build_dynamic_rcb_trial.ps1 -BuildDir build-static-rcb-trial -Configuration Release
```

The shared harness builds both the dynamic and static reporting executables and
runs their help smoke tests plus the dynamic member-planner regression.

## Read-only planning

```powershell
.\build-static-rcb-trial\Release\ariec61850_static_rcb_trial.exe <ied-host> --no-urcb-fallback
```

Success ends with `READ_ONLY_STATIC_PLAN_OK` and prints the selected RCB, mode,
bound DataSet, member count, and every bounded pre-claim attempt.

Use `--dataset-ref <reference>` to require a particular static DataSet or
`--preferred-rcb <reference>` to rank one RCB first. A preferred RCB is not an
ownership override: a busy preferred candidate is still skipped.

## Guarded subscription

Run only against an authorized lab IED:

```powershell
.\build-static-rcb-trial\Release\ariec61850_static_rcb_trial.exe <ied-host> --no-urcb-fallback --arm IEC61850-LAB-STATIC-RCB
```

The strongest successful result is `SMART_STATIC_RCB_TRIAL_PASS`, which means
the selected static RCB was enabled, its binding remained populated during
bounded confirmation Reads, an InformationReport was decoded, and cleanup
completed without deferred state.

`STATIC_TRIAL_LIFECYCLE_PASS_REPORT_PENDING` exits with code 4. It means guarded
enable/confirm/disable cleanup passed, but no GI report arrived within the
bounded observation window. That is lifecycle evidence, not report-delivery
acceptance.

## Portable runtime API

Applications can embed `MmsStaticReportSessionRuntime` directly:

1. retain the `MmsLiveDiscoveryResult` for the session lifetime;
2. call `prepare()` for read-only selection and pre-claim failover;
3. inspect `snapshot()` and the selected static binding;
4. call `start()` only when remote activation is authorized;
5. poll or drain reports, then call `stop()` before disconnecting.

The runtime rejects `write_data_set_reference=true` at construction, so static
and dynamic DataSet ownership cannot be accidentally mixed.
83 changes: 83 additions & 0 deletions include/ariec61850/mms/static_report_session.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

#include "ariec61850/mms/live_discovery.hpp"
#include "ariec61850/mms/rcb_contention.hpp"
#include "ariec61850/mms/rcb_failover.hpp"
#include "ariec61850/mms/rcb_selection.hpp"
#include "ariec61850/mms/report_subscription_runtime.hpp"

#include <cstddef>
#include <memory>
#include <optional>
#include <stop_token>
#include <stdexcept>
#include <string>
#include <vector>

namespace ar::iec61850::mms {

class MmsStaticReportSessionError final : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};

struct MmsStaticReportSessionOptions final {
MmsRcbStaticSelectionOptions selection;
MmsRcbContentionProbeOptions contention;
std::size_t maximum_candidate_attempts{4U};
MmsReportSubscriptionOptions subscription;
};

struct MmsStaticReportSessionSnapshot final {
bool prepared{};
bool active{};
std::string selected_rcb_reference;
std::string data_set_reference;
std::size_t data_set_member_count{};
MmsRcbPreclaimFailoverSnapshot failover;
std::vector<std::string> blockers;
std::optional<MmsReportSubscriptionSnapshot> subscription;
};

// Client-side static DataSet reporting orchestration. prepare() performs only
// bounded Read operations. start() never rewrites DatSet and delegates guarded
// reservation/enable/report/cleanup behavior to MmsReportSubscriptionRuntime.
class MmsStaticReportSessionRuntime final {
public:
MmsStaticReportSessionRuntime(
MmsAssociationRuntime& association,
const MmsLiveDiscoveryResult& discovery,
MmsStaticReportSessionOptions options = {});

void prepare(std::stop_token stop_token = {});
void start(std::stop_token stop_token = {});
void stop(std::stop_token stop_token = {}) noexcept;
[[nodiscard]] bool poll_once(std::stop_token stop_token = {});
[[nodiscard]] std::size_t drain_queued_reports();

[[nodiscard]] MmsStaticReportSessionSnapshot snapshot() const;
[[nodiscard]] bool prepared() const noexcept { return prepared_; }
[[nodiscard]] bool active() const noexcept {
return subscription_ != nullptr && subscription_->active();
}
[[nodiscard]] const MmsReportControlCandidate* selected_candidate() const noexcept {
return selected_candidate_ ? &*selected_candidate_ : nullptr;
}

private:
void resolve_selected_static_binding();

MmsAssociationRuntime& association_;
const MmsLiveDiscoveryResult& discovery_;
MmsStaticReportSessionOptions options_;
MmsRcbPreclaimFailoverTracker failover_;
std::optional<MmsReportControlCandidate> selected_candidate_;
std::optional<MmsDataSetDirectoryResponse> selected_directory_;
std::string selected_data_set_reference_;
std::vector<std::string> blockers_;
std::unique_ptr<MmsReportSubscriptionRuntime> subscription_;
bool prepared_{};
};

} // namespace ar::iec61850::mms
19 changes: 19 additions & 0 deletions scripts/build_dynamic_rcb_trial.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,22 @@ Write-Host " `"$exe`" 192.168.1.10 --no-urcb-fallback --auto-members 4"
Write-Host ""
Write-Host "Armed lab lifecycle example:"
Write-Host " `"$exe`" 192.168.1.10 --no-urcb-fallback --auto-members 4 --arm IEC61850-LAB-DYNAMIC-RCB"

$staticCandidates = @(
(Join-Path $build "$Configuration\ariec61850_static_rcb_trial.exe"),
(Join-Path $build "ariec61850_static_rcb_trial.exe")
)
$staticExe = $staticCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $staticExe) {
throw "ariec61850_static_rcb_trial.exe was not found after build."
}

Write-Host ""
Write-Host "Static RCB trial executable:"
Write-Host " $staticExe"
Write-Host ""
Write-Host "Read-only static plan example:"
Write-Host " `"$staticExe`" 192.168.1.10 --no-urcb-fallback"
Write-Host ""
Write-Host "Armed static subscription example:"
Write-Host " `"$staticExe`" 192.168.1.10 --no-urcb-fallback --arm IEC61850-LAB-STATIC-RCB"
Loading
Loading