From 3d03ccd7b09bcb4a6483aa61d98202fe13c3ebe9 Mon Sep 17 00:00:00 2001 From: Roberto Martin Fantini Date: Fri, 30 May 2025 14:53:59 +0200 Subject: [PATCH 1/3] Add Xetra enum case --- src/mfast/xml_parser/field_builder.cpp | 142 +++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/src/mfast/xml_parser/field_builder.cpp b/src/mfast/xml_parser/field_builder.cpp index 8f7c0e1c..c2a38d88 100644 --- a/src/mfast/xml_parser/field_builder.cpp +++ b/src/mfast/xml_parser/field_builder.cpp @@ -9,6 +9,10 @@ #include #include "mfast/field_instructions.h" +#ifdef XETRA_FAST_SPECIFICATION + #include +#endif + using namespace tinyxml2; namespace mfast { @@ -520,6 +524,31 @@ void field_builder::add_template(const char *, template_instruction *inst) { << referenced_by_info(parent_->name())); } +#if defined(XETRA_FAST_SPECIFICATION) +bool parse_enum_value(const char **enum_element_names, + const uint64_t *enum_element_values, + uint64_t num_elements, const char *value_name, + uint64_t &result) { + boost::optional value_int; + try { + value_int = std::stoul(value_name); + } catch (...) {} + for (uint64_t i = 0; i < num_elements; ++i) { + // FAST 1.2 does not cleary specify what a default enum value refers to, + // search for a match in either name or value/deduce_value + if (std::strcmp(enum_element_names[i], value_name) == 0 || + (value_int.has_value() && enum_element_values[i] == *value_int)) { + if (enum_element_values) + result = enum_element_values[i]; + else + result = i; + return true; + } + } + + return false; +} +#else bool parse_enum_value(const char **enum_element_names, const uint64_t *enum_element_values, uint64_t num_elements, const char *value_name, @@ -537,6 +566,7 @@ bool parse_enum_value(const char **enum_element_names, return false; } +#endif bool parse_enum_value(const enum_field_instruction *inst, const char *value_name, uint64_t &result) { @@ -547,6 +577,117 @@ bool parse_enum_value(const enum_field_instruction *inst, struct tag_value; typedef boost::error_info value_info; + +#if defined(XETRA_FAST_SPECIFICATION) +void field_builder::visit(const enum_field_instruction *inst, void *) { + + const XMLElement *element = &this->element_; + if (!field_op::find_field_op_element(*element)) + element = content_element_; + field_op fop(inst, element, alloc()); + + const char **enum_element_names = inst->elements(); + uint64_t num_elements = inst->num_elements(); + const uint64_t *enum_element_values = inst->element_values(); + + const char *init_value_str = nullptr; + if (!fop.initial_value_.is_defined()) { + // if the defined flag is false, the content value is parsed string from + // XML + init_value_str = fop.initial_value_.get(); + } + + if (enum_element_names == nullptr) { + + std::deque names; + std::deque values; + + const XMLElement *xml_element = + content_element_->FirstChildElement("element"); + for (; xml_element != nullptr; + xml_element = xml_element->NextSiblingElement("element")) { + // Use fancier identifier if available (Eurex style) + const char *name_attr = xml_element->Attribute("id"); + // Otherwise revert to the specified name attribute + if (name_attr == nullptr) + name_attr = xml_element->Attribute("name"); + if (name_attr != nullptr) { + if (init_value_str && std::strcmp(name_attr, init_value_str) == 0) { + fop.initial_value_.set(names.size()); + } + names.push_back(string_dup(name_attr, alloc())); + + const char *value_str = xml_element->Attribute("value"); + if (value_str) { + uint64_t v = boost::lexical_cast(value_str); + if (values.empty() || v > values.back()) { + values.push_back(v); + } + } else { + // FAST 1.2 specification does not require a value attribute + if (values.empty()) + values.push_back(0); + else + values.push_back(values.back() + 1); + } + } + } + + if (values.size() != names.size()) { + throw std::runtime_error("Invalid value specification for enum elements"); + } + + num_elements = names.size(); + enum_element_names = static_cast( + alloc().allocate(names.size() * sizeof(const char *))); + std::copy(names.begin(), names.end(), enum_element_names); + + if (values.size()) { + uint64_t *values_array = static_cast( + alloc().allocate(values.size() * sizeof(uint64_t))); + std::copy(values.begin(), values.end(), values_array); + enum_element_values = values_array; + } + } else if (init_value_str) { + // In this case, the element names are already defined, but we haven't + // decide what the specified + // initial value is. + + uint64_t init_value; + if (parse_enum_value(enum_element_names, enum_element_values, num_elements, + init_value_str, init_value)) { + fop.initial_value_ = + value_storage(0); // reset the storage to defined value + fop.initial_value_.set(init_value); + } else { + BOOST_THROW_EXCEPTION( + fast_static_error("Unrecognized enum initial value : ") + << value_info(init_value_str)); + } + } + + if (!fop.initial_value_.is_defined()) { + if (fop.initial_value_.get() != nullptr) { + std::string msg = "Invalid initial value for enum : "; + throw std::runtime_error(msg + init_value_str); + } else { + // at this point if initial_value_ is still undefined, we should reset it + // to zero + fop.initial_value_.set(0); + } + } + + auto instruction = new (alloc()) enum_field_instruction( + fop.op_, get_presence(inst), get_id(inst), get_name(alloc()), + get_ns(inst, alloc()), fop.context_, + int_value_storage(fop.initial_value_), enum_element_names, + enum_element_values, num_elements, + inst->elements_ == nullptr ? nullptr : inst, inst->cpp_ns(), + parse_tag(inst)); + + parent_->add_instruction(instruction); +} +#else void field_builder::visit(const enum_field_instruction *inst, void *) { const XMLElement *element = &this->element_; @@ -647,6 +788,7 @@ void field_builder::visit(const enum_field_instruction *inst, void *) { parent_->add_instruction(instruction); } +#endif instruction_tag field_builder::parse_tag(const field_instruction *inst) { uint64_t value = inst->tag().to_uint64(); From 9bfe9b86975841793eef09c2c3096c0764397faf Mon Sep 17 00:00:00 2001 From: Roberto Martin Fantini Date: Fri, 30 May 2025 14:55:20 +0200 Subject: [PATCH 2/3] Unit test for the case of the xetra enum --- tests/CMakeLists.txt | 4 +++ tests/simple18.xml | 30 +++++++++++++++++ tests/xetra_enum_encoder_decoder.cpp | 43 +++++++++++++++++++++++++ tests/xetra_enum_encoder_decoder_v2.cpp | 43 +++++++++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 tests/simple18.xml create mode 100644 tests/xetra_enum_encoder_decoder.cpp create mode 100644 tests/xetra_enum_encoder_decoder_v2.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index be7a7a86..41082a32 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ FASTTYPEGEN_TARGET(simple_types16 simple16.xml) if(XETRA_FAST_SPECIFICATION) FASTTYPEGEN_TARGET(simple_types17 simple17.xml) + FASTTYPEGEN_TARGET(simple_types18 simple18.xml) endif(XETRA_FAST_SPECIFICATION) FASTTYPEGEN_TARGET(test_types1 test1.xml test2.xml) @@ -94,8 +95,11 @@ set(test_sources if(XETRA_FAST_SPECIFICATION) set(test_sources ${test_sources} ${FASTTYPEGEN_simple_types17_OUTPUTS} + ${FASTTYPEGEN_simple_types18_OUTPUTS} timestamp_encoder_decoder_v2.cpp timestamp_encoder_decoder.cpp + xetra_enum_encoder_decoder_v2.cpp + xetra_enum_encoder_decoder.cpp ) endif(XETRA_FAST_SPECIFICATION) diff --git a/tests/simple18.xml b/tests/simple18.xml new file mode 100644 index 00000000..16012949 --- /dev/null +++ b/tests/simple18.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/xetra_enum_encoder_decoder.cpp b/tests/xetra_enum_encoder_decoder.cpp new file mode 100644 index 00000000..75e07709 --- /dev/null +++ b/tests/xetra_enum_encoder_decoder.cpp @@ -0,0 +1,43 @@ +#include "catch.hpp" +#include + +#include "fast_test_coding_case.hpp" +#include "byte_stream.h" + +#include "simple18.h" + +using namespace test::coding; + +TEST_CASE("xetra enum test encoder/decoder","[xetra_enum_encoder_decoder]") +{ + SECTION("No optional enum / Active") + { + fast_test_coding_case test_case; + simple18::Test_1 test_1; + simple18::Test_1_mref test_1_mref = test_1.mref(); + test_1_mref.set_MDStatisticStatus().as_Active(); + REQUIRE(test_case.encoding(test_1.cref(),"\xc0\x81\x80",true)); + REQUIRE(test_case.decoding("\xc0\x81\x80",test_1.cref(),true)); + } + + SECTION("No optional enum / Inactive") + { + fast_test_coding_case test_case; + simple18::Test_1 test_1; + simple18::Test_1_mref test_1_mref = test_1.mref(); + test_1_mref.set_MDStatisticStatus().as_Inactive(); + REQUIRE(test_case.encoding(test_1.cref(),"\xe0\x81\x81\x80",true)); + REQUIRE(test_case.decoding("\xe0\x81\x81\x80",test_1.cref(),true)); + } + + SECTION("Optional enum") + { + fast_test_coding_case test_case; + simple18::Test_1 test_1; + simple18::Test_1_mref test_1_mref = test_1.mref(); + test_1_mref.set_MDStatisticStatus().as_Inactive(); + test_1_mref.set_MDStatisticFrequencyUnit().as_Minutes(); + REQUIRE(test_case.encoding(test_1.cref(),"\xe0\x81\x81\x83",true)); + REQUIRE(test_case.decoding("\xe0\x81\x81\x83",test_1.cref(),true)); + } +} \ No newline at end of file diff --git a/tests/xetra_enum_encoder_decoder_v2.cpp b/tests/xetra_enum_encoder_decoder_v2.cpp new file mode 100644 index 00000000..543d9418 --- /dev/null +++ b/tests/xetra_enum_encoder_decoder_v2.cpp @@ -0,0 +1,43 @@ +#include "catch.hpp" +#include + +#include "fast_test_coding_case_v2.hpp" +#include "byte_stream.h" + +#include "simple18.h" + +using namespace test::coding; + +TEST_CASE("xetra enum test encoder_V2/decoder_v2","[xetra_enum_encoder_v2_decoder_v2]") +{ + SECTION("No optional enum / Active") + { + fast_test_coding_case_v2 test_case; + simple18::Test_1 test_1; + simple18::Test_1_mref test_1_mref = test_1.mref(); + test_1_mref.set_MDStatisticStatus().as_Active(); + REQUIRE(test_case.encoding(test_1.cref(),"\xc0\x81\x80",true)); + REQUIRE(test_case.decoding("\xc0\x81\x80",test_1.cref(),true)); + } + + SECTION("No optional enum / Inactive") + { + fast_test_coding_case_v2 test_case; + simple18::Test_1 test_1; + simple18::Test_1_mref test_1_mref = test_1.mref(); + test_1_mref.set_MDStatisticStatus().as_Inactive(); + REQUIRE(test_case.encoding(test_1.cref(),"\xe0\x81\x81\x80",true)); + REQUIRE(test_case.decoding("\xe0\x81\x81\x80",test_1.cref(),true)); + } + + SECTION("Optional enum") + { + fast_test_coding_case_v2 test_case; + simple18::Test_1 test_1; + simple18::Test_1_mref test_1_mref = test_1.mref(); + test_1_mref.set_MDStatisticStatus().as_Inactive(); + test_1_mref.set_MDStatisticFrequencyUnit().as_Minutes(); + REQUIRE(test_case.encoding(test_1.cref(),"\xe0\x81\x81\x83",true)); + REQUIRE(test_case.decoding("\xe0\x81\x81\x83",test_1.cref(),true)); + } +} \ No newline at end of file From 8b0494330c9e1501251c370a9bbc61a8cd5a24fb Mon Sep 17 00:00:00 2001 From: Roberto Martin Fantini Date: Tue, 17 Jun 2025 21:19:28 +0200 Subject: [PATCH 3/3] Add Xetra tests --- .github/workflows/main.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cf167187..2227ea86 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,8 +16,14 @@ jobs: run: curl -L https://archives.boost.io/release/1.72.0/source/boost_1_72_0.tar.gz | tar zx shell: bash - name: configure - run: cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DXETRA_FAST_SPECIFICATION=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON + - name: configure xetra + run: cmake -B ${{github.workspace}}/build-xetra -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DXETRA_FAST_SPECIFICATION=ON - name: build - run: cmake --build build --parallel 2 + run: cmake --build ${{github.workspace}}/build --parallel 2 + - name: build xetra + run: cmake --build ${{github.workspace}}/build-xetra --parallel 2 - name: test - run: cd build && ctest -VV \ No newline at end of file + run: cd ${{github.workspace}}/build && ctest -VV + - name: test xetra + run: cd ${{github.workspace}}/build-xetra && ctest -VV