Skip to content
Closed
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
72 changes: 71 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,38 @@ add_library(ariec61850_core

add_library(ARIEC61850::core ALIAS ariec61850_core)

add_library(ariec61850_mms_server_core
src/asn1/ber_span_reader.cpp
src/osi/tpkt_span.cpp
src/osi/cotp_span.cpp
src/osi/session_span.cpp
src/osi/presentation_span.cpp
src/acse/association_span.cpp
src/mms/pdu_span.cpp
src/mms/services_span.cpp
src/mms/write_span.cpp
src/mms/data_set_span.cpp
src/mms/static_object_table.cpp
src/mms/static_write.cpp
src/mms/static_data_set_table.cpp
src/mms/static_dispatcher.cpp
src/mms/connection_runtime.cpp
src/mms/static_server_session.cpp)

add_library(ARIEC61850::mms_server_core ALIAS ariec61850_mms_server_core)

target_compile_features(ariec61850_core PUBLIC cxx_std_20)
target_include_directories(ariec61850_core
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)

target_compile_features(ariec61850_mms_server_core PUBLIC cxx_std_20)
target_include_directories(ariec61850_mms_server_core
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)

if(WIN32)
target_link_libraries(ariec61850_core PRIVATE ws2_32)
endif()
Expand Down Expand Up @@ -93,6 +119,8 @@ endfunction()

ariec61850_apply_warnings(ariec61850_core)
ariec61850_apply_sanitizers(ariec61850_core)
ariec61850_apply_warnings(ariec61850_mms_server_core)
ariec61850_apply_sanitizers(ariec61850_mms_server_core)

if(ARIEC61850_BUILD_TOOLS)
add_executable(ariec61850_pcap_interop_check tools/pcap_interop_check.cpp)
Expand Down Expand Up @@ -136,6 +164,17 @@ if(ARIEC61850_BUILD_TOOLS)
target_compile_features(ariec61850_sv_injector PRIVATE cxx_std_20)
ariec61850_apply_warnings(ariec61850_sv_injector)
ariec61850_apply_sanitizers(ariec61850_sv_injector)

add_executable(ariec61850_static_ied_server tools/static_ied_server.cpp)
target_link_libraries(
ariec61850_static_ied_server
PRIVATE ARIEC61850::mms_server_core ARIEC61850::core)
if(WIN32)
target_link_libraries(ariec61850_static_ied_server PRIVATE ws2_32)
endif()
target_compile_features(ariec61850_static_ied_server PRIVATE cxx_std_20)
ariec61850_apply_warnings(ariec61850_static_ied_server)
ariec61850_apply_sanitizers(ariec61850_static_ied_server)
endif()

if(ARIEC61850_BUILD_TESTS)
Expand Down Expand Up @@ -266,6 +305,34 @@ if(ARIEC61850_BUILD_TESTS)
ariec61850_apply_sanitizers(ariec61850_fuzz_smoke_tests)
add_test(NAME ariec61850_fuzz_smoke_tests COMMAND ariec61850_fuzz_smoke_tests)

add_executable(
ariec61850_mms_server_session_tests
tests/test_mms_static_server_session.cpp)
target_link_libraries(
ariec61850_mms_server_session_tests
PRIVATE ARIEC61850::mms_server_core)
target_compile_features(
ariec61850_mms_server_session_tests PRIVATE cxx_std_20)
ariec61850_apply_warnings(ariec61850_mms_server_session_tests)
ariec61850_apply_sanitizers(ariec61850_mms_server_session_tests)
add_test(
NAME ariec61850_mms_server_session_tests
COMMAND ariec61850_mms_server_session_tests)

add_executable(
ariec61850_mms_connection_runtime_tests
embedded/mms_connection_runtime_hard_profile_smoke.cpp)
target_link_libraries(
ariec61850_mms_connection_runtime_tests
PRIVATE ARIEC61850::mms_server_core)
target_compile_features(
ariec61850_mms_connection_runtime_tests PRIVATE cxx_std_20)
ariec61850_apply_warnings(ariec61850_mms_connection_runtime_tests)
ariec61850_apply_sanitizers(ariec61850_mms_connection_runtime_tests)
add_test(
NAME ariec61850_mms_connection_runtime_tests
COMMAND ariec61850_mms_connection_runtime_tests)

if(ARIEC61850_BUILD_TOOLS)
add_test(
NAME ariec61850_pcap_interop_smoke
Expand All @@ -283,6 +350,9 @@ if(ARIEC61850_BUILD_TESTS)
add_test(
NAME ariec61850_sv_injector_loopback_smoke
COMMAND ariec61850_sv_injector --frames 160 --json)
add_test(
NAME ariec61850_static_ied_server_help
COMMAND ariec61850_static_ied_server --help)
endif()
endif()

Expand Down Expand Up @@ -313,4 +383,4 @@ if(ARIEC61850_BUILD_FUZZERS)
ariec61850_add_fuzzer(ariec61850_fuzz_association fuzz/fuzz_association.cpp)
ariec61850_add_fuzzer(ariec61850_fuzz_mms_services fuzz/fuzz_mms_services.cpp)
ariec61850_add_fuzzer(ariec61850_fuzz_reporting fuzz/fuzz_reporting.cpp)
endif()
endif()
65 changes: 65 additions & 0 deletions docs/STATIC_IED_SERVER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Static IEC 61850 server foundation

`ariec61850_static_ied_server` is the first runnable desktop adapter for the
bounded MMS server core. It deliberately keeps platform networking outside the
protocol engine, matching the same boundary required by ESP-IDF/lwIP.

```text
TCP listen/accept (WinSock or BSD sockets)
-> connected TcpByteStream callbacks
-> MmsStaticServerSession fixed-buffer pump
-> MmsStaticConnectionRuntime
-> MmsStaticApplicationDispatcher
-> static object/DataSet tables
```

The included reference model is read-only:

```text
ESP32S3IOLD0
|- LLN0 Mod.stVal
|- LPHD1 PhyHealth.stVal
`- GGIO1 Ind1.stVal ... Ind8.stVal
```

## Desktop smoke run

Build with `ARIEC61850_BUILD_TOOLS=ON`, then run:

```powershell
.\ariec61850_static_ied_server.exe --bind 0.0.0.0 --port 8102 --di-mask 0x35
```

Port 8102 is the unprivileged development default. Use `--port 102` for a
normal IEC 61850 lab endpoint when the operating system permits binding it.

Implemented server services in this slice:

- COTP connection accept;
- ACSE association accept and MMS Initiate response;
- `GetNameList`;
- `GetVariableAccessAttributes`;
- `GetNamedVariableListAttributes` when a static DataSet table is supplied;
- `Read`;
- bounded `Write` only for object entries that explicitly provide a write
callback (the included desktop model provides none).

## ESP32/lwIP integration boundary

The protocol core does not include an ESP-IDF header or own a listening socket.
An ESP-IDF application accepts a TCP/102 connection through lwIP, supplies
`TcpSendFn`/`TcpReceiveFn` callbacks, gives each connection its own
`MmsStaticConnectionRuntime`, `MmsStaticServerSession`, and fixed buffers, then
calls `poll_once()` from its task or event loop. Buffer capacities and the
maximum concurrent association count remain product-profile decisions.

## Current limits

- The desktop adapter serves connections sequentially. The core is per-session
and supports platform-owned multi-client orchestration, but the adapter does
not yet schedule multiple clients concurrently.
- Physical IEDScout browse/read acceptance is not yet recorded.
- The reference values are startup constants; a board adapter still needs to
bind the eight GGIO values to debounced input snapshots.
- Reporting, control, authentication, TLS, file services, and runtime SCL model
loading are outside this first read-only server slice.
23 changes: 23 additions & 0 deletions embedded/hard_profile/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,26 @@ add_executable(ariec61850_hard_mms_report_connection_smoke
${ARIEC61850_ROOT}/src/mms/static_report_connection.cpp)
ariec61850_configure_hard_target(ariec61850_hard_mms_report_connection_smoke)

add_executable(ariec61850_hard_mms_server_session_smoke
${ARIEC61850_ROOT}/tests/test_mms_static_server_session.cpp
${ARIEC61850_ROOT}/src/asn1/ber_span_reader.cpp
${ARIEC61850_ROOT}/src/osi/tpkt_span.cpp
${ARIEC61850_ROOT}/src/osi/cotp_span.cpp
${ARIEC61850_ROOT}/src/osi/session_span.cpp
${ARIEC61850_ROOT}/src/osi/presentation_span.cpp
${ARIEC61850_ROOT}/src/acse/association_span.cpp
${ARIEC61850_ROOT}/src/mms/pdu_span.cpp
${ARIEC61850_ROOT}/src/mms/services_span.cpp
${ARIEC61850_ROOT}/src/mms/write_span.cpp
${ARIEC61850_ROOT}/src/mms/data_set_span.cpp
${ARIEC61850_ROOT}/src/mms/static_object_table.cpp
${ARIEC61850_ROOT}/src/mms/static_write.cpp
${ARIEC61850_ROOT}/src/mms/static_data_set_table.cpp
${ARIEC61850_ROOT}/src/mms/static_dispatcher.cpp
${ARIEC61850_ROOT}/src/mms/connection_runtime.cpp
${ARIEC61850_ROOT}/src/mms/static_server_session.cpp)
ariec61850_configure_hard_target(ariec61850_hard_mms_server_session_smoke)

enable_testing()
add_test(NAME ariec61850_hard_sv_smoke COMMAND ariec61850_hard_sv_smoke)
add_test(NAME ariec61850_hard_goose_smoke COMMAND ariec61850_hard_goose_smoke)
Expand All @@ -230,6 +250,9 @@ add_test(NAME ariec61850_hard_mms_information_report_smoke COMMAND ariec61850_ha
add_test(NAME ariec61850_hard_mms_urcb_runtime_smoke COMMAND ariec61850_hard_mms_urcb_runtime_smoke)
add_test(NAME ariec61850_hard_mms_urcb_objects_smoke COMMAND ariec61850_hard_mms_urcb_objects_smoke)
add_test(NAME ariec61850_hard_mms_report_connection_smoke COMMAND ariec61850_hard_mms_report_connection_smoke)
add_test(
NAME ariec61850_hard_mms_server_session_smoke
COMMAND ariec61850_hard_mms_server_session_smoke)

message(STATUS "arstack61850 hard embedded profile: exceptions disabled=ON")
message(STATUS "arstack61850 hard embedded profile: RTTI disabled=ON")
Expand Down
1 change: 1 addition & 0 deletions include/ariec61850/mms/static_object_table.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct MmsStaticObjectEntry final {
bool mms_deletable{};
MmsStaticWriteCallback write{};
void* write_context{};
bool name_list_visible{true};

[[nodiscard]] constexpr bool writable() const noexcept {
return write != nullptr;
Expand Down
100 changes: 100 additions & 0 deletions include/ariec61850/mms/static_server_session.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

#include "ariec61850/embedded/io.hpp"
#include "ariec61850/mms/connection_runtime.hpp"

#include <cstddef>
#include <cstdint>
#include <span>

namespace ar::iec61850::mms {

enum class MmsStaticServerSessionStatus : std::uint8_t {
progressed,
response_pending,
would_block,
timed_out,
application_rejected,
peer_closed,
transport_error,
protocol_error,
receive_buffer_full,
invalid_configuration,
};

struct MmsStaticServerSessionResult final {
MmsStaticServerSessionStatus status{
MmsStaticServerSessionStatus::invalid_configuration};
MmsStaticConnectionStatus connection_status{
MmsStaticConnectionStatus::need_more};
std::size_t bytes_received{};
std::size_t bytes_sent{};
std::size_t buffered_input_bytes{};
std::size_t pending_output_bytes{};

[[nodiscard]] constexpr bool terminal() const noexcept {
return status == MmsStaticServerSessionStatus::peer_closed ||
status == MmsStaticServerSessionStatus::transport_error ||
status == MmsStaticServerSessionStatus::protocol_error ||
status == MmsStaticServerSessionStatus::receive_buffer_full ||
status == MmsStaticServerSessionStatus::invalid_configuration;
}
};

struct MmsStaticServerSessionBuffers final {
std::span<std::uint8_t> receive;
std::span<std::uint8_t> response;
std::span<std::uint8_t> workspace;

[[nodiscard]] constexpr bool valid() const noexcept {
return !receive.empty() && !response.empty() && !workspace.empty();
}
};

// Allocation-free bridge between a connected TCP byte stream and the portable
// MMS connection state machine. Listen/accept/close remain platform-owned.
// One instance is used per accepted connection, which lets desktop servers,
// FreeRTOS tasks and lwIP callbacks choose their own concurrency policy.
class MmsStaticServerSession final {
public:
constexpr MmsStaticServerSession(
MmsStaticConnectionRuntime& runtime,
const embedded::TcpByteStream stream,
const MmsStaticServerSessionBuffers buffers) noexcept
: runtime_{runtime}, stream_{stream}, buffers_{buffers} {}

// Performs at most one transport send/receive or one protocol dispatch.
// Call repeatedly from the platform event/task loop. Partial sends and
// fragmented/coalesced TPKT input are retained across calls.
[[nodiscard]] MmsStaticServerSessionResult poll_once() noexcept;

void reset() noexcept;

[[nodiscard]] constexpr std::size_t buffered_input_bytes() const noexcept {
return receive_size_;
}

[[nodiscard]] constexpr std::size_t pending_output_bytes() const noexcept {
return response_size_ - response_offset_;
}

private:
[[nodiscard]] MmsStaticServerSessionResult make_result(
MmsStaticServerSessionStatus status,
MmsStaticConnectionStatus connection_status,
std::size_t bytes_received = 0U,
std::size_t bytes_sent = 0U) const noexcept;

void consume_input(std::size_t consumed) noexcept;

MmsStaticConnectionRuntime& runtime_;
embedded::TcpByteStream stream_{};
MmsStaticServerSessionBuffers buffers_{};
std::size_t receive_size_{};
std::size_t response_offset_{};
std::size_t response_size_{};
bool terminal_{};
};

} // namespace ar::iec61850::mms
3 changes: 2 additions & 1 deletion src/mms/static_dispatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ namespace {
if (request.object_class == MmsNameListObjectClass::named_variable &&
request.scope == MmsNameScopeKind::domain_specific) {
for (const auto& object : objects.objects()) {
if (span_equals(request.domain_id, object.domain)) {
if (object.name_list_visible &&
span_equals(request.domain_id, object.domain)) {
if (count >= names.size()) {
return names.size() + 1U;
}
Expand Down
Loading
Loading