diff --git a/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp b/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp index 2b16395772..0cbabb2301 100644 --- a/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp +++ b/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp @@ -19,6 +19,9 @@ namespace data_types { constexpr auto SESSION_ID_LENGTH = 8; using SESSION_ID = std::array; // hexBinary, max length 8 +using ServiceID = uint16_t; +using ParameterSetID = int16_t; + constexpr auto GEN_CHALLENGE_LENGTH = 16; using GenChallenge = std::array; // base64 binary using PercentValue = uint8_t; // [0 - 100] @@ -65,6 +68,11 @@ enum class FaultCode { UnknownError, }; +enum class PaymentOption { + Contract, + ExternalPayment +}; + enum class EvseProcessing { Finished, Ongoing, diff --git a/lib/everest/iso15118/include/iso15118/message/d2/payment_service_selection.hpp b/lib/everest/iso15118/include/iso15118/message/d2/payment_service_selection.hpp new file mode 100644 index 0000000000..d73788a314 --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/payment_service_selection.hpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +#include +#include + +namespace iso15118::d2::msg { + +namespace data_types { +struct SelectedService { + ServiceID service_id; + std::optional parameter_set_id{std::nullopt}; +}; +using SelectedServiceList = std::vector; // [1 - 16] +} // namespace data_types + +struct PaymentServiceSelectionRequest { + Header header; + data_types::PaymentOption selected_payment_option; + data_types::SelectedServiceList selected_service_list; +}; + +struct PaymentServiceSelectionResponse { + Header header; + data_types::ResponseCode response_code; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/service_detail.hpp b/lib/everest/iso15118/include/iso15118/message/d2/service_detail.hpp new file mode 100644 index 0000000000..8b7e2d84f1 --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/service_detail.hpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +#include +#include +#include +#include + +namespace iso15118::d2::msg { + +namespace data_types { +struct Parameter { + std::string name; + std::variant value; +}; + +struct ParameterSet { + ParameterSetID parameter_set_id; + std::vector parameter; // [1 - 16] +}; +using ServiceParameterList = std::vector; // [1 - 255] +} // namespace data_types + +struct ServiceDetailRequest { + Header header; + data_types::ServiceID service_id; +}; + +struct ServiceDetailResponse { + Header header; + data_types::ResponseCode response_code; + data_types::ServiceID service_id; + std::optional service_parameter_list{std::nullopt}; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/service_discovery.hpp b/lib/everest/iso15118/include/iso15118/message/d2/service_discovery.hpp new file mode 100644 index 0000000000..c18a2e410c --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/service_discovery.hpp @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace iso15118::d2::msg { + +namespace data_types { +using ServiceName = std::string; // MaxLength: 32 +using ServiceScope = std::string; // MaxLength: 64 +using ServiceScope = std::string; // MaxLength: 32 +using PaymentOptionList = std::vector; // [1 - 2] + +enum class ServiceCategory { + EvCharging, + Internet, + ContractCertificate, + OtherCustom +}; + +enum class EnergyTransferMode { + AC_single_phase_core, + AC_three_phase_core, + DC_core, + DC_extended, + DC_combo_core, + DC_unique +}; +using SupportedEnergyTransferMode = std::vector; // MaxLength: 6 + +struct Service { + ServiceID service_id; + std::optional service_name{std::nullopt}; + ServiceCategory service_category; + std::optional service_scope{std::nullopt}; + bool FreeService; +}; +using ServiceList = std::vector; // [1 - 8] + +struct ChargeService : Service { + SupportedEnergyTransferMode supported_energy_transfer_mode; +}; +} // namespace data_types + +struct ServiceDiscoveryRequest { + Header header; + std::optional service_scope{std::nullopt}; + std::optional service_category{std::nullopt}; +}; + +struct ServiceDiscoveryResponse { + Header header; + data_types::ResponseCode response_code; + data_types::PaymentOptionList payment_option_list; + data_types::ChargeService charge_service; + std::optional service_list{std::nullopt}; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/type.hpp b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp index d87872d6f9..fe289336c9 100644 --- a/lib/everest/iso15118/include/iso15118/message/d2/type.hpp +++ b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp @@ -12,6 +12,12 @@ enum class Type { SupportedAppProtocolRes, SessionSetupReq, SessionSetupRes, + ServiceDiscoveryReq, + ServiceDiscoveryRes, + ServiceDetailReq, + ServiceDetailRes, + PaymentServiceSelectionReq, + PaymentServiceSelectionRes, AuthorizationReq, AuthorizationRes, CableCheckReq, @@ -49,6 +55,12 @@ CREATE_TYPE_TRAIT(SupportedAppProtocolRequest, SupportedAppProtocolReq); CREATE_TYPE_TRAIT(SupportedAppProtocolResponse, SupportedAppProtocolRes); CREATE_TYPE_TRAIT(SessionSetupRequest, SessionSetupReq); CREATE_TYPE_TRAIT(SessionSetupResponse, SessionSetupRes); +CREATE_TYPE_TRAIT(ServiceDiscoveryRequest, ServiceDiscoveryReq); +CREATE_TYPE_TRAIT(ServiceDiscoveryResponse, ServiceDiscoveryRes); +CREATE_TYPE_TRAIT(ServiceDetailRequest, ServiceDetailReq); +CREATE_TYPE_TRAIT(ServiceDetailResponse, ServiceDetailRes); +CREATE_TYPE_TRAIT(PaymentServiceSelectionRequest, PaymentServiceSelectionReq); +CREATE_TYPE_TRAIT(PaymentServiceSelectionResponse, PaymentServiceSelectionRes); CREATE_TYPE_TRAIT(AuthorizationRequest, AuthorizationReq); CREATE_TYPE_TRAIT(AuthorizationResponse, AuthorizationRes); CREATE_TYPE_TRAIT(DC_CableCheckRequest, CableCheckReq); diff --git a/lib/everest/iso15118/src/iso15118/CMakeLists.txt b/lib/everest/iso15118/src/iso15118/CMakeLists.txt index e7e35dadc9..ca278c8485 100644 --- a/lib/everest/iso15118/src/iso15118/CMakeLists.txt +++ b/lib/everest/iso15118/src/iso15118/CMakeLists.txt @@ -75,6 +75,9 @@ target_sources(iso15118 message/d2/dc_welding_detection.cpp message/d2/variant.cpp message/d2/msg_data_types.cpp + message/d2/payment_service_selection.cpp + message/d2/service_detail.cpp + message/d2/service_discovery.cpp message/d2/session_setup.cpp tbd_controller.cpp diff --git a/lib/everest/iso15118/src/iso15118/message/d2/payment_service_selection.cpp b/lib/everest/iso15118/src/iso15118/message/d2/payment_service_selection.cpp new file mode 100644 index 0000000000..4a0c67b89c --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/payment_service_selection.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 Pionix GmbH and Contributors to EVerest +#include + +#include + +#include +#include + +#include + +namespace iso15118::d2::msg { + +template <> void convert(const struct iso2_PaymentServiceSelectionReqType& in, PaymentServiceSelectionRequest& out) { + cb_convert_enum(in.SelectedPaymentOption, out.selected_payment_option); + + for (int i = 0; i < in.SelectedServiceList.SelectedService.arrayLen; i++) { + const auto& in_service = in.SelectedServiceList.SelectedService.array[i]; + data_types::SelectedService service; + service.service_id = in_service.ServiceID; + if (in_service.ParameterSetID_isUsed) { + service.parameter_set_id = in_service.ParameterSetID; + } + out.selected_service_list.push_back(service); + } +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_PaymentServiceSelectionReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const PaymentServiceSelectionResponse& in, struct iso2_PaymentServiceSelectionResType& out) { + init_iso2_PaymentServiceSelectionResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); +} + +template <> int serialize_to_exi(const PaymentServiceSelectionResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.PaymentServiceSelectionRes); + convert(in, doc.V2G_Message.Body.PaymentServiceSelectionRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const PaymentServiceSelectionResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/service_detail.cpp b/lib/everest/iso15118/src/iso15118/message/d2/service_detail.cpp new file mode 100644 index 0000000000..500c0154f1 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/service_detail.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 Pionix GmbH and Contributors to EVerest +#include +#include + +#include + +#include +#include + +#include +#include + +namespace iso15118::d2::msg { + +template <> void convert(const data_types::ServiceParameterList& in, struct iso2_ServiceParameterListType& out) { + int parameter_set_index = 0; + for (auto const& parameter_set : in) { + if (parameter_set_index >= iso2_ParameterSetType_5_ARRAY_SIZE) { + // TODO(kd): Should we raise exception here? I've seen that in -20 there is often no bounds checking at all. + // Example: src/iso15118/message/service_detail.cpp:274 + break; + } + + auto& out_parameter_set = out.ParameterSet.array[parameter_set_index++]; + out_parameter_set.ParameterSetID = parameter_set.parameter_set_id; + + int parameter_idx = 0; + for (auto const& parameter : parameter_set.parameter) { + if (parameter_idx >= iso2_ParameterType_16_ARRAY_SIZE) { + // TODO(kd): Should we raise exception here? I've seen that in -20 there is often no bounds checking at + // all. Example: src/iso15118/message/service_detail.cpp:274 + break; + } + auto& out_param = out_parameter_set.Parameter.array[parameter_idx++]; + CPP2CB_STRING(parameter.name, out_param.Name); + + if (std::holds_alternative(parameter.value)) { + out_param.boolValue = std::get(parameter.value); + out_param.boolValue_isUsed = true; + } else if (std::holds_alternative(parameter.value)) { + out_param.byteValue = std::get(parameter.value); + out_param.byteValue_isUsed = true; + } else if (std::holds_alternative(parameter.value)) { + out_param.shortValue = std::get(parameter.value); + out_param.shortValue_isUsed = true; + } else if (std::holds_alternative(parameter.value)) { + out_param.intValue = std::get(parameter.value); + out_param.intValue_isUsed = true; + } else if (std::holds_alternative(parameter.value)) { + convert(std::get(parameter.value), out_param.physicalValue); + out_param.physicalValue_isUsed = true; + } else if (std::holds_alternative(parameter.value)) { + CPP2CB_STRING(std::get(parameter.value), out_param.stringValue); + out_param.stringValue_isUsed = true; + } + } + out_parameter_set.Parameter.arrayLen = parameter_set.parameter.size(); + } + out.ParameterSet.arrayLen = in.size(); +} + +template <> void convert(const struct iso2_ServiceDetailReqType& in, ServiceDetailRequest& out) { + out.service_id = in.ServiceID; +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_ServiceDetailReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const ServiceDetailResponse& in, struct iso2_ServiceDetailResType& out) { + init_iso2_ServiceDetailResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); + out.ServiceID = in.service_id; + + CPP2CB_CONVERT_IF_USED(in.service_parameter_list, out.ServiceParameterList); +} + +template <> int serialize_to_exi(const ServiceDetailResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.ServiceDetailRes); + convert(in, doc.V2G_Message.Body.ServiceDetailRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const ServiceDetailResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/service_discovery.cpp b/lib/everest/iso15118/src/iso15118/message/d2/service_discovery.cpp new file mode 100644 index 0000000000..d2ff5da2c9 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/service_discovery.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#include +#include + +#include + +#include +#include + +#include + +namespace iso15118::d2::msg { + +template <> void convert(const data_types::Service& in, struct iso2_ServiceType& out) { + out.ServiceID = in.service_id; + CPP2CB_STRING_IF_USED(in.service_name, out.ServiceName); + cb_convert_enum(in.service_category, out.ServiceCategory); + CPP2CB_STRING_IF_USED(in.service_scope, out.ServiceScope); + out.FreeService = in.FreeService; +} + +template <> void convert(const data_types::ChargeService& in, struct iso2_ChargeServiceType& out) { + out.ServiceID = in.service_id; + CPP2CB_STRING_IF_USED(in.service_name, out.ServiceName); + cb_convert_enum(in.service_category, out.ServiceCategory); + CPP2CB_STRING_IF_USED(in.service_scope, out.ServiceScope); + out.FreeService = in.FreeService; + + int modeIndex = 0; + for (auto const& mode : in.supported_energy_transfer_mode) { + // TODO(kd) guard for accessing iso2_ struct array out of bounds? + cb_convert_enum(mode, out.SupportedEnergyTransferMode.EnergyTransferMode.array[modeIndex++]); + } + out.SupportedEnergyTransferMode.EnergyTransferMode.arrayLen = in.supported_energy_transfer_mode.size(); +} + +template <> void convert(const struct iso2_ServiceDiscoveryReqType& in, ServiceDiscoveryRequest& out) { + if (in.ServiceCategory_isUsed) { + d2::msg::data_types::ServiceCategory category{}; + cb_convert_enum(in.ServiceCategory, category); + out.service_category = category; + } + CB2CPP_STRING_IF_USED(in.ServiceScope, out.service_scope); +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_ServiceDiscoveryReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const ServiceDiscoveryResponse& in, struct iso2_ServiceDiscoveryResType& out) { + init_iso2_ServiceDiscoveryResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); + + int optionIndex = 0; + for (auto const& option : in.payment_option_list) { + cb_convert_enum(option, out.PaymentOptionList.PaymentOption.array[optionIndex++]); + } + out.PaymentOptionList.PaymentOption.arrayLen = in.payment_option_list.size(); + + convert(in.charge_service, out.ChargeService); + + int serviceIndex = 0; + if (in.service_list) { + for (auto const& service : in.service_list.value()) { + convert(service, out.ServiceList.Service.array[serviceIndex++]); + } + out.ServiceList.Service.arrayLen = in.service_list->size(); + CB_SET_USED(out.ServiceList); + } +} + +template <> int serialize_to_exi(const ServiceDiscoveryResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.ServiceDiscoveryRes); + convert(in, doc.V2G_Message.Body.ServiceDiscoveryRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const ServiceDiscoveryResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp index e82d8a5b14..38163e75ca 100644 --- a/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp +++ b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp @@ -45,6 +45,12 @@ void handle_v2g(VariantAccess& va) { if (doc.V2G_Message.Body.SessionSetupReq_isUsed) { insert_type(va, doc.V2G_Message.Body.SessionSetupReq, doc.V2G_Message.Header); + } else if (doc.V2G_Message.Body.ServiceDiscoveryReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.ServiceDiscoveryReq, doc.V2G_Message.Header); + } else if (doc.V2G_Message.Body.ServiceDetailReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.ServiceDetailReq, doc.V2G_Message.Header); + } else if (doc.V2G_Message.Body.PaymentServiceSelectionReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.PaymentServiceSelectionReq, doc.V2G_Message.Header); } else if (doc.V2G_Message.Body.AuthorizationReq_isUsed) { insert_type(va, doc.V2G_Message.Body.AuthorizationReq, doc.V2G_Message.Header); } else if (doc.V2G_Message.Body.CableCheckReq_isUsed) { diff --git a/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt index 6a9b91885b..525eb01dfb 100644 --- a/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt +++ b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt @@ -14,6 +14,9 @@ create_exi_test_target(dc_cable_check) create_exi_test_target(dc_current_demand) create_exi_test_target(dc_pre_charge) create_exi_test_target(dc_welding_detection) +create_exi_test_target(service_discovery) +create_exi_test_target(service_detail) +create_exi_test_target(payment_service_selection) create_exi_test_target(session_setup) create_exi_test_target(authorization) diff --git a/lib/everest/iso15118/test/exi/cb/iso2/payment_service_selection.cpp b/lib/everest/iso15118/test/exi/cb/iso2/payment_service_selection.cpp new file mode 100644 index 0000000000..3203c8a240 --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/payment_service_selection.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include + +#include "helper.hpp" + +using namespace iso15118; +namespace dt = d2::msg::data_types; + +SCENARIO("Ser/Deserialize d2 payment service selection messages") { + + GIVEN("Deserialize payment service selection req") { + uint8_t doc_raw[] = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, + 0x13, 0x4B, 0x51, 0x32, 0x06, 0x30, 0x01, 0x08}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::PaymentServiceSelectionReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.selected_payment_option == dt::PaymentOption::ExternalPayment); + REQUIRE(msg.selected_service_list[0].service_id == 99); + REQUIRE(msg.selected_service_list[0].parameter_set_id == 2); + } + } + GIVEN("Serialize payment service selection res") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + const auto res = d2::msg::PaymentServiceSelectionResponse{header, dt::ResponseCode::OK}; + + std::vector expected = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, + 0xCE, 0xC2, 0x13, 0x4B, 0x51, 0x40, 0x00}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); + } + } +} diff --git a/lib/everest/iso15118/test/exi/cb/iso2/service_detail.cpp b/lib/everest/iso15118/test/exi/cb/iso2/service_detail.cpp new file mode 100644 index 0000000000..fd87f1a667 --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/service_detail.cpp @@ -0,0 +1,68 @@ +#include + +#include +#include + +#include "helper.hpp" + +using namespace iso15118; +namespace dt = d2::msg::data_types; + +SCENARIO("Ser/Deserialize d2 service detail messages") { + + GIVEN("Deserialize service detail req") { + uint8_t doc_raw[] = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, 0x51, 0x91, 0x8C, 0x00}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::ServiceDetailReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.service_id == 99); + } + } + GIVEN("Serialize service detail res") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + auto res = d2::msg::ServiceDetailResponse{}; + res.header = header; + res.response_code = dt::ResponseCode::OK; + res.service_id = 99; + auto parameterSet = dt::ParameterSet{}; + parameterSet.parameter_set_id = 50; + + dt::Parameter boolValue{"bool", true}; + dt::Parameter byteValue{"byte", (int8_t)8}; + dt::Parameter shortValue{"short", (int16_t)16}; + dt::Parameter intValue{"int", (int32_t)32}; + dt::Parameter physicalValue{"physical", dt::PhysicalValue{55, -1, dt::UnitSymbol::A}}; + dt::Parameter stringValue{"string", "Foo Bar"}; + + parameterSet.parameter.push_back(boolValue); + parameterSet.parameter.push_back(byteValue); + parameterSet.parameter.push_back(shortValue); + parameterSet.parameter.push_back(intValue); + parameterSet.parameter.push_back(physicalValue); + parameterSet.parameter.push_back(stringValue); + res.service_parameter_list = {parameterSet}; + + std::vector expected = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, 0x51, 0xA0, + 0x01, 0x8C, 0x01, 0x90, 0x06, 0x62, 0x6F, 0x6F, 0x6C, 0x08, 0x01, 0x98, 0x9E, + 0x5D, 0x19, 0x4A, 0x20, 0x00, 0xEE, 0x6D, 0x0D, 0xEE, 0x4E, 0x88, 0x10, 0x00, + 0x2B, 0x4B, 0x73, 0xA3, 0x08, 0x00, 0x14, 0xE0, 0xD0, 0xF2, 0xE6, 0xD2, 0xC6, + 0xC2, 0xD9, 0x04, 0x18, 0x1B, 0x80, 0x10, 0xE6, 0xE8, 0xE4, 0xD2, 0xDC, 0xCF, + 0x41, 0x28, 0xCD, 0xED, 0xE4, 0x08, 0x4C, 0x2E, 0x42, 0x80}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); // FIXME: Failing right now + } + } +} diff --git a/lib/everest/iso15118/test/exi/cb/iso2/service_discovery.cpp b/lib/everest/iso15118/test/exi/cb/iso2/service_discovery.cpp new file mode 100644 index 0000000000..78267d8e8f --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/service_discovery.cpp @@ -0,0 +1,95 @@ +#include + +#include +#include + +#include "helper.hpp" + +#include +#include + +using namespace iso15118; +namespace dt = d2::msg::data_types; + +SCENARIO("Ser/Deserialize d2 service discovery messages") { + GIVEN("Deserialize service discovery req") { + uint8_t doc_raw[] = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, 0x51, 0xB0, + 0x18, 0xA8, 0xCA, 0xE6, 0xE8, 0x40, 0xE6, 0xC6, 0xDE, 0xE0, 0xCA, 0x00, 0x00}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::ServiceDiscoveryReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.service_scope.value() == "Test scope"); + REQUIRE(msg.service_category.value() == dt::ServiceCategory::EvCharging); + } + } + GIVEN("Serialize service discovery res - minimal") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + auto res = d2::msg::ServiceDiscoveryResponse{}; + res.header = header; + res.response_code = dt::ResponseCode::OK; + res.payment_option_list = { + dt::PaymentOption::ExternalPayment, + }; + auto service = dt::ChargeService{}; + service.service_id = 1; + service.supported_energy_transfer_mode = {dt::EnergyTransferMode::DC_extended}; + service.service_category = dt::ServiceCategory::EvCharging; + service.FreeService = true; + res.charge_service = service; + + std::vector expected = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, + 0x4B, 0x51, 0xC0, 0x01, 0x20, 0x04, 0x82, 0x83, 0x24}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); + } + } + GIVEN("Serialize service discovery res - all fields present") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + auto res = d2::msg::ServiceDiscoveryResponse{}; + res.header = header; + res.response_code = dt::ResponseCode::OK; + res.payment_option_list = { + dt::PaymentOption::ExternalPayment, + }; + auto chargeService = dt::ChargeService{}; + chargeService.service_id = 1; + chargeService.service_name = "Service Foo"; + chargeService.supported_energy_transfer_mode = {dt::EnergyTransferMode::DC_extended}; + chargeService.service_scope = "Test scope"; + chargeService.service_category = dt::ServiceCategory::EvCharging; + chargeService.FreeService = true; + res.charge_service = chargeService; + auto service = dt::Service{}; + service.service_id = 99; + service.service_name = "Service Bar"; + service.service_scope = "Test scope 2"; + service.service_category = dt::ServiceCategory::Internet; + service.FreeService = true; + res.service_list = {service}; + + std::vector expected = { + 0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, 0x51, 0xC0, 0x01, 0x20, 0x04, 0x03, 0x54, + 0xD9, 0x5C, 0x9D, 0x9A, 0x58, 0xD9, 0x48, 0x11, 0x9B, 0xDB, 0xC0, 0x01, 0x8A, 0x8C, 0xAE, 0x6E, 0x84, 0x0E, + 0x6C, 0x6D, 0xEE, 0x0C, 0xA2, 0x0C, 0x80, 0xC6, 0x01, 0xAA, 0x6C, 0xAE, 0x4E, 0xCD, 0x2C, 0x6C, 0xA4, 0x08, + 0x4C, 0x2E, 0x41, 0x00, 0xE5, 0x46, 0x57, 0x37, 0x42, 0x07, 0x36, 0x36, 0xF7, 0x06, 0x52, 0x03, 0x21, 0x10}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); + } + } +}