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
19 changes: 19 additions & 0 deletions embedded/mms_dispatcher_hard_profile_smoke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ constexpr std::array<std::uint8_t, 41U> kReadRequest{
0x1AU, 0x03U, 0x4CU, 0x44U, 0x30U,
0x1AU, 0x02U, 0x4DU, 0x31U};

constexpr std::array<std::uint8_t, 44U> kReadWithSpecificationRequest{
0xA0U, 0x2AU, 0x02U, 0x01U, 0x0DU,
0xA4U, 0x25U, 0x80U, 0x01U, 0xFFU,
0xA1U, 0x20U, 0xA0U, 0x1EU,
0x30U, 0x0DU, 0xA0U, 0x0BU, 0xA1U, 0x09U,
0x1AU, 0x03U, 0x4CU, 0x44U, 0x30U,
0x1AU, 0x02U, 0x52U, 0x31U,
0x30U, 0x0DU, 0xA0U, 0x0BU, 0xA1U, 0x09U,
0x1AU, 0x03U, 0x4CU, 0x44U, 0x30U,
0x1AU, 0x02U, 0x4DU, 0x31U};

constexpr std::array<std::uint8_t, 15U> kReadResponse{
0xA1U, 0x0DU, 0x02U, 0x01U, 0x0CU,
0xA4U, 0x08U, 0xA1U, 0x06U,
Expand Down Expand Up @@ -239,6 +250,14 @@ int main() {
return 6;
}

dispatched = dispatcher.dispatch(
kReadWithSpecificationRequest, response, workspace);
if (dispatched.status != mms::MmsStaticDispatchStatus::unsupported_request ||
dispatched.service != mms::MmsWireConfirmedService::read ||
dispatched.invoke_id != 13U) {
return 30;
}

std::array<std::uint8_t, 2U> tiny_workspace{};
dispatched = dispatcher.dispatch(kReadRequest, response, tiny_workspace);
if (dispatched.status != mms::MmsStaticDispatchStatus::workspace_too_small ||
Expand Down
162 changes: 155 additions & 7 deletions embedded/mms_negotiated_limits_hard_profile_smoke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,32 @@ constexpr std::array<std::uint8_t, 41U> kReadRequest{
return {wire::EncodeStatus::ok, required, required};
}

[[nodiscard]] mms::MmsStaticWriteResult write_boolean(
void* context,
const std::span<const std::uint8_t> encoded_data) noexcept {
if (context == nullptr || encoded_data.size() != 3U ||
encoded_data[0] != 0x83U || encoded_data[1] != 0x01U) {
return {false, 10U};
}
*static_cast<bool*>(context) = encoded_data[2] != 0U;
return {true, 0U};
}

[[nodiscard]] wire::EncodeResult read_boolean(
const void* context,
const std::span<std::uint8_t> destination) noexcept {
if (context == nullptr) {
return {wire::EncodeStatus::value_out_of_range, 0U, 3U};
}
if (destination.size() < 3U) {
return {wire::EncodeStatus::buffer_too_small, 0U, 3U};
}
destination[0] = 0x83U;
destination[1] = 0x01U;
destination[2] = *static_cast<const bool*>(context) ? 0xFFU : 0x00U;
return {wire::EncodeStatus::ok, 3U, 3U};
}

[[nodiscard]] bool set_maximum_mms_pdu(
std::array<std::uint8_t, kAssociationRequest.size()>& association,
const std::uint32_t value) noexcept {
Expand All @@ -94,6 +120,82 @@ constexpr std::array<std::uint8_t, 41U> kReadRequest{
return false;
}

[[nodiscard]] bool set_initiate_negotiation(
std::array<std::uint8_t, kAssociationRequest.size()>& association,
const std::uint8_t outstanding_calling,
const std::uint8_t outstanding_called,
const std::uint8_t nesting_level) noexcept {
bool calling_set = false;
bool called_set = false;
bool nesting_set = false;
for (std::size_t index = 0U; index + 3U <= association.size(); ++index) {
if (association[index + 1U] != 0x01U) {
continue;
}
if (!calling_set && association[index] == 0x81U) {
association[index + 2U] = outstanding_calling;
calling_set = true;
} else if (!called_set && association[index] == 0x82U) {
association[index + 2U] = outstanding_called;
called_set = true;
} else if (!nesting_set && association[index] == 0x83U) {
association[index + 2U] = nesting_level;
nesting_set = true;
}
}
return calling_set && called_set && nesting_set;
}

[[nodiscard]] bool decode_initiate_response(
const std::span<const std::uint8_t> response,
mms::MmsInitiateView& initiate) noexcept {
for (std::size_t index = 0U; index + 2U <= response.size(); ++index) {
if (response[index] != 0xA9U) {
continue;
}
const auto content = static_cast<std::size_t>(response[index + 1U]);
if ((response[index + 1U] & 0x80U) != 0U ||
content > response.size() - index - 2U) {
continue;
}
if (mms::MmsPduSpanCodec::try_decode_initiate_response_view(
response.subspan(index, content + 2U), initiate)) {
return true;
}
}
return false;
}

[[nodiscard]] wire::EncodeResult build_ten_variable_write(
std::array<std::uint8_t, 256U>& output) noexcept {
constexpr std::array<std::uint8_t, 15U> definition{
0x30U,0x0DU,0xA0U,0x0BU,0xA1U,0x09U,
0x1AU,0x03U,0x4CU,0x44U,0x30U,
0x1AU,0x02U,0x57U,0x31U};
std::array<std::uint8_t, 185U> service{};
std::size_t offset = 0U;
service[offset++] = 0xA0U;
service[offset++] = 0x81U;
service[offset++] = 0x96U;
for (std::size_t index = 0U; index < 10U; ++index) {
for (const auto byte : definition) {
service[offset++] = byte;
}
}
service[offset++] = 0xA0U;
service[offset++] = 0x1EU;
for (std::size_t index = 0U; index < 10U; ++index) {
service[offset++] = 0x83U;
service[offset++] = 0x01U;
service[offset++] = 0xFFU;
}
if (offset != service.size()) {
return {wire::EncodeStatus::value_out_of_range, 0U, 0U};
}
return mms::MmsPduSpanCodec::encode_confirmed_request_into(
20U, 5, true, service, output);
}

template <std::size_t N>
[[nodiscard]] wire::EncodeResult build_data_tpkt(
const std::span<const std::uint8_t> payload,
Expand Down Expand Up @@ -164,9 +266,17 @@ template <std::size_t N>
std::span<std::uint8_t> request,
std::span<std::uint8_t> response,
std::span<std::uint8_t> workspace,
std::span<std::uint8_t> scratch) noexcept {
std::span<std::uint8_t> scratch,
const std::uint8_t outstanding_calling = 10U,
const std::uint8_t outstanding_called = 10U,
const std::uint8_t nesting_level = 5U) noexcept {
auto association = kAssociationRequest;
if (!set_maximum_mms_pdu(association, maximum_mms_pdu)) {
if (!set_maximum_mms_pdu(association, maximum_mms_pdu) ||
!set_initiate_negotiation(
association,
outstanding_calling,
outstanding_called,
nesting_level)) {
return {};
}
const auto cotp = osi::CotpSpanCodec::encode_data_into(association, scratch);
Expand All @@ -189,18 +299,26 @@ int main() {
for (std::size_t index = 0U; index < large_value.size(); ++index) {
large_value[index] = static_cast<std::uint8_t>(index);
}
const std::array<mms::MmsStaticObjectEntry, 2U> objects{
bool write_state = false;
constexpr std::array<std::uint8_t, 2U> boolean_type{0x83U,0x00U};
const std::array<mms::MmsStaticObjectEntry, 3U> objects{
mms::MmsStaticObjectEntry{
"LD0", "R1", kOctet80Type, read_large_octets, &large_value, false},
mms::MmsStaticObjectEntry{
"LD0", "M1", kOctet80Type, read_large_octets, &large_value, false}};
"LD0", "M1", kOctet80Type, read_large_octets, &large_value, false},
mms::MmsStaticObjectEntry{
"LD0", "W1", boolean_type, read_boolean, &write_state, false,
write_boolean, &write_state}};
const mms::MmsStaticObjectTable object_table{objects};
const std::array<mms::MmsStaticDataSetMember, 1U> members{
mms::MmsStaticDataSetMember{"LD0", "R1"}};
const std::array<mms::MmsStaticDataSetEntry, 1U> data_sets{
mms::MmsStaticDataSetEntry{"LD0", "LLN0$Events", members, false}};
const mms::MmsStaticDataSetTable data_set_table{data_sets};
const mms::MmsStaticApplicationDispatcher dispatcher{object_table, data_set_table};
mms::MmsStaticDispatchPolicy dispatch_policy;
dispatch_policy.maximum_write_variables = 10U;
const mms::MmsStaticApplicationDispatcher dispatcher{
object_table, data_set_table, dispatch_policy};
if (!object_table.valid() || !data_set_table.valid_against(object_table)) {
return 1;
}
Expand Down Expand Up @@ -236,14 +354,44 @@ int main() {
if (!connect_cotp(runtime, 0x09U, request, response, workspace, scratch)) {
return 4;
}
const auto association = associate(runtime, 64U, request, response, workspace, scratch);
const auto association = associate(
runtime, 64U, request, response, workspace, scratch, 2U, 3U, 1U);
mms::MmsInitiateView negotiated;
if (!association.response_ready() ||
runtime.state() != mms::MmsStaticConnectionState::established ||
runtime.negotiated_tpdu_size_bytes() != 512U ||
runtime.negotiated_mms_pdu_size() != 64U) {
runtime.negotiated_mms_pdu_size() != 64U ||
!decode_initiate_response(
std::span<const std::uint8_t>{response}.first(association.bytes_written),
negotiated) ||
negotiated.maximum_mms_pdu_size != 64U ||
negotiated.maximum_outstanding_calling != 2U ||
negotiated.maximum_outstanding_called != 3U ||
negotiated.data_structure_nesting_level != 1U) {
return 5;
}

std::array<std::uint8_t, 256U> write_pdu{};
const auto write_encoded = build_ten_variable_write(write_pdu);
const auto write_tpkt = write_encoded.success()
? build_mms_tpkt(
std::span<const std::uint8_t>{write_pdu}.first(write_encoded.bytes_written),
request,
presentation,
scratch)
: wire::EncodeResult{};
if (!write_tpkt.success()) {
return 13;
}
const auto write_result = runtime.process_tcp_window(
std::span<const std::uint8_t>{request}.first(write_tpkt.bytes_written),
response,
workspace);
if (write_result.status != mms::MmsStaticConnectionStatus::peer_limit_exceeded ||
write_result.bytes_written != 0U || write_state) {
return 14;
}

// The two large Read results exceed the peer's 64-byte MMS PDU offer. No
// oversized confirmed response may be placed on the wire.
const auto read_tpkt = build_mms_tpkt(
Expand Down
25 changes: 24 additions & 1 deletion embedded/mms_pdu_hard_profile_smoke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,29 @@ int main() {
return 3;
}

const auto dynamic_default = mms::MmsPduSpanCodec::encode_initiate_response_into(
65'000U, 10U, 10U, 5U, buffer);
if (!dynamic_default.success() ||
dynamic_default.bytes_written != kInitiateResponse.size() ||
!matches(
std::span<const std::uint8_t>{buffer}.first(dynamic_default.bytes_written),
kInitiateResponse)) {
return 30;
}

const auto negotiated = mms::MmsPduSpanCodec::encode_initiate_response_into(
1'024U, 2U, 3U, 1U, buffer);
if (!negotiated.success() ||
!mms::MmsPduSpanCodec::try_decode_initiate_response_view(
std::span<const std::uint8_t>{buffer}.first(negotiated.bytes_written),
initiate) ||
initiate.maximum_mms_pdu_size != 1'024U ||
initiate.maximum_outstanding_calling != 2U ||
initiate.maximum_outstanding_called != 3U ||
initiate.data_structure_nesting_level != 1U) {
return 31;
}

constexpr std::array<std::uint8_t, 7U> confirmed_request_golden{
0xA0U, 0x05U, 0x02U, 0x01U, 0x07U, 0xA4U, 0x00U};
const auto confirmed_request_result =
Expand Down Expand Up @@ -229,4 +252,4 @@ int main() {
}

return 0;
}
}
5 changes: 5 additions & 0 deletions include/ariec61850/acse/association_span.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ class AcseSpanCodec final {
[[nodiscard]] static wire::EncodeResult build_accept_response_into(
const AssociationRequestView& request,
std::span<std::uint8_t> destination) noexcept;

[[nodiscard]] static wire::EncodeResult build_accept_response_into(
const AssociationRequestView& request,
std::span<const std::uint8_t> mms_initiate_response,
std::span<std::uint8_t> destination) noexcept;
};

} // namespace ar::iec61850::acse
7 changes: 7 additions & 0 deletions include/ariec61850/mms/pdu_span.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ class MmsPduSpanCodec final {
[[nodiscard]] static wire::EncodeResult encode_default_initiate_response_into(
std::span<std::uint8_t> destination) noexcept;

[[nodiscard]] static wire::EncodeResult encode_initiate_response_into(
std::uint32_t maximum_mms_pdu_size,
std::uint32_t maximum_outstanding_calling,
std::uint32_t maximum_outstanding_called,
std::uint32_t data_structure_nesting_level,
std::span<std::uint8_t> destination) noexcept;

[[nodiscard]] static bool try_decode_confirmed_request_view(
std::span<const std::uint8_t> bytes,
MmsConfirmedPduView& request) noexcept;
Expand Down
Loading
Loading