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 @@ -49,6 +49,7 @@ add_library(ariec61850_core
src/control/mms_association_control_transport.cpp
src/sampled_values/pdu_codec.cpp
src/sampled_values/frame_codec.cpp
src/sampled_values/compiled_device_profile.cpp
src/sampled_values/quality.cpp
src/sampled_values/payload_inspector.cpp
src/sampled_values/sample_counter.cpp
Expand Down
35 changes: 35 additions & 0 deletions include/ariec61850/integrity/crc32.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

#include <cstdint>
#include <span>

namespace ar::iec61850::integrity {

inline constexpr std::uint32_t crc32_initial_state = 0xFFFF'FFFFU;

[[nodiscard]] inline std::uint32_t crc32_update(
std::uint32_t state,
const std::span<const std::uint8_t> bytes) noexcept {
for (const auto byte : bytes) {
state ^= byte;
for (std::uint8_t bit = 0U; bit < 8U; ++bit) {
const auto mask = static_cast<std::uint32_t>(
0U - static_cast<std::uint32_t>(state & 1U));
state = (state >> 1U) ^ (0xEDB88320U & mask);
}
}
return state;
}

[[nodiscard]] constexpr std::uint32_t crc32_finalize(
const std::uint32_t state) noexcept {
return ~state;
}

[[nodiscard]] inline std::uint32_t crc32(
const std::span<const std::uint8_t> bytes) noexcept {
return crc32_finalize(crc32_update(crc32_initial_state, bytes));
}

} // namespace ar::iec61850::integrity
130 changes: 130 additions & 0 deletions include/ariec61850/sampled_values/compiled_device_profile.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

#include "ariec61850/sampled_values/publisher_profile.hpp"

#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <vector>

namespace ar::iec61850::sampled_values {

inline constexpr std::uint16_t compiled_sv_device_profile_version = 1U;
inline constexpr std::size_t compiled_sv_device_profile_max_sv_id_bytes = 95U;
inline constexpr std::size_t compiled_sv_device_profile_max_dataset_bytes = 159U;
inline constexpr std::size_t compiled_sv_device_profile_max_leaves = 64U;

enum class SvDeviceWireType : std::uint8_t {
boolean = 1U,
int8 = 2U,
uint8 = 3U,
int16 = 4U,
uint16 = 5U,
int32 = 6U,
uint32 = 7U,
int64 = 8U,
uint64 = 9U,
float32 = 10U,
float64 = 11U,
quality = 12U,
timestamp = 13U,
};

struct SvDeviceLeafDescriptor final {
SvDeviceWireType wire_type{SvDeviceWireType::int32};
std::uint16_t wire_width_bytes{};
bool quality{};
bool timestamp{};

friend bool operator==(const SvDeviceLeafDescriptor&,
const SvDeviceLeafDescriptor&) = default;
};

struct CompiledSvDeviceProfile final {
std::uint16_t schema_version{compiled_sv_device_profile_version};
SvProfileFamily profile_family{SvProfileFamily::unspecified};
SvTransportMode transport_mode{SvTransportMode::multicast};

std::array<std::uint8_t, 6> destination_mac{};
std::uint16_t app_id{};
bool vlan_present{};
std::uint16_t vlan_id{};
std::uint8_t vlan_priority{};

std::uint32_t configuration_revision{};
SvSampleMode sampling_basis{SvSampleMode::unknown};
std::uint32_t configured_sample_rate{};
std::uint32_t frame_rate_hz{};
std::uint16_t sample_counter_modulus{};
std::uint16_t no_asdu{};

SvAsduOptions asdu_options{};
std::uint32_t payload_size_bytes{};

std::uint16_t sv_id_length{};
std::array<char, compiled_sv_device_profile_max_sv_id_bytes + 1U> sv_id{};
std::uint16_t data_set_reference_length{};
std::array<char, compiled_sv_device_profile_max_dataset_bytes + 1U>
data_set_reference{};

std::uint16_t leaf_count{};
std::array<SvDeviceLeafDescriptor, compiled_sv_device_profile_max_leaves> leaves{};

friend bool operator==(const CompiledSvDeviceProfile&,
const CompiledSvDeviceProfile&) = default;
};

struct SvDeviceProfileCompileResult final {
std::optional<CompiledSvDeviceProfile> profile;
std::vector<std::string> errors;

[[nodiscard]] bool ok() const noexcept {
return profile.has_value() && errors.empty();
}
};

[[nodiscard]] SvDeviceProfileCompileResult
compile_esp32p4_device_profile(const SvPublisherProfile& profile);

enum class SvDeviceProfileCodecStatus : std::uint8_t {
ok,
buffer_too_small,
invalid_value,
invalid_magic,
unsupported_version,
invalid_length,
checksum_mismatch,
};

struct SvDeviceProfileCodecResult final {
SvDeviceProfileCodecStatus status{SvDeviceProfileCodecStatus::invalid_value};
std::size_t bytes{};

[[nodiscard]] bool success() const noexcept {
return status == SvDeviceProfileCodecStatus::ok;
}
};

class SvDeviceProfileBinaryCodec final {
public:
static constexpr std::size_t header_bytes = 20U;
static constexpr std::size_t fixed_payload_bytes = 42U;
static constexpr std::size_t leaf_descriptor_bytes = 4U;

[[nodiscard]] static std::optional<std::size_t> encoded_size(
const CompiledSvDeviceProfile& profile) noexcept;

[[nodiscard]] static SvDeviceProfileCodecResult encode_into(
const CompiledSvDeviceProfile& profile,
std::span<std::uint8_t> destination) noexcept;

[[nodiscard]] static SvDeviceProfileCodecResult decode(
std::span<const std::uint8_t> source,
CompiledSvDeviceProfile& profile) noexcept;
};

} // namespace ar::iec61850::sampled_values
38 changes: 10 additions & 28 deletions src/mms/static_brcb_checkpoint_store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "ariec61850/mms/static_brcb_checkpoint_store.hpp"

#include "ariec61850/integrity/crc32.hpp"
#include "ariec61850/mms/static_brcb_runtime.hpp"

#include <algorithm>
Expand Down Expand Up @@ -84,25 +85,6 @@ void write_u64(
return value;
}

[[nodiscard]] std::uint32_t crc32_update(
std::uint32_t state,
const std::span<const std::uint8_t> bytes) noexcept {
for (const auto byte : bytes) {
state ^= byte;
for (std::uint8_t bit = 0U; bit < 8U; ++bit) {
const auto mask = static_cast<std::uint32_t>(
0U - static_cast<std::uint32_t>(state & 1U));
state = (state >> 1U) ^ (0xEDB88320U & mask);
}
}
return state;
}

[[nodiscard]] std::uint32_t crc32(
const std::span<const std::uint8_t> bytes) noexcept {
return ~crc32_update(0xFFFF'FFFFU, bytes);
}

[[nodiscard]] bool generation_newer(
const std::uint64_t left,
const std::uint64_t right) noexcept {
Expand Down Expand Up @@ -131,7 +113,7 @@ void write_u64(
std::uint32_t& result) noexcept {
std::array<std::uint8_t, kCrcChunkBytes> chunk{};
std::size_t consumed = 0U;
std::uint32_t state = 0xFFFF'FFFFU;
std::uint32_t state = integrity::crc32_initial_state;
while (consumed < payload_bytes) {
const auto remaining = payload_bytes - consumed;
const auto bytes = std::min(remaining, chunk.size());
Expand All @@ -141,12 +123,12 @@ void write_u64(
std::span<std::uint8_t>{chunk}.first(bytes))) {
return false;
}
state = crc32_update(
state = integrity::crc32_update(
state,
std::span<const std::uint8_t>{chunk}.first(bytes));
consumed += bytes;
}
result = ~state;
result = integrity::crc32_finalize(state);
return true;
}

Expand All @@ -168,9 +150,9 @@ void write_u64(
if (!std::equal(kHeaderMagic.begin(), kHeaderMagic.end(), header.begin()) ||
read_u16(header, 8U) != MmsStaticBrcbCheckpointStore::format_version ||
read_u16(header, 10U) != MmsStaticBrcbCheckpointStore::record_header_bytes ||
crc32(std::span<const std::uint8_t>{header}.first(36U)) != read_u32(header, 36U) ||
integrity::crc32(std::span<const std::uint8_t>{header}.first(36U)) != read_u32(header, 36U) ||
!std::equal(kFooterMagic.begin(), kFooterMagic.end(), footer.begin()) ||
crc32(std::span<const std::uint8_t>{footer}.first(28U)) != read_u32(footer, 28U)) {
integrity::crc32(std::span<const std::uint8_t>{footer}.first(28U)) != read_u32(footer, 28U)) {
return info;
}
for (std::size_t index = 28U; index < 36U; ++index) {
Expand Down Expand Up @@ -232,7 +214,7 @@ void build_header(
write_u64(header, 12U, generation);
write_u32(header, 20U, payload_bytes);
write_u32(header, 24U, payload_crc);
write_u32(header, 36U, crc32(std::span<const std::uint8_t>{header}.first(36U)));
write_u32(header, 36U, integrity::crc32(std::span<const std::uint8_t>{header}.first(36U)));
}

void build_footer(
Expand All @@ -245,7 +227,7 @@ void build_footer(
write_u64(footer, 8U, generation);
write_u32(footer, 16U, payload_bytes);
write_u32(footer, 20U, payload_crc);
write_u32(footer, 28U, crc32(std::span<const std::uint8_t>{footer}.first(28U)));
write_u32(footer, 28U, integrity::crc32(std::span<const std::uint8_t>{footer}.first(28U)));
}

} // namespace
Expand Down Expand Up @@ -303,7 +285,7 @@ MmsStaticBrcbCheckpointResult MmsStaticBrcbCheckpointStore::checkpoint(
}
const auto generation = next_generation(current_generation);
const auto payload_bytes = static_cast<std::uint32_t>(state.bytes_written);
const auto payload_crc = crc32(state_buffer.first(state.bytes_written));
const auto payload_crc = integrity::crc32(state_buffer.first(state.bytes_written));

std::array<std::uint8_t, record_header_bytes> header{};
std::array<std::uint8_t, record_footer_bytes> footer{};
Expand Down Expand Up @@ -395,7 +377,7 @@ MmsStaticBrcbCheckpointResult MmsStaticBrcbCheckpointStore::restore(
backend_.context,
base + record_header_bytes,
payload) ||
crc32(payload) != info.payload_crc) {
integrity::crc32(payload) != info.payload_crc) {
return MmsStaticBrcbCheckpointStatus::storage_failure;
}
return MmsStaticBrcbCheckpointStatus::ok;
Expand Down
Loading
Loading