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
6 changes: 6 additions & 0 deletions .changesets/volatile-protection-sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
release: patch
summary: Allow protections to read volatile sample sources

Protection sources now preserve the storage cv-qualification through a reference source wrapper,
so protections can be declared directly over volatile variables while rule evaluation still uses
plain sample values.
12 changes: 9 additions & 3 deletions Inc/ST-LIB_HIGH/Protections/Protection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,11 +488,17 @@ inline RuleModel<T> make_rule_model(const RuleDefinition<T>& definition) {
}
}

template <ProtectionSample T, std::size_t RuleCount> class Protection {
template <ProtectionSample T, std::size_t RuleCount, ReadableSampleSource Source = SampleSource<T>>
class Protection {
public:
static_assert(
std::same_as<std::remove_cvref_t<typename Source::value_type>, T>,
"Protection source value_type must match the protection sample type"
);

Protection(
const char* name,
SampleSource<T> source,
Source source,
const std::array<RuleDefinition<T>, RuleCount>& definitions
)
: name(name), source(source), definitions(definitions),
Expand Down Expand Up @@ -550,7 +556,7 @@ template <ProtectionSample T, std::size_t RuleCount> class Protection {
}

const char* name{nullptr};
SampleSource<T> source;
Source source;
std::array<RuleDefinition<T>, RuleCount> definitions{};
std::array<RuleModel<T>, RuleCount> rules{};
uint64_t last_fault_publish_tick{0};
Expand Down
24 changes: 13 additions & 11 deletions Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@ namespace Protections {
namespace detail {

template <typename Source>
concept SampleSourceLike = requires(const std::remove_cvref_t<Source>& source) {
typename std::remove_cvref_t<Source>::value_type;
source.read();
};
concept SampleSourceLike = ReadableSampleSource<Source>;

template <typename Source> consteval auto sample_type_tag() {
using source_type = std::remove_cvref_t<Source>;
if constexpr (requires { typename source_type::value_type; }) {
return std::type_identity<typename source_type::value_type>{};
return std::type_identity<std::remove_cvref_t<typename source_type::value_type>>{};
} else {
return std::type_identity<source_type>{};
return std::type_identity<std::remove_cvref_t<Source>>{};
}
}

Expand All @@ -33,8 +30,8 @@ template <typename Source> constexpr auto to_sample_source(Source& source) {
if constexpr (SampleSourceLike<Source>) {
return source;
} else {
using SampleType = sample_type_from_source_t<Source>;
return SampleSource<SampleType>{source};
using StorageType = std::remove_reference_t<Source>;
return ReferenceSampleSource<StorageType>{source};
}
}

Expand Down Expand Up @@ -93,8 +90,9 @@ template <std::size_t N> struct FixedString {
template <std::size_t N> FixedString(const char (&)[N]) -> FixedString<N>;

template <FixedString Name, auto& Source, std::size_t RuleCount> struct ProtectionSpec {
using source_type = std::remove_cvref_t<decltype(Source)>;
using source_type = std::remove_reference_t<decltype(Source)>;
using sample_type = detail::sample_type_from_source_t<source_type>;
using source_model_type = decltype(detail::to_sample_source(Source));

static constexpr auto name = Name;
static constexpr auto& source = Source;
Expand Down Expand Up @@ -132,7 +130,10 @@ template <auto&... ProtectionSpecs> class ProtectionEngine {

template <auto& Spec> struct StorageForSpec {
using spec_type = std::remove_cvref_t<decltype(Spec)>;
using type = Protection<typename spec_type::sample_type, spec_type::rule_count>;
using type = Protection<
typename spec_type::sample_type,
spec_type::rule_count,
typename spec_type::source_model_type>;
};

template <auto& Spec> using storage_for_spec_t = typename StorageForSpec<Spec>::type;
Expand Down Expand Up @@ -163,8 +164,9 @@ template <auto&... ProtectionSpecs> class ProtectionEngine {
template <auto& ProtectionSpec> static constexpr auto make_protection() {
using SpecType = std::remove_cvref_t<decltype(ProtectionSpec)>;
using SampleType = typename SpecType::sample_type;
using SourceType = typename SpecType::source_model_type;

return Protection<SampleType, SpecType::rule_count>{
return Protection<SampleType, SpecType::rule_count, SourceType>{
SpecType::name.c_str(),
detail::to_sample_source(SpecType::source),
ProtectionSpec.rules.definitions
Expand Down
18 changes: 11 additions & 7 deletions Inc/ST-LIB_HIGH/Protections/SampleSource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@
#include "C++Utilities/CppUtils.hpp"
#include "ST-LIB_HIGH/Protections/ProtectionConcepts.hpp"

template <Protections::ProtectionSample T> class SampleSource {
template <typename Storage>
requires Protections::ProtectionSample<std::remove_cvref_t<Storage>>
class ReferenceSampleSource {
public:
using value_type = T;
using value_type = std::remove_cvref_t<Storage>;

explicit constexpr SampleSource(T& value) : value_ptr(&value) {}
explicit constexpr SampleSource(T* value_ptr) : value_ptr(value_ptr) {}
explicit constexpr ReferenceSampleSource(Storage& value) : value_ptr(&value) {}
explicit constexpr ReferenceSampleSource(Storage* value_ptr) : value_ptr(value_ptr) {}

constexpr const T& read() const { return *value_ptr; }
constexpr T* raw() const { return value_ptr; }
constexpr value_type read() const { return *value_ptr; }
constexpr Storage* raw() const { return value_ptr; }

private:
T* value_ptr{nullptr};
Storage* value_ptr{nullptr};
};

template <typename Storage> using SampleSource = ReferenceSampleSource<Storage>;
29 changes: 29 additions & 0 deletions Tests/diagnostics_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ namespace TestAccess = ST_LIB::TestAccess;
namespace TestPanicReporter = ST_LIB::TestPanicReporter;

static_assert(Protections::ReadableSampleSource<SampleSource<float>>);
static_assert(Protections::ReadableSampleSource<SampleSource<volatile float>>);
static_assert(std::same_as<SampleSource<volatile float>::value_type, float>);
static_assert(!Protections::ReadableSampleSource<int>);
static_assert(Protections::FloatingSample<float>);
static_assert(!Protections::FloatingSample<int>);
Expand Down Expand Up @@ -83,6 +85,14 @@ inline constexpr auto monitored_protection =
);
using MonitoredProtectionEngine = Protections::ProtectionEngine<monitored_protection>;

inline volatile float volatile_monitored_value = 2.0f;
inline constexpr auto volatile_monitored_protection =
Protections::protection<"volatile_monitored_value", volatile_monitored_value>(
Protections::Rules::below(1.0f, 1.5f)
);
using VolatileMonitoredProtectionEngine =
Protections::ProtectionEngine<volatile_monitored_protection>;

inline float time_value = 0.0f;
inline constexpr auto time_protection = Protections::protection<"time_value", time_value>(
Protections::Rules::time_accumulation(10.0f, 0.001f)
Expand Down Expand Up @@ -124,6 +134,7 @@ class DiagnosticsHubTest : public ::testing::Test {
void SetUp() override {
TestAccess::DiagnosticsHub::clear();
MonitoredProtectionEngine::reset();
VolatileMonitoredProtectionEngine::reset();
TimeProtectionEngine::reset();
TimeResetProtectionEngine::reset();
TestAccess::FaultController::clear();
Expand Down Expand Up @@ -288,6 +299,7 @@ TEST_F(DiagnosticsHubTest, FaultControllerStopsDelegatingAfterFault) {
TEST(DiagnosticsBootstrapTest, PanicBeforeRuntimeInstallationSurvivesBootstrapAndIsDelivered) {
TestAccess::DiagnosticsHub::clear();
MonitoredProtectionEngine::reset();
VolatileMonitoredProtectionEngine::reset();
TimeProtectionEngine::reset();
TimeResetProtectionEngine::reset();
TestAccess::FaultController::clear();
Expand Down Expand Up @@ -340,6 +352,23 @@ TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots)
EXPECT_EQ(sink->records.front().payload.protection.rule_kind, Protections::RuleKind::BELOW);
}

TEST_F(DiagnosticsHubTest, ProtectionEngineReadsVolatileSource) {
auto sink_result = Diagnostics::Hub::emplace_sink<RecordingSink>();
ASSERT_TRUE(sink_result.has_value());
auto* sink = *sink_result;

VolatileMonitoredProtectionEngine::initialize();
volatile_monitored_value = 0.5f;
VolatileMonitoredProtectionEngine::evaluate();
Diagnostics::Hub::flush();

ASSERT_FALSE(sink->records.empty());
EXPECT_TRUE(FaultController::is_faulted());
EXPECT_EQ(sink->records.front().category, Diagnostics::Category::PROTECTION_EVENT);
EXPECT_EQ(sink->records.front().payload.protection.rule_kind, Protections::RuleKind::BELOW);
EXPECT_FLOAT_EQ(sink->records.front().payload.protection.observed_value.float32_value, 0.5f);
}

TEST_F(DiagnosticsHubTest, TimeAccumulationUsesSchedulerTickForContinuousDuration) {
auto sink_result = Diagnostics::Hub::emplace_sink<RecordingSink>();
ASSERT_TRUE(sink_result.has_value());
Expand Down
Loading