// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2023 chargebyte GmbH
// Copyright (C) 2023 Contributors to EVerest
#include <cbv2g/common/exi_bitstream.h>
#include <cbv2g/exi_v2gtp.h> //for V2GTP_HEADER_LENGTHs
#include <cbv2g/iso_2/iso2_msgDefDatatypes.h>
#include <cbv2g/iso_2/iso2_msgDefDecoder.h>
#include <cbv2g/iso_2/iso2_msgDefEncoder.h>
#include <everest/tls/openssl_util.hpp>

#include <algorithm>
#include <cstdint>
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "crypto/crypto_openssl.hpp"
using namespace openssl;
using namespace crypto::openssl;

#include "iso_server.hpp"
#include "log.hpp"
#include "tools.hpp"
#include "v2g_ctx.hpp"
#include "v2g_server.hpp"

#define MQTT_MAX_PAYLOAD_SIZE         268435455
#define V2G_SECC_MSG_CERTINSTALL_TIME 4500

constexpr uint16_t SAE_V2H = 28472;
constexpr uint16_t SAE_V2G = 28473;

/*!
 * \brief iso_validate_state This function checks whether the received message is expected and valid at this
 * point in the communication sequence state machine. The current V2G msg type must be set with the current V2G msg
 * state. [V2G2-538]
 * \param state is the current state of the charging session.
 * \param current_v2g_msg is the current handled V2G message.
 * \param is_dc_charging is \c true if it is a DC charging session.
 * \return Returns a iso2_responseCode with sequence error if current_v2g_msg is not expected, otherwise OK.
 */
static iso2_responseCodeType iso_validate_state(int state, enum V2gMsgTypeId current_v2g_msg, bool is_dc_charging) {

    int allowed_requests =
        (true == is_dc_charging)
            ? iso_dc_states[state].allowed_requests
            : iso_ac_states[state].allowed_requests; // dc_charging is determined in charge_parameter. dc
    return (allowed_requests & (1 << current_v2g_msg)) ? iso2_responseCodeType_OK
                                                       : iso2_responseCodeType_FAILED_SequenceError;
}

/*!
 * \brief iso_validate_response_code This function checks if an external error has occurred (sequence error, user abort)
 * ... ). \param iso_response_code is a pointer to the current response code. The value will be modified if an external
 *  error has occurred.
 * \param conn the structure with the external error information.
 * \return Returns \c V2G_EVENT_SEND_AND_TERMINATE if the charging must be terminated after sending the response
 * message, returns \c V2G_EVENT_TERMINATE_CONNECTION if charging must be aborted immediately and \c V2G_EVENT_NO_EVENT
 * if no error
 */
static v2g_event iso_validate_response_code(iso2_responseCodeType* const v2g_response_code,
                                            struct v2g_connection const* const conn) {
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;
    iso2_responseCodeType response_code_tmp;

    if (conn->ctx->is_connection_terminated == true) {
        dlog(DLOG_LEVEL_ERROR, "Connection is terminated. Abort charging");
        return V2G_EVENT_TERMINATE_CONNECTION;
    }

    /* If MQTT user abort or emergency shutdown has occurred */
    if ((conn->ctx->stop_hlc == true) || (conn->ctx->intl_emergency_shutdown == true)) {
        *v2g_response_code = iso2_responseCodeType_FAILED;
    }

    /* [V2G2-460]: check whether the session id matches the expected one of the active session */
    *v2g_response_code =
        ((conn->ctx->current_v2g_msg != V2G_SESSION_SETUP_MSG) && (conn->ctx->ev_v2g_data.received_session_id != 0) &&
         (conn->ctx->evse_v2g_data.session_id != conn->ctx->ev_v2g_data.received_session_id))
            ? iso2_responseCodeType_FAILED_UnknownSession
            : *v2g_response_code;

    /* [V2G-DC-390]: at this point we must check whether the given request is valid at this step;
     * the idea is that we catch this error in each function below to respond with a valid
     * encoded message; note, that the handler functions below must not access v2g_session in
     * error path, since it might not be set, yet!
     */
    response_code_tmp =
        iso_validate_state(conn->ctx->state, conn->ctx->current_v2g_msg, conn->ctx->is_dc_charger); // [V2G2-538]

    *v2g_response_code = (response_code_tmp >= iso2_responseCodeType_FAILED) ? response_code_tmp : *v2g_response_code;

    /* [V2G2-539] SequenceError always terminates the connection regardless of configuration.
     * For other FAILED codes, termination depends on the terminate_connection_on_failed_response setting. */
    if (*v2g_response_code == iso2_responseCodeType_FAILED_SequenceError ||
        ((conn->ctx->terminate_connection_on_failed_response == true) &&
         (*v2g_response_code >= iso2_responseCodeType_FAILED))) {
        next_event = V2G_EVENT_SEND_AND_TERMINATE; // [V2G2-539], [V2G2-034] Send response and terminate tcp-connection
    }

    /* log failed response code message */
    if ((*v2g_response_code >= iso2_responseCodeType_FAILED) &&
        (*v2g_response_code <= iso2_responseCodeType_FAILED_CertificateRevoked)) {
        dlog(DLOG_LEVEL_ERROR, "Failed response code detected for message \"%s\", error: %s",
             v2g_msg_type[conn->ctx->current_v2g_msg], isoResponse[*v2g_response_code]);
    }

    return next_event;
}

/*!
 * \brief populate_ac_evse_status This function configures the evse_status struct
 * \param ctx is the V2G context
 * \param evse_status is the destination struct
 */
static void populate_ac_evse_status(struct v2g_context* ctx, struct iso2_AC_EVSEStatusType* evse_status) {
    evse_status->EVSENotification = (iso2_EVSENotificationType)ctx->evse_v2g_data.evse_notification;
    evse_status->NotificationMaxDelay = ctx->evse_v2g_data.notification_max_delay;
    evse_status->RCD = ctx->evse_v2g_data.rcd;
}

/*!
 * \brief check_iso2_charging_profile_values This function checks if EV charging profile values are within permissible
 * ranges \param req is the PowerDeliveryReq \param res is the PowerDeliveryRes \param conn holds the structure with the
 * V2G msg pair \param sa_schedule_tuple_idx is the index of SA schedule tuple
 */
static void check_iso2_charging_profile_values(iso2_PowerDeliveryReqType* req, iso2_PowerDeliveryResType* res,
                                               v2g_connection* conn, uint8_t sa_schedule_tuple_idx) {
    if (req->ChargingProfile_isUsed == (unsigned int)1) {

        const struct iso2_PMaxScheduleType* evse_p_max_schedule =
            &conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[sa_schedule_tuple_idx].PMaxSchedule;

        uint32_t ev_time_sum = 0;                     // Summed EV relative time interval
        uint32_t evse_time_sum = 0;                   // Summed EVSE relative time interval
        uint8_t evse_idx = 0;                         // Actual PMaxScheduleEntry index
        bool ev_time_is_within_profile_entry = false; /* Is true if the summed EV relative time interval
                                                         is within the actual EVSE time interval */

        /* Check if the EV ChargingProfileEntryStart time and PMax value fits with the provided EVSE PMaxScheduleEntry
         * list. [V2G2-293] */
        for (uint8_t ev_idx = 0;
             ev_idx < req->ChargingProfile.ProfileEntry.arrayLen && (res->ResponseCode == iso2_responseCodeType_OK);
             ev_idx++) {

            ev_time_sum += req->ChargingProfile.ProfileEntry.array[ev_idx].ChargingProfileEntryStart;

            while (evse_idx < evse_p_max_schedule->PMaxScheduleEntry.arrayLen &&
                   (ev_time_is_within_profile_entry == false)) {

                /* Check if EV ChargingProfileEntryStart value is within one EVSE schedule entry.
                 * The last element must be checked separately, because of the duration value */

                /* If we found an entry which fits in the EVSE time schedule, check if the next EV time slot fits as
                 * well Otherwise check if the next time interval fits in the EVSE time schedule */
                evse_time_sum += evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].RelativeTimeInterval.start;

                /* Check the time intervals, in the last schedule element the duration value must be considered */
                if (evse_idx < (evse_p_max_schedule->PMaxScheduleEntry.arrayLen - 1)) {
                    ev_time_is_within_profile_entry =
                        (ev_time_sum >= evse_time_sum) &&
                        (ev_time_sum <
                         (evse_time_sum +
                          evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx + 1].RelativeTimeInterval.start));
                } else {
                    ev_time_is_within_profile_entry =
                        (ev_time_sum >= evse_time_sum) &&
                        (ev_time_sum <=
                         (evse_time_sum +
                          evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].RelativeTimeInterval.duration_isUsed *
                              evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].RelativeTimeInterval.duration));
                }

                if (ev_time_is_within_profile_entry == true) {
                    /* Check if ev ChargingProfileEntryMaxPower element is equal to or smaller than the limits in
                     * respective elements of the PMaxScheduleType */
                    if ((req->ChargingProfile.ProfileEntry.array[ev_idx].ChargingProfileEntryMaxPower.Value *
                         pow(10,
                             req->ChargingProfile.ProfileEntry.array[ev_idx].ChargingProfileEntryMaxPower.Multiplier)) >
                        (evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].PMax.Value *
                         pow(10, evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].PMax.Multiplier))) {
                        res->ResponseCode = iso2_responseCodeType_FAILED_ChargingProfileInvalid; // [V2G2-224]
                        // [V2G2-225] [V2G2-478]
                        //  setting response code is commented because some EVs do not support schedules correctly
                        dlog(DLOG_LEVEL_WARNING,
                             "EV's charging profile is invalid (ChargingProfileEntryMaxPower too high)!");
                        break;
                    }
                }
                /* If the last EVSE element is reached and ChargingProfileEntryStart time doesn't fit */
                else if (evse_idx == (evse_p_max_schedule->PMaxScheduleEntry.arrayLen - 1)) {
                    // res->ResponseCode = iso2_responseCodeType_FAILED_ChargingProfileInvalid; // EV charing profile
                    // time exceeds EVSE provided schedule
                    //  setting response code is commented because some EVs do not support schedules correctly
                    dlog(DLOG_LEVEL_WARNING,
                         "EV's charging profile is invalid (EV charging profile time exceeds provided schedule)!");
                } else {
                    /* Now we checked if the current EV interval fits within the EVSE interval, but it fails.
                     * Next step is to check the EVSE interval until we reached the last EVSE interval */
                    evse_idx++;
                }
            }
        }
    }
}

static void publish_DcEvStatus(struct v2g_context* ctx, const struct iso2_DC_EVStatusType& iso2_ev_status) {
    if ((ctx->ev_v2g_data.iso2_dc_ev_status.EVErrorCode != iso2_ev_status.EVErrorCode) ||
        (ctx->ev_v2g_data.iso2_dc_ev_status.EVReady != iso2_ev_status.EVReady) ||
        (ctx->ev_v2g_data.iso2_dc_ev_status.EVRESSSOC != iso2_ev_status.EVRESSSOC)) {
        ctx->ev_v2g_data.iso2_dc_ev_status.EVErrorCode = iso2_ev_status.EVErrorCode;
        ctx->ev_v2g_data.iso2_dc_ev_status.EVReady = iso2_ev_status.EVReady;
        ctx->ev_v2g_data.iso2_dc_ev_status.EVRESSSOC = iso2_ev_status.EVRESSSOC;

        types::iso15118::DcEvStatus ev_status;
        ev_status.dc_ev_error_code = static_cast<types::iso15118::DcEvErrorCode>(iso2_ev_status.EVErrorCode);
        ev_status.dc_ev_ready = iso2_ev_status.EVReady;
        ev_status.dc_ev_ress_soc = static_cast<float>(iso2_ev_status.EVRESSSOC);
        ctx->p_charger->publish_dc_ev_status(ev_status);
    }
}

static auto get_emergency_status_code(const struct v2g_context* ctx, uint8_t phase_type) {
    if (ctx->intl_emergency_shutdown)
        return iso2_DC_EVSEStatusCodeType_EVSE_EmergencyShutdown;
    else
        return static_cast<iso2_DC_EVSEStatusCodeType>(ctx->evse_v2g_data.evse_status_code[phase_type]);
}

//=============================================
//             Publishing request msg
//=============================================

/*!
 * \brief publish_iso_service_discovery_req This function publishes the iso_service_discovery_req message to the MQTT
 * interface. \param iso2_ServiceDiscoveryReqType is the request message.
 */
static void
publish_iso_service_discovery_req(struct iso2_ServiceDiscoveryReqType const* const v2g_service_discovery_req) {
    // V2G values that can be published: ServiceCategory, ServiceScope
}

/*!
 * \brief publish_iso_service_detail_req This function publishes the iso_service_detail_req message to the MQTT
 * interface. \param v2g_service_detail_req is the request message.
 */
static void publish_iso_service_detail_req(struct iso2_ServiceDetailReqType const* const v2g_service_detail_req) {
    // V2G values that can be published: ServiceID
}

/*!
 * \brief publish_iso_payment_service_selection_req This function publishes the iso_payment_service_selection_req
 * message to the MQTT interface.
 * \param v2g_payment_service_selection_req is the request message.
 */
static void publish_iso_payment_service_selection_req(
    struct iso2_PaymentServiceSelectionReqType const* const v2g_payment_service_selection_req) {
    // V2G values that can be published: selected_payment_option, SelectedServiceList
}

/*!
 * \brief publish_iso_authorization_req This function publishes the publish_iso_authorization_req message to the MQTT
 * interface. \param v2g_authorization_req is the request message.
 */
static void publish_iso_authorization_req(struct iso2_AuthorizationReqType const* const v2g_authorization_req) {
    // V2G values that can be published: Id, Id_isUsed, GenChallenge, GenChallenge_isUsed
}

/*!
 * \brief publish_iso_charge_parameter_discovery_req This function publishes the charge_parameter_discovery_req message
 * to the MQTT interface. \param ctx is the V2G context. \param v2g_charge_parameter_discovery_req is the request
 * message.
 */
static void publish_iso_charge_parameter_discovery_req(
    struct v2g_context* ctx,
    struct iso2_ChargeParameterDiscoveryReqType const* const v2g_charge_parameter_discovery_req) {
    // Charging needs for OCPP
    types::iso15118::ChargingNeeds charging_needs;

    // V2G values that can be published: DC_EVChargeParameter, MaxEntriesSAScheduleTuple
    types::iso15118::EnergyTransferMode transfer_mode{};

    switch (v2g_charge_parameter_discovery_req->RequestedEnergyTransferMode) {
    case iso2_EnergyTransferModeType_AC_single_phase_core:
        transfer_mode = types::iso15118::EnergyTransferMode::AC_single_phase_core;
        break;
    case iso2_EnergyTransferModeType_AC_three_phase_core:
        transfer_mode = types::iso15118::EnergyTransferMode::AC_three_phase_core;
        break;
    case iso2_EnergyTransferModeType_DC_core:
        transfer_mode = types::iso15118::EnergyTransferMode::DC_core;
        break;
    case iso2_EnergyTransferModeType_DC_extended:
        transfer_mode = types::iso15118::EnergyTransferMode::DC_extended;
        break;
    case iso2_EnergyTransferModeType_DC_combo_core:
        transfer_mode = types::iso15118::EnergyTransferMode::DC_combo_core;
        break;
    case iso2_EnergyTransferModeType_DC_unique:
        transfer_mode = types::iso15118::EnergyTransferMode::DC_unique;
        break;
    default:
        dlog(DLOG_LEVEL_WARNING, "Unable to convert RequestedEnergyTransferMode to EnergyTransferMode: %d",
             v2g_charge_parameter_discovery_req->RequestedEnergyTransferMode);
    }

    charging_needs.requested_energy_transfer = transfer_mode;

    ctx->p_charger->publish_requested_energy_transfer_mode(transfer_mode);
    if (v2g_charge_parameter_discovery_req->AC_EVChargeParameter_isUsed == (unsigned int)1) {
        if (v2g_charge_parameter_discovery_req->AC_EVChargeParameter.DepartureTime_isUsed == (unsigned int)1) {
            const char* format = "%Y-%m-%dT%H:%M:%SZ";
            char buffer[100];
            std::time_t time_now_in_sec = time(NULL);
            std::time_t departure_time =
                time_now_in_sec + v2g_charge_parameter_discovery_req->AC_EVChargeParameter.DepartureTime;
            std::strftime(buffer, sizeof(buffer), format, std::gmtime(&departure_time));
            ctx->p_charger->publish_departure_time(buffer);
        }

        // TODO(ioan): calc physical once
        float ac_eamount =
            calc_physical_value(v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EAmount.Value,
                                v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EAmount.Multiplier);
        float ac_ev_max_voltage =
            calc_physical_value(v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMaxVoltage.Value,
                                v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMaxVoltage.Multiplier);
        float ac_ev_max_current =
            calc_physical_value(v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMaxCurrent.Value,
                                v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMaxCurrent.Multiplier);
        float ac_ev_min_current =
            calc_physical_value(v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMinCurrent.Value,
                                v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMinCurrent.Multiplier);

        ctx->p_charger->publish_ac_eamount(ac_eamount);
        ctx->p_charger->publish_ac_ev_max_voltage(ac_ev_max_voltage);
        ctx->p_charger->publish_ac_ev_max_current(ac_ev_max_current);
        ctx->p_charger->publish_ac_ev_min_current(ac_ev_min_current);

        auto& ac_charging_parameters = charging_needs.ac_charging_parameters.emplace();

        // We do not require to calc a min/max here
        ac_charging_parameters.energy_amount = ac_eamount;
        ac_charging_parameters.ev_max_voltage = ac_ev_max_voltage;
        ac_charging_parameters.ev_max_current = ac_ev_max_current;
        ac_charging_parameters.ev_min_current = ac_ev_min_current;

    } else if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter_isUsed == (unsigned int)1) {
        if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.DepartureTime_isUsed == (unsigned int)1) {
            const char* format = "%Y-%m-%dT%H:%M:%SZ";
            char buffer[100];
            std::time_t time_now_in_sec = time(NULL);
            std::time_t departure_time =
                time_now_in_sec + v2g_charge_parameter_discovery_req->DC_EVChargeParameter.DepartureTime;
            std::strftime(buffer, sizeof(buffer), format, std::gmtime(&departure_time));
            ctx->p_charger->publish_departure_time(buffer);
        }

        auto& dc_charging_parameters = charging_needs.dc_charging_parameters.emplace();

        if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity_isUsed == (unsigned int)1) {
            ctx->p_charger->publish_dc_ev_energy_capacity(calc_physical_value(
                v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity.Value,
                v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity.Multiplier));

            dc_charging_parameters.ev_energy_capacity = calc_physical_value(
                v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity.Value,
                v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity.Multiplier);
        }
        if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest_isUsed == (unsigned int)1) {
            ctx->p_charger->publish_dc_ev_energy_request(calc_physical_value(
                v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest.Value,
                v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest.Multiplier));

            // OCPP2.1 Spec: Relates to: ISO 15118-2: DC_EVChargeParameterType: EVEnergyRequest
            dc_charging_parameters.energy_amount = calc_physical_value(
                v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest.Value,
                v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest.Multiplier);
        }
        if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.FullSOC_isUsed == (unsigned int)1) {
            ctx->p_charger->publish_dc_full_soc(v2g_charge_parameter_discovery_req->DC_EVChargeParameter.FullSOC);
            dc_charging_parameters.full_soc = v2g_charge_parameter_discovery_req->DC_EVChargeParameter.FullSOC;
        }
        if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.BulkSOC_isUsed == (unsigned int)1) {
            ctx->p_charger->publish_dc_bulk_soc(v2g_charge_parameter_discovery_req->DC_EVChargeParameter.BulkSOC);
            dc_charging_parameters.bulk_soc = v2g_charge_parameter_discovery_req->DC_EVChargeParameter.BulkSOC;
        }
        float evMaximumCurrentLimit = calc_physical_value(
            v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumCurrentLimit.Value,
            v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumCurrentLimit.Multiplier);
        float evMaximumPowerLimit = calc_physical_value(
            v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumPowerLimit.Value,
            v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumPowerLimit.Multiplier);
        float evMaximumVoltageLimit = calc_physical_value(
            v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumVoltageLimit.Value,
            v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumVoltageLimit.Multiplier);
        publish_dc_ev_maximum_limits(
            ctx, evMaximumCurrentLimit, (unsigned int)1, evMaximumPowerLimit,
            v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumPowerLimit_isUsed, evMaximumVoltageLimit,
            (unsigned int)1);
        publish_DcEvStatus(ctx, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.DC_EVStatus);

        dc_charging_parameters.ev_max_current = evMaximumCurrentLimit;
        dc_charging_parameters.ev_max_power = evMaximumPowerLimit;
        dc_charging_parameters.ev_max_voltage = evMaximumVoltageLimit;

        dc_charging_parameters.state_of_charge =
            v2g_charge_parameter_discovery_req->DC_EVChargeParameter.DC_EVStatus.EVRESSSOC;
    }

    // Publish charging needs
    ctx->p_extensions->publish_charging_needs(charging_needs);
}

/*!
 * \brief publish_iso_pre_charge_req This function publishes the iso_pre_charge_req message to the MQTT interface.
 * \param ctx is the V2G context.
 * \param v2g_precharge_req is the request message.
 */
static void publish_iso_pre_charge_req(struct v2g_context* ctx,
                                       struct iso2_PreChargeReqType const* const v2g_precharge_req) {
    publish_dc_ev_target_voltage_current(
        ctx,
        calc_physical_value(v2g_precharge_req->EVTargetVoltage.Value, v2g_precharge_req->EVTargetVoltage.Multiplier),
        calc_physical_value(v2g_precharge_req->EVTargetCurrent.Value, v2g_precharge_req->EVTargetCurrent.Multiplier));
    publish_DcEvStatus(ctx, v2g_precharge_req->DC_EVStatus);
}

/*!
 * \brief publish_iso_power_delivery_req This function publishes the iso_power_delivery_req message to the MQTT
 * interface. \param ctx is the V2G context. \param v2g_power_delivery_req is the request message.
 */
static void publish_iso_power_delivery_req(struct v2g_context* ctx,
                                           struct iso2_PowerDeliveryReqType const* const v2g_power_delivery_req) {
    // V2G values that can be published: ChargeProgress, SAScheduleTupleID
    if (v2g_power_delivery_req->DC_EVPowerDeliveryParameter_isUsed == (unsigned int)1) {
        ctx->p_charger->publish_dc_charging_complete(
            v2g_power_delivery_req->DC_EVPowerDeliveryParameter.ChargingComplete);
        if (v2g_power_delivery_req->DC_EVPowerDeliveryParameter.BulkChargingComplete_isUsed == (unsigned int)1) {
            ctx->p_charger->publish_dc_bulk_charging_complete(
                v2g_power_delivery_req->DC_EVPowerDeliveryParameter.BulkChargingComplete);
        }
        publish_DcEvStatus(ctx, v2g_power_delivery_req->DC_EVPowerDeliveryParameter.DC_EVStatus);
    }
}

/*!
 * \brief publish_iso_current_demand_req This function publishes the iso_current_demand_req message to the MQTT
 * interface. \param ctx is the V2G context \param v2g_current_demand_req is the request message.
 */
static void publish_iso_current_demand_req(struct v2g_context* ctx,
                                           struct iso2_CurrentDemandReqType const* const v2g_current_demand_req) {
    if ((v2g_current_demand_req->BulkChargingComplete_isUsed == (unsigned int)1) &&
        (ctx->ev_v2g_data.bulk_charging_complete != v2g_current_demand_req->BulkChargingComplete)) {
        ctx->p_charger->publish_dc_bulk_charging_complete(v2g_current_demand_req->BulkChargingComplete);
        ctx->ev_v2g_data.bulk_charging_complete = v2g_current_demand_req->BulkChargingComplete;
    }
    if (ctx->ev_v2g_data.charging_complete != v2g_current_demand_req->ChargingComplete) {
        ctx->p_charger->publish_dc_charging_complete(v2g_current_demand_req->ChargingComplete);
        ctx->ev_v2g_data.charging_complete = v2g_current_demand_req->ChargingComplete;
    }

    publish_DcEvStatus(ctx, v2g_current_demand_req->DC_EVStatus);

    publish_dc_ev_target_voltage_current(ctx,
                                         calc_physical_value(v2g_current_demand_req->EVTargetVoltage.Value,
                                                             v2g_current_demand_req->EVTargetVoltage.Multiplier),
                                         calc_physical_value(v2g_current_demand_req->EVTargetCurrent.Value,
                                                             v2g_current_demand_req->EVTargetCurrent.Multiplier));

    float evMaximumCurrentLimit = calc_physical_value(v2g_current_demand_req->EVMaximumCurrentLimit.Value,
                                                      v2g_current_demand_req->EVMaximumCurrentLimit.Multiplier);
    float evMaximumPowerLimit = calc_physical_value(v2g_current_demand_req->EVMaximumPowerLimit.Value,
                                                    v2g_current_demand_req->EVMaximumPowerLimit.Multiplier);
    float evMaximumVoltageLimit = calc_physical_value(v2g_current_demand_req->EVMaximumVoltageLimit.Value,
                                                      v2g_current_demand_req->EVMaximumVoltageLimit.Multiplier);

    publish_dc_ev_maximum_limits(ctx, evMaximumCurrentLimit, v2g_current_demand_req->EVMaximumCurrentLimit_isUsed,
                                 evMaximumPowerLimit, v2g_current_demand_req->EVMaximumPowerLimit_isUsed,
                                 evMaximumVoltageLimit, v2g_current_demand_req->EVMaximumVoltageLimit_isUsed);

    float v2g_dc_ev_remaining_time_to_full_soc =
        calc_physical_value(v2g_current_demand_req->RemainingTimeToFullSoC.Value,
                            v2g_current_demand_req->RemainingTimeToFullSoC.Multiplier);
    float v2g_dc_ev_remaining_time_to_bulk_soc =
        calc_physical_value(v2g_current_demand_req->RemainingTimeToBulkSoC.Value,
                            v2g_current_demand_req->RemainingTimeToBulkSoC.Multiplier);
    publish_dc_ev_remaining_time(
        ctx, v2g_dc_ev_remaining_time_to_full_soc, v2g_current_demand_req->RemainingTimeToFullSoC_isUsed,
        v2g_dc_ev_remaining_time_to_bulk_soc, v2g_current_demand_req->RemainingTimeToBulkSoC_isUsed);
}
/*!
 * \brief publish_iso_metering_receipt_req This function publishes the iso_metering_receipt_req message to the MQTT
 * interface. \param v2g_metering_receipt_req is the request message.
 */
static void publish_iso_metering_receipt_req(struct iso2_MeteringReceiptReqType const* const v2g_metering_receipt_req) {
    // TODO: publish PnC only
}

/*!
 * \brief publish_iso_welding_detection_req This function publishes the iso_welding_detection_req message to the MQTT
 * interface. \param p_charger to publish MQTT topics. \param v2g_welding_detection_req is the request message.
 */
static void
publish_iso_welding_detection_req(struct v2g_context* ctx,
                                  struct iso2_WeldingDetectionReqType const* const v2g_welding_detection_req) {
    // TODO: V2G values that can be published: EVErrorCode, EVReady, EVRESSSOC
    publish_DcEvStatus(ctx, v2g_welding_detection_req->DC_EVStatus);
}

/*!
 * \brief publish_iso_certificate_installation_exi_req This function publishes the iso_certificate_update_req message to
 * the MQTT interface. \param AExiBuffer is the exi msg where the V2G EXI msg is stored. \param AExiBufferSize is the
 * size of the V2G msg. \return Returns \c true if it was successful, otherwise \c false.
 */
static bool publish_iso_certificate_installation_exi_req(struct v2g_context* ctx, uint8_t* AExiBuffer,
                                                         size_t AExiBufferSize) {
    // PnC only

    bool rv = true;
    types::iso15118::RequestExiStreamSchema certificate_request;

    certificate_request.exi_request = openssl::base64_encode(AExiBuffer, AExiBufferSize);
    if (certificate_request.exi_request.size() > MQTT_MAX_PAYLOAD_SIZE) {
        dlog(DLOG_LEVEL_ERROR, "Mqtt payload size exceeded!");
        return false;
    }
    if (certificate_request.exi_request.size() == 0) {
        dlog(DLOG_LEVEL_ERROR, "Unable to encode contract leaf certificate");
        return false;
    }

    certificate_request.iso15118_schema_version = ISO_15118_2013_MSG_DEF;
    certificate_request.certificate_action = types::iso15118::CertificateActionEnum::Install;
    ctx->p_extensions->publish_iso15118_certificate_request(certificate_request);

    return rv;
}

//=============================================
//             Request Handling
//=============================================

/*!
 * \brief handle_iso_session_setup This function handles the iso_session_setup msg pair. It analyzes the request msg and
 * fills the response msg. \param conn holds the structure with the V2G msg pair. \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_session_setup(struct v2g_connection* conn) {
    struct iso2_SessionSetupReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.SessionSetupReq;
    struct iso2_SessionSetupResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.SessionSetupRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;

    /* format EVCC ID */
    const auto mac_addr = to_mac_address_str(&req->EVCCID.bytes[0], req->EVCCID.bytesLen);

    conn->ctx->p_charger->publish_evcc_id(mac_addr); // publish EVCC ID

    dlog(DLOG_LEVEL_INFO, "SessionSetupReq.EVCCID: %s",
         (mac_addr.empty()) ? "(zero length provided)" : mac_addr.c_str());

    /* [V2G2-756]: If the SECC receives a SessionSetupReq including a SessionID value which is not
     * equal to zero (0) and not equal to the SessionID value stored from the preceding V2G
     * Communication Session, it shall send a SessionID value in the SessionSetupRes message that is
     * unequal to "0" and unequal to the SessionID value stored from the preceding V2G Communication
     * Session and indicate the new V2G Communication Session with the ResponseCode set to
     * "OK_NewSessionEstablished"
     */

    // TODO: handle resuming sessions [V2G2-463]

    /* Now fill the evse response message */
    res->ResponseCode = iso2_responseCodeType_OK_NewSessionEstablished;

    /* Check and init session id */
    /* If no session id is configured, generate one */
    srand((unsigned int)time(NULL));
    if (conn->ctx->evse_v2g_data.session_id == (uint64_t)0 ||
        conn->ctx->evse_v2g_data.session_id != conn->ctx->ev_v2g_data.received_session_id) {
        generate_random_data(&conn->ctx->evse_v2g_data.session_id, 8);
        dlog(
            DLOG_LEVEL_INFO,
            "No session_id found or not equal to the id from the preceding v2g session. Generating random session id.");
        dlog(DLOG_LEVEL_INFO, "Created new session with id 0x%016" PRIx64,
             be64toh(conn->ctx->evse_v2g_data.session_id));
    } else {
        dlog(DLOG_LEVEL_INFO, "Found Session_id from the old session: 0x%016" PRIx64,
             be64toh(conn->ctx->evse_v2g_data.session_id));
        res->ResponseCode = iso2_responseCodeType_OK_OldSessionJoined;
    }

    /* TODO: publish EVCCID to MQTT */

    // An overflow check is already done in ISO15118_chargerImpl.cpp
    res->EVSEID.charactersLen = conn->ctx->evse_v2g_data.evse_id.bytesLen;
    memcpy(res->EVSEID.characters, conn->ctx->evse_v2g_data.evse_id.bytes, conn->ctx->evse_v2g_data.evse_id.bytesLen);

    res->EVSETimeStamp_isUsed = conn->ctx->evse_v2g_data.date_time_now_is_used;
    res->EVSETimeStamp = time(NULL);

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_SERVICEDISCOVERY; // [V2G-543]

    return next_event;
}

/*!
 * \brief handle_iso_service_discovery This function handles the iso service discovery msg pair. It analyzes the request
 * msg and fills the response msg. The request and response msg based on the open V2G structures. This structures must
 * be provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_service_discovery(struct v2g_connection* conn) {
    struct iso2_ServiceDiscoveryReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.ServiceDiscoveryReq;
    struct iso2_ServiceDiscoveryResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.ServiceDiscoveryRes;
    enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;

    /* At first, publish the received ev request message to the MQTT interface */
    publish_iso_service_discovery_req(req);

    /* build up response */
    res->ResponseCode = iso2_responseCodeType_OK;

    // Checking of the charge service id
    if (conn->ctx->evse_v2g_data.charge_service.ServiceID != V2G_SERVICE_ID_CHARGING) {
        dlog(DLOG_LEVEL_WARNING,
             "Selected ServiceID is not ISO15118 conform. Correcting value to '1' (Charge service id)");
        conn->ctx->evse_v2g_data.charge_service.ServiceID = V2G_SERVICE_ID_CHARGING;
    }
    // Checking of the service category
    if (conn->ctx->evse_v2g_data.charge_service.ServiceCategory != iso2_serviceCategoryType_EVCharging) {
        dlog(DLOG_LEVEL_WARNING,
             "Selected ServiceCategory is not ISO15118 conform. Correcting value to '0' (EVCharging)");
        conn->ctx->evse_v2g_data.charge_service.ServiceCategory = iso2_serviceCategoryType_EVCharging;
    }

    res->ChargeService = conn->ctx->evse_v2g_data.charge_service;

    // Checking of the payment options
    const auto pnc_enabled =
        std::find(conn->ctx->evse_v2g_data.payment_option_list.begin(),
                  conn->ctx->evse_v2g_data.payment_option_list.end(),
                  iso2_paymentOptionType_Contract) != conn->ctx->evse_v2g_data.payment_option_list.end();
    if (not conn->is_tls_connection and pnc_enabled) {
        conn->ctx->evse_v2g_data.payment_option_list = {iso2_paymentOptionType_ExternalPayment};
        dlog(DLOG_LEVEL_WARNING,
             "PnC is not allowed without TLS-communication. Correcting value to '1' (ExternalPayment)");
    }

    const auto payment_option_length = std::min(conn->ctx->evse_v2g_data.payment_option_list.size(),
                                                static_cast<size_t>(iso2_paymentOptionType_2_ARRAY_SIZE));

    memcpy(res->PaymentOptionList.PaymentOption.array, conn->ctx->evse_v2g_data.payment_option_list.data(),
           payment_option_length * sizeof(iso2_paymentOptionType));
    res->PaymentOptionList.PaymentOption.arrayLen = payment_option_length;

    // ensure a "clean" service list
    res->ServiceList.Service.arrayLen = 0;

    // consider all services by default, but...
    for (const auto& service : conn->ctx->evse_v2g_data.evse_service_list) {
        // ...skip the service if the (possibly set) scope does not match a (possibly) requested one
        if (req->ServiceScope_isUsed and service.ServiceScope_isUsed and
            strncmp(req->ServiceScope.characters, service.ServiceScope.characters,
                    sizeof(req->ServiceScope.characters)) != 0)
            continue;
        // ...skip if a possibly requested service category does not match the category of the service
        if (req->ServiceCategory_isUsed and req->ServiceCategory != service.ServiceCategory)
            continue;
        // copy otherwise
        res->ServiceList.Service.array[res->ServiceList.Service.arrayLen] = service;
        // update the new size
        res->ServiceList.Service.arrayLen++;
    }

    // tell the EXI encoder that we want to use the filled list
    res->ServiceList_isUsed = res->ServiceList.Service.arrayLen ? 1 : 0;

    /* Check the current response code and check if no external error has occurred */
    nextEvent = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_SVCDETAIL_PAYMENTSVCSEL; // [V2G-545]

    return nextEvent;
}

/*!
 * \brief handle_iso_service_detail This function handles the iso_service_detail msg pair. It analyzes the request msg
 * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be
 * provided within the \c conn structure. (Optional VAS)
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_service_detail(struct v2g_connection* conn) {
    struct iso2_ServiceDetailReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.ServiceDetailReq;
    struct iso2_ServiceDetailResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.ServiceDetailRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;

    /* At first, publish the received ev request message to the MQTT interface */
    publish_iso_service_detail_req(req);

    res->ResponseCode = iso2_responseCodeType_OK;

    /* ServiceID reported back always matches the requested one */
    res->ServiceID = req->ServiceID;

    bool service_id_found = false;

    for (uint8_t idx = 0; idx < conn->ctx->evse_v2g_data.evse_service_list.size(); idx++) {

        if (req->ServiceID == conn->ctx->evse_v2g_data.evse_service_list[idx].ServiceID) {
            service_id_found = true;

            init_iso2_ServiceParameterListType(&res->ServiceParameterList);
            /* Fill parameter list of the requested service id [V2G2-549] */
            if (conn->ctx->evse_v2g_data.service_parameter_list.find(req->ServiceID) !=
                conn->ctx->evse_v2g_data.service_parameter_list.end()) {
                res->ServiceParameterList.ParameterSet =
                    conn->ctx->evse_v2g_data.service_parameter_list.at(req->ServiceID).ParameterSet;
                res->ServiceParameterList_isUsed = true;
                break;
            }

            for (size_t i = 0; i < conn->ctx->supported_vas_services_per_provider.size(); i++) {
                const auto& provider_services = conn->ctx->supported_vas_services_per_provider.at(i);
                if (std::find(provider_services.begin(), provider_services.end(), req->ServiceID) !=
                    provider_services.end()) {
                    const auto vas_parameters = conn->ctx->r_vas.at(i)->call_get_service_parameters(req->ServiceID);

                    if (vas_parameters.empty()) {
                        break;
                    }

                    iso2_ServiceParameterListType vas_parameter_list{};
                    init_iso2_ServiceParameterListType(&vas_parameter_list);
                    size_t maxParameterSetArrayLen = ARRAY_SIZE(vas_parameter_list.ParameterSet.array);
                    vas_parameter_list.ParameterSet.arrayLen = std::min(maxParameterSetArrayLen, vas_parameters.size());

                    for (size_t j = 0; j < vas_parameters.size() and j < maxParameterSetArrayLen; j++) {
                        const auto& vas_parameter = vas_parameters.at(j);

                        vas_parameter_list.ParameterSet.array[j].ParameterSetID =
                            static_cast<int16_t>(vas_parameter.set_id);

                        size_t t{0};
                        for (const auto& parameter : vas_parameter.parameters) {
                            // quit loop when the maximum count of elements our encoder can handle is reached
                            if (t == ARRAY_SIZE(vas_parameter_list.ParameterSet.array[j].Parameter.array))
                                break;
                            auto& out_parameter = vas_parameter_list.ParameterSet.array[j].Parameter.array[t++];

                            init_iso2_ParameterType(&out_parameter);

                            strncpy_to_v2g(out_parameter.Name.characters, sizeof(out_parameter.Name.characters),
                                           &out_parameter.Name.charactersLen, parameter.name);

                            if (parameter.value.int_value.has_value()) {
                                out_parameter.intValue = parameter.value.int_value.value();
                                out_parameter.intValue_isUsed = true;
                            } else if (parameter.value.finite_string.has_value()) {
                                const auto& temp = parameter.value.finite_string.value();
                                if (temp.length() > sizeof(out_parameter.stringValue.characters)) {
                                    EVLOG_warning << fmt::format("The value of parameter string '{}' is too long and "
                                                                 "was truncated from '{}' to '{:.{}}'",
                                                                 parameter.name, temp, temp,
                                                                 sizeof(out_parameter.stringValue.characters));
                                }
                                strncpy_to_v2g(out_parameter.stringValue.characters,
                                               sizeof(out_parameter.stringValue.characters),
                                               &out_parameter.stringValue.charactersLen, temp);
                                out_parameter.stringValue_isUsed = true;
                            } else if (parameter.value.bool_value.has_value()) {
                                out_parameter.boolValue = static_cast<int>(parameter.value.bool_value.value());
                                out_parameter.boolValue_isUsed = true;
                            } else if (parameter.value.byte_value.has_value()) {
                                out_parameter.byteValue = static_cast<int8_t>(parameter.value.byte_value.value());
                                out_parameter.byteValue_isUsed = true;
                            } else if (parameter.value.short_value.has_value()) {
                                out_parameter.shortValue = static_cast<int16_t>(parameter.value.short_value.value());
                                out_parameter.shortValue_isUsed = true;
                            } else if (parameter.value.rational_number.has_value()) {
                                // TODO(SL): Specify the correct unit symbol
                                populate_physical_value_float(&out_parameter.physicalValue,
                                                              parameter.value.rational_number.value(), 1,
                                                              iso2_unitSymbolType::iso2_unitSymbolType_W);
                                out_parameter.physicalValue_isUsed = true;
                            }
                        }

                        vas_parameter_list.ParameterSet.array[j].Parameter.arrayLen = t;
                    }

                    res->ServiceParameterList.ParameterSet = vas_parameter_list.ParameterSet;
                    res->ServiceParameterList_isUsed = true;
                    break;
                }
            }
        }
    }
    service_id_found = (req->ServiceID == V2G_SERVICE_ID_CHARGING) ? true : service_id_found;

    if (false == service_id_found) {
        res->ResponseCode = iso2_responseCodeType_FAILED_ServiceIDInvalid; // [V2G2-464]
    }

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_SVCDETAIL_PAYMENTSVCSEL; // [V2G-DC-548]

    return next_event;
}

/*!
 * \brief handle_iso_payment_service_selection This function handles the iso_payment_service_selection msg pair. It
 * analyzes the request msg and fills the response msg. The request and response msg based on the open V2G structures.
 * This structures must be provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_payment_service_selection(struct v2g_connection* conn) {
    struct iso2_PaymentServiceSelectionReqType* req =
        &conn->exi_in.iso2EXIDocument->V2G_Message.Body.PaymentServiceSelectionReq;
    struct iso2_PaymentServiceSelectionResType* res =
        &conn->exi_out.iso2EXIDocument->V2G_Message.Body.PaymentServiceSelectionRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;
    uint8_t idx = 0;
    bool list_element_found = false;

    /* At first, publish the received ev request message to the customer mqtt interface */
    publish_iso_payment_service_selection_req(req);

    res->ResponseCode = iso2_responseCodeType_OK;

    /* check whether the selected payment option was announced at all;
     * this also covers the case that the peer sends any invalid/unknown payment option
     * in the message; if we are not happy -> bail out
     */
    for (idx = 0; idx < conn->ctx->evse_v2g_data.payment_option_list.size(); idx++) {
        if (conn->ctx->evse_v2g_data.payment_option_list.at(idx) == req->SelectedPaymentOption) {
            list_element_found = true;
            if (req->SelectedPaymentOption == iso2_paymentOptionType_ExternalPayment) {
                conn->ctx->p_charger->publish_selected_payment_option(types::iso15118::PaymentOption::ExternalPayment);
            } else if (req->SelectedPaymentOption == iso2_paymentOptionType_Contract) {
                conn->ctx->p_charger->publish_selected_payment_option(types::iso15118::PaymentOption::Contract);
            }
            break;
        }
    }
    res->ResponseCode = (list_element_found == true)
                            ? res->ResponseCode
                            : iso2_responseCodeType_FAILED_PaymentSelectionInvalid; // [V2G2-465]

    /* Check the selected services */
    bool charge_service_found = false;
    bool selected_services_found = true;

    std::map<size_t, std::vector<types::iso15118_vas::SelectedService>> services_by_provider;

    for (uint8_t req_idx = 0;
         (req_idx < req->SelectedServiceList.SelectedService.arrayLen) && (selected_services_found == true);
         req_idx++) {

        /* Check if it's a charging service */
        if (req->SelectedServiceList.SelectedService.array[req_idx].ServiceID == V2G_SERVICE_ID_CHARGING) {
            charge_service_found = true;
        }
        /* Otherwise check if the selected service is in the stored in the service list */
        else {
            bool entry_found = false;
            for (uint8_t ci_idx = 0;
                 (ci_idx < conn->ctx->evse_v2g_data.evse_service_list.size()) && (entry_found == false); ci_idx++) {

                const auto& selected_service = req->SelectedServiceList.SelectedService.array[req_idx];

                if (selected_service.ServiceID == conn->ctx->evse_v2g_data.evse_service_list[ci_idx].ServiceID) {
                    /* If it's stored, search for the next requested SelectedService entry */
                    dlog(DLOG_LEVEL_INFO, "Selected service id %i found",
                         conn->ctx->evse_v2g_data.evse_service_list[ci_idx].ServiceID);

                    if (conn->ctx->evse_v2g_data.evse_service_list[ci_idx].ServiceID == SAE_V2H) {
                        conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h = true;
                        conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2g = false;
                        conn->ctx->p_charger->publish_sae_bidi_mode_active(nullptr);
                    } else if (conn->ctx->evse_v2g_data.evse_service_list[ci_idx].ServiceID == SAE_V2G) {
                        conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h = false;
                        conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2g = true;
                        conn->ctx->p_charger->publish_sae_bidi_mode_active(nullptr);
                    }

                    for (size_t i = 0; i < conn->ctx->supported_vas_services_per_provider.size(); i++) {
                        const auto& provider_services = conn->ctx->supported_vas_services_per_provider.at(i);
                        if (std::find(provider_services.begin(), provider_services.end(), selected_service.ServiceID) !=
                            provider_services.end()) {

                            // TODO(SL): What to do if parameterset is not available?
                            types::iso15118_vas::SelectedService service{
                                selected_service.ServiceID,
                                selected_service.ParameterSetID_isUsed ? selected_service.ParameterSetID : 0};

                            services_by_provider[i].push_back(service);
                        }
                    }

                    entry_found = true;
                    break;
                }
            }
            if (entry_found == false) {
                /* If the requested SelectedService entry was not found, break up service list check */
                selected_services_found = false;
                break;
            }
        }
    }

    if (selected_services_found) {
        for (const auto& [provider_index, services] : services_by_provider) {
            conn->ctx->r_vas.at(provider_index)->call_selected_services(services);
        }
    }

    res->ResponseCode = (selected_services_found == false) ? iso2_responseCodeType_FAILED_ServiceSelectionInvalid
                                                           : res->ResponseCode; // [V2G2-467]
    res->ResponseCode = (charge_service_found == false) ? iso2_responseCodeType_FAILED_NoChargeServiceSelected
                                                        : res->ResponseCode; // [V2G2-804]

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    if (req->SelectedPaymentOption == iso2_paymentOptionType_Contract) {
        dlog(DLOG_LEVEL_INFO, "SelectedPaymentOption: Contract");
        conn->ctx->session.iso_selected_payment_option = iso2_paymentOptionType_Contract;
        /* Set next expected req msg */
        conn->ctx->state =
            (int)iso_dc_state_id::WAIT_FOR_PAYMENTDETAILS_CERTINST_CERTUPD; // [V2G-551] (iso specification describes
                                                                            // only the ac case... )
    } else {
        dlog(DLOG_LEVEL_INFO, "SelectedPaymentOption: ExternalPayment");
        conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH] =
            (uint8_t)iso2_EVSEProcessingType_Ongoing_WaitingForCustomerInteraction; // [V2G2-854]
        /* Set next expected req msg */
        conn->ctx->state = (int)
            iso_dc_state_id::WAIT_FOR_AUTHORIZATION; // [V2G-551] (iso specification describes only the ac case... )
        conn->ctx->session.auth_start_timeout = getmonotonictime();
    }

    return next_event;
}

/*!
 * \brief handle_iso_payment_details This function handles the iso_payment_details msg pair. It analyzes the request msg
 * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be
 * provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_payment_details(struct v2g_connection* conn) {
    struct iso2_PaymentDetailsReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.PaymentDetailsReq;
    struct iso2_PaymentDetailsResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.PaymentDetailsRes;
    enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
    int err;

    // === For the contract certificate, the certificate chain should be checked ===
    if (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract) {
        // Free old stuff if it exists
        free_connection_crypto_data(conn);

        // Parse contract leaf certificate

        certificate_ptr contract_crt{nullptr, nullptr};
        certificate_list chain{};

        if (req->ContractSignatureCertChain.Certificate.bytesLen != 0) {
            err = parse_contract_certificate(contract_crt, req->ContractSignatureCertChain.Certificate.bytes,
                                             req->ContractSignatureCertChain.Certificate.bytesLen);
        } else {
            dlog(DLOG_LEVEL_ERROR, "No certificate received!");
            res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError;
            goto error_out;
        }

        auto cert_emaid = getEmaidFromContractCert(contract_crt);
        std::string req_emaid{&req->eMAID.characters[0], req->eMAID.charactersLen};

        /* Filter '-' character */
        cert_emaid.erase(std::remove(cert_emaid.begin(), cert_emaid.end(), '-'), cert_emaid.end());
        req_emaid.erase(std::remove(req_emaid.begin(), req_emaid.end(), '-'), req_emaid.end());

        dlog(DLOG_LEVEL_TRACE, "emaid-v2g: %s emaid-cert: %s", req_emaid.c_str(), cert_emaid.c_str());

        if ((req_emaid.size() != cert_emaid.size()) ||
            (strncasecmp(req_emaid.c_str(), cert_emaid.c_str(), req_emaid.size()) != 0)) {
            dlog(DLOG_LEVEL_ERROR, "emaid of the contract certificate doesn't match with the received v2g-emaid");
            res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError;
            goto error_out;
        }

        if (err != 0) {
            memset(res, 0, sizeof(*res));
            res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError;
            goto error_out;
        }

        assert(conn->pubkey != nullptr);
        *conn->pubkey = certificate_public_key(contract_crt.get());
        err = (*conn->pubkey == nullptr) ? -1 : 0;

        if (err != 0) {
            memset(res, 0, sizeof(*res));
            res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError;
            goto error_out;
        }

        // Parse contract sub certificates
        if (req->ContractSignatureCertChain.SubCertificates_isUsed == 1) {
            for (int i = 0; i < req->ContractSignatureCertChain.SubCertificates.Certificate.arrayLen; i++) {
                err =
                    load_certificate(&chain, req->ContractSignatureCertChain.SubCertificates.Certificate.array[i].bytes,
                                     req->ContractSignatureCertChain.SubCertificates.Certificate.array[i].bytesLen);
                if (err != 0) {
                    res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError;
                    goto error_out;
                }
            }
        }

        // initialize contract cert chain to retrieve ocsp request data

        // Save the certificate chain in a variable in PEM format to publish it
        std::string contract_cert_chain_pem = chain_to_pem(contract_crt, &chain);

        std::optional<std::vector<types::iso15118::CertificateHashDataInfo>> iso15118_certificate_hash_data;

        /* Only if certificate chain verification should be done locally by the EVSE */
        if (conn->ctx->session.verify_contract_cert_chain == true) {
            std::string v2g_root_cert_path =
                conn->ctx->r_security->call_get_verify_file(types::evse_security::CaCertificateType::V2G);
            std::string mo_root_cert_path =
                conn->ctx->r_security->call_get_verify_file(types::evse_security::CaCertificateType::MO);

            crypto::verify_result_t vRes = verify_certificate(contract_crt, &chain, v2g_root_cert_path.c_str(),
                                                              mo_root_cert_path.c_str(), conn->ctx->debugMode);

            err = -1;
            bool forward_contract = false;
            switch (vRes) {
            case crypto::verify_result_t::Verified:
                err = 0;
                break;
            case crypto::verify_result_t::CertificateExpired:
                res->ResponseCode = iso2_responseCodeType_FAILED_CertificateExpired;
                break;
            case crypto::verify_result_t::CertificateRevoked:
                // forward to csms if central_contract_validation_allowed is true
                if (conn->ctx->evse_v2g_data.central_contract_validation_allowed) {
                    forward_contract = true;
                } else {
                    res->ResponseCode = iso2_responseCodeType_FAILED_CertificateRevoked;
                }
                break;
            case crypto::verify_result_t::NoCertificateAvailable:
                // forward to csms if central_contract_validation_allowed is true
                if (conn->ctx->evse_v2g_data.central_contract_validation_allowed) {
                    forward_contract = true;
                } else {
                    res->ResponseCode = iso2_responseCodeType_FAILED_NoCertificateAvailable;
                }
                break;
            case crypto::verify_result_t::CertificateNotAllowed:
                // forward to csms if central_contract_validation_allowed is true
                if (conn->ctx->evse_v2g_data.central_contract_validation_allowed) {
                    forward_contract = true;
                } else {
                    res->ResponseCode = iso2_responseCodeType_FAILED_CertificateNotAllowedAtThisEVSE;
                }
                break;
            case crypto::verify_result_t::CertChainError:
            default:
                res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError;
                break;
            }

            if (err == -1) {
                dlog(DLOG_LEVEL_ERROR, "Validation of the contract certificate failed!");
                if (!forward_contract) {
                    dlog(DLOG_LEVEL_ERROR, "Central contract validation is not allowed.");
                    // EVSETimeStamp and GenChallenge are mandatory, GenChallenge has fixed size
                    res->EVSETimeStamp = time(NULL);
                    memset(res->GenChallenge.bytes, 0, GEN_CHALLENGE_SIZE);
                    res->GenChallenge.bytesLen = GEN_CHALLENGE_SIZE;
                    goto error_out;
                } else {
                    dlog(DLOG_LEVEL_INFO, "Central contract validation is allowed: Forwarding contract");
                }
            } else {
                dlog(DLOG_LEVEL_INFO, "Validation of the contract certificate was successful!");

                // contract chain ocsp data can only be retrieved if the MO root is present and the chain could be
                // verified
                const auto ocsp_response =
                    conn->ctx->r_security->call_get_mo_ocsp_request_data(contract_cert_chain_pem);
                iso15118_certificate_hash_data = convert_to_certificate_hash_data_info_vector(ocsp_response);
            }
        }

        generate_random_data(&conn->ctx->session.gen_challenge, GEN_CHALLENGE_SIZE);
        memcpy(res->GenChallenge.bytes, conn->ctx->session.gen_challenge, GEN_CHALLENGE_SIZE);
        res->GenChallenge.bytesLen = GEN_CHALLENGE_SIZE;

        // Publish the provided signature certificate chain and eMAID from EVCC
        // to receive PnC authorization
        types::authorization::ProvidedIdToken ProvidedIdToken;
        ProvidedIdToken.id_token = {std::string(cert_emaid), types::authorization::IdTokenType::eMAID};
        ProvidedIdToken.authorization_type = types::authorization::AuthorizationType::PlugAndCharge;
        ProvidedIdToken.iso15118CertificateHashData = iso15118_certificate_hash_data;
        ProvidedIdToken.certificate = contract_cert_chain_pem;
        conn->ctx->session.provided_id_token.emplace(ProvidedIdToken);

    } else {
        res->ResponseCode = iso2_responseCodeType_FAILED;
        goto error_out;
    }
    res->EVSETimeStamp = time(NULL);
    res->ResponseCode = iso2_responseCodeType_OK;

error_out:

    /* Check the current response code and check if no external error has occurred */
    nextEvent = iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_AUTHORIZATION; // [V2G-560]
    conn->ctx->session.auth_start_timeout = getmonotonictime();

    return nextEvent;
}

/*!
 * \brief handle_iso_authorization This function handles the iso_authorization msg pair. It analyzes the request msg and
 * fills the response msg. The request and response msg based on the open v2g structures. This structures must be
 * provided within the \c conn structure.
 * \param conn holds the structure with the v2g msg pair.
 * \return Returns the next v2g-event.
 */
static enum v2g_event handle_iso_authorization(struct v2g_connection* conn) {
    struct iso2_AuthorizationReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.AuthorizationReq;
    struct iso2_AuthorizationResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.AuthorizationRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;
    bool is_payment_option_contract = conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract;

    /* At first, publish the received ev request message to the customer mqtt interface */
    publish_iso_authorization_req(req);

    res->ResponseCode = iso2_responseCodeType_OK;

    if (conn->ctx->last_v2g_msg != V2G_AUTHORIZATION_MSG &&
        (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract)) { /* [V2G2-684] */
        if (req->GenChallenge_isUsed == 0 ||
            req->GenChallenge.bytesLen != 16 // [V2G2-697]  The GenChallenge field shall be exactly 128 bits long.
            || memcmp(req->GenChallenge.bytes, conn->ctx->session.gen_challenge, 16) != 0) {
            dlog(DLOG_LEVEL_ERROR, "Challenge invalid or not present");
            res->ResponseCode = iso2_responseCodeType_FAILED_ChallengeInvalid; // [V2G2-475]
            goto error_out;
        }
        if (conn->exi_in.iso2EXIDocument->V2G_Message.Header.Signature_isUsed == 0) {
            dlog(DLOG_LEVEL_ERROR, "Missing signature (Signature_isUsed == 0)");
            res->ResponseCode = iso2_responseCodeType_FAILED_SignatureError;
            goto error_out;
        }

        /* Validation of the received signature */
        struct iso2_exiFragment iso2_fragment;
        init_iso2_exiFragment(&iso2_fragment);

        iso2_fragment.AuthorizationReq_isUsed = 1u;
        memcpy(&iso2_fragment.AuthorizationReq, req, sizeof(*req));

        assert(conn->pubkey != nullptr);
        const bool bSigRes = check_iso2_signature(&conn->exi_in.iso2EXIDocument->V2G_Message.Header.Signature,
                                                  conn->pubkey->get(), &iso2_fragment);

        if (!bSigRes) {
            res->ResponseCode = iso2_responseCodeType_FAILED_SignatureError;
            goto error_out;
        }
        if (conn->ctx->session.provided_id_token.has_value()) {
            conn->ctx->p_charger->publish_require_auth_pnc(conn->ctx->session.provided_id_token.value());
            conn->ctx->session.provided_id_token.reset();
        } else {
            // this should never happen, since the contract certificate is set in handle_iso_payment_details in case
            // contract is selected
            dlog(DLOG_LEVEL_ERROR, "No contract certificate could be retrieved!");
            res->ResponseCode = iso2_responseCodeType_FAILED;
            goto error_out;
        }
    }
    res->EVSEProcessing = (iso2_EVSEProcessingType)conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH];

    if (conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH] != iso2_EVSEProcessingType_Finished) {
        if (((is_payment_option_contract == false) && (conn->ctx->session.auth_timeout_eim == 0)) ||
            ((is_payment_option_contract == true) && (conn->ctx->session.auth_timeout_pnc == 0))) {
            dlog(DLOG_LEVEL_DEBUG, "Waiting for authorization forever!");
        } else if ((getmonotonictime() - conn->ctx->session.auth_start_timeout) >=
                   1000 * (is_payment_option_contract ? conn->ctx->session.auth_timeout_pnc
                                                      : conn->ctx->session.auth_timeout_eim)) {
            conn->ctx->session.auth_start_timeout = getmonotonictime();
            res->ResponseCode = iso2_responseCodeType_FAILED;
        }
    } else if (conn->ctx->session.authorization_rejected == true and
               (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract)) {
        if (conn->ctx->session.certificate_status == types::authorization::CertificateStatus::CertificateRevoked) {
            res->ResponseCode = iso2_responseCodeType_FAILED_CertificateRevoked;
        } else {
            res->ResponseCode = iso2_responseCodeType_FAILED;
        }
    } else if (conn->ctx->session.authorization_rejected == true) {
        res->ResponseCode = iso2_responseCodeType_FAILED;
    }

error_out:
    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (res->EVSEProcessing == iso2_EVSEProcessingType_Finished)
                           ? (int)iso_dc_state_id::WAIT_FOR_CHARGEPARAMETERDISCOVERY
                           : (int)iso_dc_state_id::WAIT_FOR_AUTHORIZATION; // [V2G-573] (AC) , [V2G-687] (DC)

    return next_event;
}

/*!
 * \brief handle_iso_charge_parameter_discovery This function handles the iso_charge_parameter_discovery msg pair. It
 * analyzes the request msg and fills the response msg. The request and response msg based on the open V2G structures.
 * This structures must be provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_charge_parameter_discovery(struct v2g_connection* conn) {
    struct iso2_ChargeParameterDiscoveryReqType* req =
        &conn->exi_in.iso2EXIDocument->V2G_Message.Body.ChargeParameterDiscoveryReq;
    struct iso2_ChargeParameterDiscoveryResType* res =
        &conn->exi_out.iso2EXIDocument->V2G_Message.Body.ChargeParameterDiscoveryRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;

    /* At first, publish the received ev request message to the MQTT interface */
    publish_iso_charge_parameter_discovery_req(conn->ctx, req);

    /* First, check requested energy transfer mode, because this information is necessary for futher configuration */
    res->ResponseCode = iso2_responseCodeType_FAILED_WrongEnergyTransferMode;
    for (uint8_t idx = 0;
         idx < conn->ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.arrayLen; idx++) {
        if (req->RequestedEnergyTransferMode ==
            conn->ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.array[idx]) {
            res->ResponseCode = iso2_responseCodeType_OK; // [V2G2-476]
            log_selected_energy_transfer_type((int)req->RequestedEnergyTransferMode);
            break;
        }
    }

    res->EVSEChargeParameter_isUsed = 0;
    res->EVSEProcessing = (iso2_EVSEProcessingType)conn->ctx->evse_v2g_data.evse_processing[PHASE_PARAMETER];

    int64_t pmax{0};

    if (conn->ctx->is_dc_charger == false) {
        /* Determin max current and nominal voltage */
        /* Setup default params (before the departure time overrides) */
        float max_current = conn->ctx->basic_config.evse_ac_nominal_current;
        int64_t voltage = conn->ctx->evse_v2g_data.evse_nominal_voltage.Value *
                          pow(10, conn->ctx->evse_v2g_data.evse_nominal_voltage.Multiplier); /* nominal voltage */
        pmax = max_current * voltage *
               ((req->RequestedEnergyTransferMode == iso2_EnergyTransferModeType_AC_single_phase_core) ? 1 : 3);

        dlog(DLOG_LEVEL_INFO,
             "before adjusting for departure time, max_current %f, nom_voltage %d, pmax %d, departure_duration %d",
             max_current, voltage, pmax, req->AC_EVChargeParameter.DepartureTime);
    }

    /* Configure SA-schedules*/
    if (res->EVSEProcessing == iso2_EVSEProcessingType_Finished) {
        /* If processing is finished, configure SASchedule list */
        if (conn->ctx->evse_v2g_data.evse_sa_schedule_list_is_used == false) {
            int64_t departure_time_duration = req->AC_EVChargeParameter.DepartureTime;
            /* If not configured, configure SA-schedule automatically for AC charging */
            if (conn->ctx->is_dc_charger == false) {
                populate_physical_value(&conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
                                             .PMaxSchedule.PMaxScheduleEntry.array[0]
                                             .PMax,
                                        pmax, iso2_unitSymbolType_W);
            } else {
                conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
                    .PMaxSchedule.PMaxScheduleEntry.array[0]
                    .PMax = conn->ctx->evse_v2g_data.power_capabilities.max_power;
            }
            if (departure_time_duration == 0) {
                departure_time_duration = SA_SCHEDULE_DURATION; // one day, per spec
            }
            conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
                .PMaxSchedule.PMaxScheduleEntry.array[0]
                .RelativeTimeInterval.start = 0;
            conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
                .PMaxSchedule.PMaxScheduleEntry.array[0]
                .RelativeTimeInterval.duration_isUsed = 1;

            constexpr auto PAUSE_DURATION = 60 * 30;
            conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
                .PMaxSchedule.PMaxScheduleEntry.array[0]
                .RelativeTimeInterval.duration = conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::None
                                                     ? departure_time_duration
                                                     : PAUSE_DURATION;
            conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
                .PMaxSchedule.PMaxScheduleEntry.arrayLen = 1;
            conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.arrayLen = 1;
        }

        res->SAScheduleList = conn->ctx->evse_v2g_data.evse_sa_schedule_list;
        res->SAScheduleList_isUsed = (unsigned int)1; //  The SECC shall only omit the parameter 'SAScheduleList' in
                                                      //  case EVSEProcessing is set to 'Ongoing'.

        if ((req->MaxEntriesSAScheduleTuple_isUsed == (unsigned int)1) &&
            (req->MaxEntriesSAScheduleTuple < res->SAScheduleList.SAScheduleTuple.arrayLen)) {
            dlog(DLOG_LEVEL_WARNING, "EV's max. SA-schedule-tuple entries exceeded");
        }
    } else {
        res->EVSEProcessing = iso2_EVSEProcessingType_Ongoing;
        res->SAScheduleList_isUsed = (unsigned int)0;
    }

    /* Checking SAScheduleTupleID */
    for (uint8_t idx = 0; idx < res->SAScheduleList.SAScheduleTuple.arrayLen; idx++) {
        if (res->SAScheduleList.SAScheduleTuple.array[idx].SAScheduleTupleID == (uint8_t)0) {
            dlog(DLOG_LEVEL_WARNING, "Selected SAScheduleTupleID is not ISO15118 conform. The SECC shall use the "
                                     "values 1 to 255"); // [V2G2-773]  The SECC shall use the values 1 to 255 for the
                                                         // parameter SAScheduleTupleID.
        }
    }

    res->SASchedules_isUsed = 0;

    // TODO: For DC charging wait for CP state B , before transmitting of the response ([V2G2-921], [V2G2-922]). CP
    // state is checked by other module

    /* reset our internal reminder that renegotiation was requested */
    conn->ctx->session.renegotiation_required = false; // Reset renegotiation flag

    if (conn->ctx->is_dc_charger == false) {
        /* Configure AC stucture elements */
        res->AC_EVSEChargeParameter_isUsed = 1;
        res->DC_EVSEChargeParameter_isUsed = 0;

        populate_ac_evse_status(conn->ctx, &res->AC_EVSEChargeParameter.AC_EVSEStatus);

        /* Max current */
        float max_current = conn->ctx->basic_config.evse_ac_nominal_current;
        populate_physical_value_float(&res->AC_EVSEChargeParameter.EVSEMaxCurrent, max_current, 1,
                                      iso2_unitSymbolType_A);

        /* Nominal voltage */
        res->AC_EVSEChargeParameter.EVSENominalVoltage = conn->ctx->evse_v2g_data.evse_nominal_voltage;

        /* Calculate pmax based on max current, nominal voltage and phase count (which the car has selected above) */
        /* Check the SASchedule */
        if (res->SAScheduleList_isUsed == (unsigned int)1) {
            for (uint8_t idx = 0; idx < res->SAScheduleList.SAScheduleTuple.arrayLen; idx++) {
                for (uint8_t idx2 = 0;
                     idx2 < res->SAScheduleList.SAScheduleTuple.array[idx].PMaxSchedule.PMaxScheduleEntry.arrayLen;
                     idx2++)
                    if ((res->SAScheduleList.SAScheduleTuple.array[idx]
                             .PMaxSchedule.PMaxScheduleEntry.array[idx2]
                             .PMax.Value *
                         pow(10, res->SAScheduleList.SAScheduleTuple.array[idx]
                                     .PMaxSchedule.PMaxScheduleEntry.array[idx2]
                                     .PMax.Multiplier)) > pmax) {
                        dlog(DLOG_LEVEL_WARNING,
                             "Provided SA-schedule-list doesn't match with the physical value limits");
                    }
            }
        }

        if (req->DC_EVChargeParameter_isUsed == (unsigned int)1) {
            res->ResponseCode = iso2_responseCodeType_FAILED_WrongChargeParameter; // [V2G2-477]
        }
    } else {

        if (conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h == true) {
            static bool first_req = true;

            if (first_req == true) {
                res->EVSEProcessing = iso2_EVSEProcessingType_Ongoing;
                first_req = false;
            } else {
                // Check if second req message contains neg values
                // Check if bulk soc is set
                if (req->DC_EVChargeParameter.BulkSOC_isUsed == 1 &&
                    req->DC_EVChargeParameter.EVMaximumCurrentLimit.Value < 0 &&
                    req->DC_EVChargeParameter.EVMaximumPowerLimit_isUsed == 1 &&
                    req->DC_EVChargeParameter.EVMaximumPowerLimit.Value < 0) {
                    // Save bulk soc for minimal soc to stop
                    conn->ctx->evse_v2g_data.sae_bidi_data.sae_v2h_minimal_soc = req->DC_EVChargeParameter.BulkSOC;
                } else {
                    res->ResponseCode = iso2_responseCodeType::iso2_responseCodeType_FAILED_WrongEnergyTransferMode;
                }
                res->EVSEProcessing = iso2_EVSEProcessingType_Finished;
                // reset first_req
                first_req = true;
            }
        }

        /* Configure DC stucture elements */
        res->DC_EVSEChargeParameter_isUsed = 1;
        res->AC_EVSEChargeParameter_isUsed = 0;

        res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEIsolationStatus =
            (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status;
        res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEIsolationStatus_isUsed =
            conn->ctx->evse_v2g_data.evse_isolation_status_is_used;
        res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSENotification =
            (iso2_EVSENotificationType)conn->ctx->evse_v2g_data.evse_notification;
        res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEStatusCode =
            get_emergency_status_code(conn->ctx, PHASE_PARAMETER);
        res->DC_EVSEChargeParameter.DC_EVSEStatus.NotificationMaxDelay =
            (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay;

        res->DC_EVSEChargeParameter.EVSECurrentRegulationTolerance =
            conn->ctx->evse_v2g_data.evse_current_regulation_tolerance;
        res->DC_EVSEChargeParameter.EVSECurrentRegulationTolerance_isUsed =
            conn->ctx->evse_v2g_data.evse_current_regulation_tolerance_is_used;
        res->DC_EVSEChargeParameter.EVSEEnergyToBeDelivered = conn->ctx->evse_v2g_data.evse_energy_to_be_delivered;
        res->DC_EVSEChargeParameter.EVSEEnergyToBeDelivered_isUsed =
            conn->ctx->evse_v2g_data.evse_energy_to_be_delivered_is_used;
        res->DC_EVSEChargeParameter.EVSEMaximumCurrentLimit = conn->ctx->evse_v2g_data.power_capabilities.max_current;
        res->DC_EVSEChargeParameter.EVSEMaximumPowerLimit = conn->ctx->evse_v2g_data.power_capabilities.max_power;
        res->DC_EVSEChargeParameter.EVSEMaximumVoltageLimit = conn->ctx->evse_v2g_data.power_capabilities.max_voltage;
        res->DC_EVSEChargeParameter.EVSEMinimumCurrentLimit = conn->ctx->evse_v2g_data.power_capabilities.min_current;
        res->DC_EVSEChargeParameter.EVSEMinimumVoltageLimit = conn->ctx->evse_v2g_data.power_capabilities.min_voltage;
        res->DC_EVSEChargeParameter.EVSEPeakCurrentRipple = conn->ctx->evse_v2g_data.evse_peak_current_ripple;

        if ((unsigned int)1 == req->AC_EVChargeParameter_isUsed) {
            res->ResponseCode = iso2_responseCodeType_FAILED_WrongChargeParameter; // [V2G2-477]
        }
        if (not conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h and
            (req->DC_EVChargeParameter.EVMaximumCurrentLimit.Value < 0 or
             req->DC_EVChargeParameter.EVMaximumPowerLimit.Value < 0 or
             req->DC_EVChargeParameter.EVMaximumVoltageLimit.Value < 0)) {
            res->ResponseCode = iso2_responseCodeType_FAILED_WrongChargeParameter; // [V2G2-477]
        }

        constexpr auto physical_value_to_float = [](const iso2_PhysicalValueType& pv) {
            return calc_physical_value(pv.Value, pv.Multiplier);
        };

        const auto ev_maximum_current_limit = physical_value_to_float(req->DC_EVChargeParameter.EVMaximumCurrentLimit);
        const auto ev_maximum_voltage_limit = physical_value_to_float(req->DC_EVChargeParameter.EVMaximumVoltageLimit);
        const auto evse_minimum_current_limit =
            physical_value_to_float(conn->ctx->evse_v2g_data.power_capabilities.min_current);
        const auto evse_minimum_voltage_limit =
            physical_value_to_float(conn->ctx->evse_v2g_data.power_capabilities.min_voltage);

        if (ev_maximum_current_limit <= evse_minimum_current_limit ||
            ev_maximum_voltage_limit <= evse_minimum_voltage_limit) {
            res->ResponseCode = iso2_responseCodeType_FAILED_WrongChargeParameter;
            res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEStatusCode = iso2_DC_EVSEStatusCodeType_EVSE_Shutdown;
        }

        if (res->EVSEProcessing == iso2_EVSEProcessingType_Finished and
            conn->ctx->evse_v2g_data.no_energy_pause != NoEnergyPauseStatus::None) {
            res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSENotification = iso2_EVSENotificationType_StopCharging;

            constexpr auto PAUSE_NOTIFICATION_DELAY = 300;
            res->DC_EVSEChargeParameter.DC_EVSEStatus.NotificationMaxDelay =
                conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::BeforeCableCheck
                    ? 0
                    : PAUSE_NOTIFICATION_DELAY;
        }
    }

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    if (conn->ctx->is_dc_charger == true) {
        if (conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::BeforeCableCheck) {
            conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_PRECHARGE_POWERDELIVERY; // IEC6185-1:2023 CC.3.5.2
        } else {
            conn->ctx->state = (iso2_EVSEProcessingType_Finished == res->EVSEProcessing)
                                   ? (int)iso_dc_state_id::WAIT_FOR_CABLECHECK
                                   : (int)iso_dc_state_id::WAIT_FOR_CHARGEPARAMETERDISCOVERY; // [V2G-582], [V2G-688]
        }
    } else {
        conn->ctx->state = (iso2_EVSEProcessingType_Finished == res->EVSEProcessing)
                               ? (int)iso_ac_state_id::WAIT_FOR_POWERDELIVERY
                               : (int)iso_ac_state_id::WAIT_FOR_CHARGEPARAMETERDISCOVERY;
    }

    if (res->ResponseCode >= iso2_responseCodeType_FAILED) {
        res->DC_EVSEChargeParameter.EVSECurrentRegulationTolerance_isUsed = 0;
        res->DC_EVSEChargeParameter.EVSEEnergyToBeDelivered_isUsed = 0;
        res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0;
        res->SAScheduleList_isUsed = 0;
    }

    return next_event;
}

/*!
 * \brief handle_iso_power_delivery This function handles the iso_power_delivery msg pair. It analyzes the request msg
 * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be
 * provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_power_delivery(struct v2g_connection* conn) {
    struct iso2_PowerDeliveryReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.PowerDeliveryReq;
    struct iso2_PowerDeliveryResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.PowerDeliveryRes;
    struct timespec ts_abs_timeout;
    uint8_t sa_schedule_tuple_idx = 0;
    bool entry_found = false;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;

    /* At first, publish the received EV request message to the MQTT interface */
    publish_iso_power_delivery_req(conn->ctx, req);

    const auto ev_should_pause =
        conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::AfterCableCheckPreCharge or
        conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::BeforeCableCheck;

    /* build up response */
    res->ResponseCode = iso2_responseCodeType_OK;

    switch (req->ChargeProgress) {
    case iso2_chargeProgressType_Start:
        conn->ctx->p_charger->publish_v2g_setup_finished(nullptr);

        if (conn->ctx->is_dc_charger == false) {
            // TODO: For AC charging wait for CP state C or D , before transmitting of the response. CP state is checked
            // by other module
            if (conn->ctx->contactor_is_closed == false) {
                // TODO: Signal closing contactor with MQTT if no timeout while waiting for state C or D
                conn->ctx->p_charger->publish_ac_close_contactor(nullptr);
                conn->ctx->session.is_charging = true;

                /* determine timeout for contactor */
                clock_gettime(CLOCK_MONOTONIC, &ts_abs_timeout);
                timespec_add_ms(&ts_abs_timeout, V2G_CONTACTOR_CLOSE_TIMEOUT);

                /* wait for contactor to really close or timeout */
                dlog(DLOG_LEVEL_INFO, "Waiting for contactor is closed");

                int rv = 0;
                while ((rv == 0) && (conn->ctx->contactor_is_closed == false) &&
                       (conn->ctx->intl_emergency_shutdown == false) && (conn->ctx->stop_hlc == false) &&
                       (conn->ctx->is_connection_terminated == false)) {
                    pthread_mutex_lock(&conn->ctx->mqtt_lock);
                    rv = pthread_cond_timedwait(&conn->ctx->mqtt_cond, &conn->ctx->mqtt_lock, &ts_abs_timeout);
                    if (rv == EINTR)
                        rv = 0; /* restart */
                    if (rv == ETIMEDOUT) {
                        dlog(DLOG_LEVEL_ERROR, "timeout while waiting for contactor to close, signaling error");
                        res->ResponseCode = iso2_responseCodeType_FAILED_ContactorError;
                    }
                    pthread_mutex_unlock(&conn->ctx->mqtt_lock);
                }
            }
        } else if (ev_should_pause) {

            dlog(DLOG_LEVEL_ERROR, "The EV did not pause the session even EVSE signaled the EV that no energy is "
                                   "available. Abort the session");
            res->ResponseCode = iso2_responseCodeType_FAILED;
            conn->ctx->session.is_charging = false;
            conn->ctx->p_charger->publish_dc_open_contactor(nullptr);
        }
        break;

    case iso2_chargeProgressType_Stop:
        conn->ctx->session.is_charging = false;

        if (conn->ctx->is_dc_charger == false) {
            // TODO: For AC charging wait for CP state change from C/D to B , before transmitting of the response. CP
            // state is checked by other module
            conn->ctx->p_charger->publish_ac_open_contactor(nullptr);
        } else {
            conn->ctx->p_charger->publish_current_demand_finished(nullptr);
            conn->ctx->p_charger->publish_dc_open_contactor(nullptr);
        }
        break;

    case iso2_chargeProgressType_Renegotiate:
        conn->ctx->session.renegotiation_required = true;
        break;

    default:
        dlog(DLOG_LEVEL_ERROR, "Unknown ChargeProgress %d received, signaling error", req->ChargeProgress);
        res->ResponseCode = iso2_responseCodeType_FAILED;
    }

    if (conn->ctx->is_dc_charger == false) {
        res->AC_EVSEStatus_isUsed = 1;
        res->DC_EVSEStatus_isUsed = 0;
        populate_ac_evse_status(conn->ctx, &res->AC_EVSEStatus);
    } else {
        res->DC_EVSEStatus_isUsed = 1;
        res->AC_EVSEStatus_isUsed = 0;
        res->DC_EVSEStatus.EVSEIsolationStatus =
            (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status;
        res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used;
        res->DC_EVSEStatus.EVSENotification =
            static_cast<iso2_EVSENotificationType>(conn->ctx->evse_v2g_data.evse_notification);
        res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_CHARGE);
        res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay;

        res->ResponseCode = (req->ChargeProgress == iso2_chargeProgressType_Start) &&
                                    (res->DC_EVSEStatus.EVSEStatusCode != iso2_DC_EVSEStatusCodeType_EVSE_Ready)
                                ? iso2_responseCodeType_FAILED_PowerDeliveryNotApplied
                                : res->ResponseCode; // [V2G2-480]
    }

    res->EVSEStatus_isUsed = 0;

    /* Check the selected SAScheduleTupleID */
    for (sa_schedule_tuple_idx = 0;
         sa_schedule_tuple_idx < conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.arrayLen;
         sa_schedule_tuple_idx++) {
        if (conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[sa_schedule_tuple_idx]
                .SAScheduleTupleID == req->SAScheduleTupleID) {
            entry_found = true;
            conn->ctx->session.sa_schedule_tuple_id = req->SAScheduleTupleID;
            break;
        }
    }

    res->ResponseCode =
        (entry_found == false) ? iso2_responseCodeType_FAILED_TariffSelectionInvalid : res->ResponseCode; // [V2G2-479]

    /* Check EV charging profile values [V2G2-478] */
    check_iso2_charging_profile_values(req, res, conn, sa_schedule_tuple_idx);

    const auto last_v2g_msg = conn->ctx->last_v2g_msg;

    /* abort charging session if EV is ready to charge after current demand phase */
    if ((req->ChargeProgress == iso2_chargeProgressType_Start and
         (last_v2g_msg == V2G_CURRENT_DEMAND_MSG or last_v2g_msg == V2G_CHARGING_STATUS_MSG)) or
        (req->ChargeProgress == iso2_chargeProgressType_Renegotiate and
         (last_v2g_msg != V2G_CURRENT_DEMAND_MSG and last_v2g_msg != V2G_CHARGING_STATUS_MSG))) {
        res->ResponseCode = iso2_responseCodeType_FAILED; // (/*[V2G2-812]*/
    }

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    if ((req->ChargeProgress == iso2_chargeProgressType_Renegotiate) &&
        ((conn->ctx->last_v2g_msg == V2G_CURRENT_DEMAND_MSG) || (conn->ctx->last_v2g_msg == V2G_CHARGING_STATUS_MSG))) {
        conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_CHARGEPARAMETERDISCOVERY; // [V2G-813]

        if (conn->ctx->is_dc_charger == false) {
            // Reset AC relevant parameter to start the renegotation process
            conn->ctx->evse_v2g_data.evse_notification =
                (conn->ctx->evse_v2g_data.evse_notification == iso2_EVSENotificationType_ReNegotiation)
                    ? iso2_EVSENotificationType_None
                    : conn->ctx->evse_v2g_data.evse_notification;
        } else {
            // Reset DC relevant parameter to start the renegotation process
            conn->ctx->evse_v2g_data.evse_processing[PHASE_ISOLATION] = iso2_EVSEProcessingType_Ongoing;
            conn->ctx->evse_v2g_data.evse_notification =
                (iso2_EVSENotificationType_ReNegotiation == conn->ctx->evse_v2g_data.evse_notification)
                    ? iso2_EVSENotificationType_None
                    : conn->ctx->evse_v2g_data.evse_notification;
            conn->ctx->evse_v2g_data.evse_isolation_status = iso2_isolationLevelType_Invalid;
        }
    } else if ((req->ChargeProgress == iso2_chargeProgressType_Start) &&
               (conn->ctx->last_v2g_msg != V2G_CURRENT_DEMAND_MSG) &&
               (conn->ctx->last_v2g_msg != V2G_CHARGING_STATUS_MSG)) {
        conn->ctx->state = (conn->ctx->is_dc_charger == true)
                               ? (int)iso_dc_state_id::WAIT_FOR_CURRENTDEMAND
                               : (int)iso_ac_state_id::WAIT_FOR_CHARGINGSTATUS; // [V2G-590], [V2G2-576]
    } else {
        conn->ctx->state = (conn->ctx->is_dc_charger == true)
                               ? (int)iso_dc_state_id::WAIT_FOR_WELDINGDETECTION_SESSIONSTOP
                               : (int)iso_ac_state_id::WAIT_FOR_SESSIONSTOP; // [V2G-601], [V2G2-568]
    }

    if (res->ResponseCode >= iso2_responseCodeType_FAILED) {
        res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0;
    }

    return next_event;
}

/*!
 * \brief handle_iso_charging_status This function handles the iso_charging_status msg pair. It analyzes the request msg
 * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be
 * provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_charging_status(struct v2g_connection* conn) {
    struct iso2_ChargingStatusResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.ChargingStatusRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;
    /* build up response */
    res->ResponseCode = iso2_responseCodeType_OK;

    res->ReceiptRequired = false; // [V2G2-691] ReceiptRequired shall be false in case of EIM
    if (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract) {
        res->ReceiptRequired = conn->ctx->evse_v2g_data.receipt_required;
    }
    res->ReceiptRequired_isUsed = true; // [V2G2-691] ChargingStatusRes shall always include ReceiptRequired

    if (conn->ctx->meter_info.meter_info_is_used == true) {
        res->MeterInfo.MeterID.charactersLen = conn->ctx->meter_info.meter_id.bytesLen;
        memcpy(res->MeterInfo.MeterID.characters, conn->ctx->meter_info.meter_id.bytes, iso2_MeterID_CHARACTER_SIZE);
        res->MeterInfo.MeterReading = conn->ctx->meter_info.meter_reading;
        res->MeterInfo.MeterReading_isUsed = 1;
        res->MeterInfo_isUsed = 1;
        // Reset the signal for the next time handle_set_MeterInfo is signaled
        conn->ctx->meter_info.meter_info_is_used = false;
    } else {
        res->MeterInfo_isUsed = 0;
    }

    res->EVSEMaxCurrent_isUsed = (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract)
                                     ? (unsigned int)0
                                     : (unsigned int)1; // This element is not included in the message if any AC PnC
                                                        // Message Set has been selected.

    if ((unsigned int)1 == res->EVSEMaxCurrent_isUsed) {
        populate_physical_value_float(&res->EVSEMaxCurrent, conn->ctx->basic_config.evse_ac_current_limit, 1,
                                      iso2_unitSymbolType_A);
    }

    conn->exi_out.iso2EXIDocument->V2G_Message.Body.ChargingStatusRes_isUsed = 1;

    /* the following field can also be set in error path */
    res->EVSEID.charactersLen = conn->ctx->evse_v2g_data.evse_id.bytesLen;
    memcpy(res->EVSEID.characters, conn->ctx->evse_v2g_data.evse_id.bytes, conn->ctx->evse_v2g_data.evse_id.bytesLen);

    /* in error path the session might not be available */
    res->SAScheduleTupleID = conn->ctx->session.sa_schedule_tuple_id;
    populate_ac_evse_status(conn->ctx, &res->AC_EVSEStatus);

    /* Check the current response code and check if no external error has occurred */
    next_event = (enum v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (((int)1 == res->ReceiptRequired))
                           ? (int)iso_ac_state_id::WAIT_FOR_METERINGRECEIPT
                           : (int)iso_ac_state_id::WAIT_FOR_CHARGINGSTATUS_POWERDELIVERY; // [V2G2-577], [V2G2-575]

    return next_event;
}

/*!
 * \brief handle_iso_metering_receipt This function handles the iso_metering_receipt msg pair. It analyzes the request
 * msg and fills the response msg. The request and response msg based on the open V2G structures. This structures must
 * be provided within the \c conn structure. \param conn holds the structure with the V2G msg pair. \return Returns the
 * next V2G-event.
 */
static enum v2g_event handle_iso_metering_receipt(struct v2g_connection* conn) {
    struct iso2_MeteringReceiptReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.MeteringReceiptReq;
    struct iso2_MeteringReceiptResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.MeteringReceiptRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;

    /* At first, publish the received ev request message to the MQTTinterface */
    publish_iso_metering_receipt_req(req);

    dlog(DLOG_LEVEL_TRACE, "EVSE side: meteringReceipt called");
    dlog(DLOG_LEVEL_TRACE, "\tReceived data:");

    dlog(DLOG_LEVEL_TRACE, "\t\t ID=%c%c%c", req->Id.characters[0], req->Id.characters[1], req->Id.characters[2]);
    dlog(DLOG_LEVEL_TRACE, "\t\t SAScheduleTupleID=%d", req->SAScheduleTupleID);
    dlog(DLOG_LEVEL_TRACE, "\t\t SessionID=%d", req->SessionID.bytes[1]);
    dlog(DLOG_LEVEL_TRACE, "\t\t MeterInfo.MeterStatus=%d", req->MeterInfo.MeterStatus);
    dlog(DLOG_LEVEL_TRACE, "\t\t MeterInfo.MeterID=%d", req->MeterInfo.MeterID.characters[0]);
    dlog(DLOG_LEVEL_TRACE, "\t\t MeterInfo.isused.MeterReading=%d", req->MeterInfo.MeterReading_isUsed);
    dlog(DLOG_LEVEL_TRACE, "\t\t MeterReading.Value=%lu", (long unsigned int)req->MeterInfo.MeterReading);
    dlog(DLOG_LEVEL_TRACE, "\t\t MeterInfo.TMeter=%li", (long int)req->MeterInfo.TMeter);

    res->ResponseCode = iso2_responseCodeType_OK;

    if (conn->ctx->is_dc_charger == false) {
        /* for AC charging we respond with AC_EVSEStatus */
        res->EVSEStatus_isUsed = 0;
        res->AC_EVSEStatus_isUsed = 1;
        res->DC_EVSEStatus_isUsed = 0;
        populate_ac_evse_status(conn->ctx, &res->AC_EVSEStatus);
    } else {
        res->DC_EVSEStatus_isUsed = 1;
        res->AC_EVSEStatus_isUsed = 0;
    }

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (conn->ctx->is_dc_charger == false)
                           ? (int)iso_ac_state_id::WAIT_FOR_CHARGINGSTATUS_POWERDELIVERY
                           : (int)iso_dc_state_id::WAIT_FOR_CURRENTDEMAND_POWERDELIVERY; // [V2G2-580]/[V2G-797]

    return next_event;
}

/*!
 * \brief handle_iso_certificate_update This function handles the iso_certificate_update msg pair. It analyzes the
 * request msg and fills the response msg. The request and response msg based on the open V2G structures. This
 * structures must be provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_certificate_update(struct v2g_connection* conn) {
    // TODO: implement CertificateUpdate handling
    return V2G_EVENT_NO_EVENT;
}

/*!
 * \brief handle_iso_certificate_installation This function handles the iso_certificate_installation msg pair. It
 * analyzes the request msg and fills the response msg. The request and response msg based on the open V2G structures.
 * This structures must be provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_certificate_installation(struct v2g_connection* conn) {
    struct iso2_CertificateInstallationResType* res =
        &conn->exi_out.iso2EXIDocument->V2G_Message.Body.CertificateInstallationRes;
    enum v2g_event nextEvent = V2G_EVENT_SEND_AND_TERMINATE;
    struct timespec ts_abs_timeout;
    int rv = 0;
    /* At first, publish the received EV request message to the customer MQTT interface */
    if (publish_iso_certificate_installation_exi_req(conn->ctx, conn->buffer + V2GTP_HEADER_LENGTH,
                                                     conn->stream.data_size - V2GTP_HEADER_LENGTH) == false) {
        dlog(DLOG_LEVEL_ERROR, "Failed to send CertificateInstallationExiReq");
        goto exit;
    }
    /* Waiting for the CertInstallationExiRes msg */
    clock_gettime(CLOCK_MONOTONIC, &ts_abs_timeout);
    timespec_add_ms(&ts_abs_timeout, V2G_SECC_MSG_CERTINSTALL_TIME);
    dlog(DLOG_LEVEL_INFO, "Waiting for the CertInstallationExiRes msg");
    while ((rv == 0) && (conn->ctx->evse_v2g_data.cert_install_res_b64_buffer.empty() == true) &&
           (conn->ctx->intl_emergency_shutdown == false) && (conn->ctx->stop_hlc == false) &&
           (conn->ctx->is_connection_terminated == false)) { // [V2G2-917]
        pthread_mutex_lock(&conn->ctx->mqtt_lock);
        rv = pthread_cond_timedwait(&conn->ctx->mqtt_cond, &conn->ctx->mqtt_lock, &ts_abs_timeout);
        if (rv == EINTR)
            rv = 0; /* restart */
        if (rv == ETIMEDOUT) {
            dlog(DLOG_LEVEL_ERROR, "CertificateInstallationRes timeout occurred");
            conn->ctx->intl_emergency_shutdown = true; // [V2G2-918] Initiating emergency shutdown, response code faild
                                                       // will be set in iso_validate_response_code() function
        }
        pthread_mutex_unlock(&conn->ctx->mqtt_lock);
    }

    if ((conn->ctx->evse_v2g_data.cert_install_res_b64_buffer.empty() == false) &&
        (conn->ctx->evse_v2g_data.cert_install_status == true)) {
        const auto data = openssl::base64_decode(conn->ctx->evse_v2g_data.cert_install_res_b64_buffer.data(),
                                                 conn->ctx->evse_v2g_data.cert_install_res_b64_buffer.size());
        if (data.empty() || (data.size() > DEFAULT_BUFFER_SIZE - V2GTP_HEADER_LENGTH)) {
            dlog(DLOG_LEVEL_ERROR, "Failed to decode base64 stream");
            goto exit;
        } else {
            std::memcpy(conn->buffer + V2GTP_HEADER_LENGTH, data.data(), data.size());
            conn->stream.byte_pos = data.size();
        }
        nextEvent = V2G_EVENT_SEND_RECV_EXI_MSG;
        res->ResponseCode =
            iso2_responseCodeType_OK; // Is irrelevant but must be valid to serve the internal validation
        conn->stream.byte_pos +=
            V2GTP_HEADER_LENGTH; // byte_pos had only the payload, so increase it to be header + payload
    } else {
        res->ResponseCode = iso2_responseCodeType_FAILED;
    }

exit:
    if (V2G_EVENT_SEND_RECV_EXI_MSG != nextEvent) {
        /* Check the current response code and check if no external error has occurred */
        nextEvent = (enum v2g_event)iso_validate_response_code(&res->ResponseCode, conn);
    } else {
        /* Reset v2g-msg If there, in case of an error */
        init_iso2_CertificateInstallationResType(res);
    }

    /* Set next expected req msg */
    conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_PAYMENTDETAILS; // [V2G-554]

    return nextEvent;
}

/*!
 * \brief handle_iso_cable_check This function handles the iso_cable_check msg pair. It analyzes the request msg and
 * fills the response msg. The request and response msg based on the open V2G structures. This structures must be
 * provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_cable_check(struct v2g_connection* conn) {
    struct iso2_CableCheckReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.CableCheckReq;
    struct iso2_CableCheckResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.CableCheckRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;

    /* At first, publish the received EV request message to the MQTT interface */
    publish_DcEvStatus(conn->ctx, req->DC_EVStatus);

    // TODO: For DC charging wait for CP state C or D , before transmitting of the response ([V2G2-917], [V2G2-918]). CP
    // state is checked by other module

    /* Fill the CableCheckRes */
    res->ResponseCode = iso2_responseCodeType_OK;
    res->DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status;
    res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used;
    res->DC_EVSEStatus.EVSENotification =
        static_cast<iso2_EVSENotificationType>(conn->ctx->evse_v2g_data.evse_notification);
    res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay;
    res->EVSEProcessing =
        static_cast<iso2_EVSEProcessingType>(conn->ctx->evse_v2g_data.evse_processing[PHASE_ISOLATION]);

    if (conn->ctx->intl_emergency_shutdown == false && res->EVSEProcessing == iso2_EVSEProcessingType_Finished) {
        res->DC_EVSEStatus.EVSEStatusCode = iso2_DC_EVSEStatusCodeType_EVSE_Ready;
    } else {
        res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_ISOLATION);
    }

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (res->EVSEProcessing == iso2_EVSEProcessingType_Finished)
                           ? (int)iso_dc_state_id::WAIT_FOR_PRECHARGE
                           : (int)iso_dc_state_id::WAIT_FOR_CABLECHECK; // [V2G-584], [V2G-621]

    if (res->ResponseCode >= iso2_responseCodeType_FAILED) {
        res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0;
    }

    return next_event;
}

/*!
 * \brief handle_iso_pre_charge This function handles the iso_pre_charge msg pair. It analyzes the request msg and fills
 * the response msg. The request and response msg based on the open V2G structures. This structures must be provided
 * within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_pre_charge(struct v2g_connection* conn) {
    struct iso2_PreChargeReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.PreChargeReq;
    struct iso2_PreChargeResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.PreChargeRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;

    /* At first, publish the received EV request message to the MQTT interface */
    publish_iso_pre_charge_req(conn->ctx, req);

    /* Fill the PreChargeRes*/
    res->DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status;
    res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used;
    res->DC_EVSEStatus.EVSENotification =
        static_cast<iso2_EVSENotificationType>(conn->ctx->evse_v2g_data.evse_notification);
    res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_PRECHARGE);
    res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay;
    res->EVSEPresentVoltage = (iso2_PhysicalValueType)conn->ctx->evse_v2g_data.evse_present_voltage;
    res->ResponseCode = iso2_responseCodeType_OK;

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_PRECHARGE_POWERDELIVERY; // [V2G-587]

    if (res->ResponseCode >= iso2_responseCodeType_FAILED) {
        res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0;
    }

    return next_event;
}

/*!
 * \brief handle_iso_current_demand This function handles the iso_current_demand msg pair. It analyzes the request msg
 * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be
 * provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_current_demand(struct v2g_connection* conn) {
    struct iso2_CurrentDemandReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.CurrentDemandReq;
    struct iso2_CurrentDemandResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.CurrentDemandRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;

    /* At first, publish the received EV request message to the MQTT interface */
    publish_iso_current_demand_req(conn->ctx, req);

    res->DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status;
    res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used;
    res->DC_EVSEStatus.EVSENotification =
        static_cast<iso2_EVSENotificationType>(conn->ctx->evse_v2g_data.evse_notification);
    res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_CHARGE);
    res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay;
    if ((conn->ctx->evse_v2g_data.evse_maximum_current_limit_is_used == 1) &&
        (calc_physical_value(req->EVTargetCurrent.Value, req->EVTargetCurrent.Multiplier) >=
         calc_physical_value(conn->ctx->evse_v2g_data.evse_maximum_current_limit.Value,
                             conn->ctx->evse_v2g_data.evse_maximum_current_limit.Multiplier))) {
        conn->ctx->evse_v2g_data.evse_current_limit_achieved = (int)1;
    } else {
        conn->ctx->evse_v2g_data.evse_current_limit_achieved = (int)0;
    }
    res->EVSECurrentLimitAchieved = conn->ctx->evse_v2g_data.evse_current_limit_achieved;
    memcpy(res->EVSEID.characters, conn->ctx->evse_v2g_data.evse_id.bytes, conn->ctx->evse_v2g_data.evse_id.bytesLen);
    res->EVSEID.charactersLen = conn->ctx->evse_v2g_data.evse_id.bytesLen;
    res->EVSEMaximumCurrentLimit = conn->ctx->evse_v2g_data.evse_maximum_current_limit;
    res->EVSEMaximumCurrentLimit_isUsed = conn->ctx->evse_v2g_data.evse_maximum_current_limit_is_used;
    res->EVSEMaximumPowerLimit = conn->ctx->evse_v2g_data.evse_maximum_power_limit;
    res->EVSEMaximumPowerLimit_isUsed = conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used;
    res->EVSEMaximumVoltageLimit = conn->ctx->evse_v2g_data.evse_maximum_voltage_limit;
    res->EVSEMaximumVoltageLimit_isUsed = conn->ctx->evse_v2g_data.evse_maximum_voltage_limit_is_used;
    double EVTargetPower = calc_physical_value(req->EVTargetCurrent.Value, req->EVTargetCurrent.Multiplier) *
                           calc_physical_value(req->EVTargetVoltage.Value, req->EVTargetVoltage.Multiplier);
    if ((conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used == 1) &&
        (EVTargetPower >= calc_physical_value(conn->ctx->evse_v2g_data.evse_maximum_power_limit.Value,
                                              conn->ctx->evse_v2g_data.evse_maximum_power_limit.Multiplier))) {
        conn->ctx->evse_v2g_data.evse_power_limit_achieved = (int)1;
    } else {
        conn->ctx->evse_v2g_data.evse_power_limit_achieved = (int)0;
    }
    res->EVSEPowerLimitAchieved = conn->ctx->evse_v2g_data.evse_power_limit_achieved;
    res->EVSEPresentCurrent = conn->ctx->evse_v2g_data.evse_present_current;
    res->EVSEPresentVoltage = conn->ctx->evse_v2g_data.evse_present_voltage;
    if ((conn->ctx->evse_v2g_data.evse_maximum_voltage_limit_is_used == 1) &&
        (calc_physical_value(req->EVTargetVoltage.Value, req->EVTargetVoltage.Multiplier) >=
         calc_physical_value(conn->ctx->evse_v2g_data.evse_maximum_voltage_limit.Value,
                             conn->ctx->evse_v2g_data.evse_maximum_voltage_limit.Multiplier))) {
        conn->ctx->evse_v2g_data.evse_voltage_limit_achieved = (int)1;
    } else {
        conn->ctx->evse_v2g_data.evse_voltage_limit_achieved = (int)0;
    }
    res->EVSEVoltageLimitAchieved = conn->ctx->evse_v2g_data.evse_voltage_limit_achieved;
    if (conn->ctx->meter_info.meter_info_is_used == true) {
        res->MeterInfo.MeterID.charactersLen = conn->ctx->meter_info.meter_id.bytesLen;
        memcpy(res->MeterInfo.MeterID.characters, conn->ctx->meter_info.meter_id.bytes, iso2_MeterID_CHARACTER_SIZE);
        res->MeterInfo.MeterReading = conn->ctx->meter_info.meter_reading;
        res->MeterInfo.MeterReading_isUsed = 1;
        res->MeterInfo_isUsed = 1;
        // Reset the signal for the next time handle_set_MeterInfo is signaled
        conn->ctx->meter_info.meter_info_is_used = false;
    } else {
        res->MeterInfo_isUsed = 0;
    }
    res->ReceiptRequired = conn->ctx->evse_v2g_data.receipt_required; // TODO: PNC only
    res->ReceiptRequired_isUsed = (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract)
                                      ? (unsigned int)conn->ctx->evse_v2g_data.receipt_required
                                      : (unsigned int)0;
    res->ResponseCode = iso2_responseCodeType_OK;
    res->SAScheduleTupleID = conn->ctx->session.sa_schedule_tuple_id;

    static uint8_t req_pos_value_count = 0;

    if (conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2g == true) {

        // case: evse initiated -> Negative PresentCurrent, EvseMaxCurrentLimit, EvseMaxCurrentLimit
        if (conn->ctx->evse_v2g_data.sae_bidi_data.discharging == false &&
            conn->ctx->evse_v2g_data.evse_present_current.Value < 0 &&
            conn->ctx->evse_v2g_data.evse_maximum_current_limit_is_used == true &&
            conn->ctx->evse_v2g_data.evse_maximum_current_limit.Value < 0 &&
            conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used == true &&
            conn->ctx->evse_v2g_data.evse_maximum_power_limit.Value < 0) {
            if (req->EVTargetCurrent.Value > 0) {
                if (req_pos_value_count++ >= 1) {
                    dlog(DLOG_LEVEL_WARNING, "SAE V2G Bidi handshake was not recognized by the ev side. Instead of "
                                             "shutting down, it is better to wait for a correct response");
                    req_pos_value_count = 0;
                } else {
                    req_pos_value_count = 0;
                    conn->ctx->evse_v2g_data.sae_bidi_data.discharging = true;
                }
            }
        } else if (conn->ctx->evse_v2g_data.sae_bidi_data.discharging == true &&
                   conn->ctx->evse_v2g_data.evse_present_current.Value > 0 &&
                   conn->ctx->evse_v2g_data.evse_maximum_current_limit_is_used == true &&
                   conn->ctx->evse_v2g_data.evse_maximum_current_limit.Value > 0 &&
                   conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used == true &&
                   conn->ctx->evse_v2g_data.evse_maximum_power_limit.Value > 0) {
            if (req->EVTargetCurrent.Value < 0) {
                if (req_pos_value_count++ >= 1) {
                    dlog(DLOG_LEVEL_WARNING, "SAE V2G Bidi handshake was not recognized by the ev side. Instead of "
                                             "shutting down, it is better to wait for a correct response");
                    req_pos_value_count = 0;
                } else {
                    req_pos_value_count = 0;
                    conn->ctx->evse_v2g_data.sae_bidi_data.discharging = false;
                }
            }
        }

        // case: ev initiated -> Negative EvTargetCurrent, EVMaxCurrentLimit, EVMaxPowerLimit
        // Todo(SL): Is it necessary to notify the evse_manager that the ev want to give power/current?
        // Or is it obvious because of the negative target current request.

    } else if (conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h == true) {
        if (req->DC_EVStatus.EVRESSSOC <= conn->ctx->evse_v2g_data.sae_bidi_data.sae_v2h_minimal_soc) {
            res->DC_EVSEStatus.EVSEStatusCode = iso2_DC_EVSEStatusCodeType_EVSE_Shutdown;
        }
    }

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = ((res->ReceiptRequired_isUsed == (unsigned int)1) && (res->ReceiptRequired == (int)1))
                           ? (int)iso_dc_state_id::WAIT_FOR_METERINGRECEIPT
                           : (int)iso_dc_state_id::WAIT_FOR_CURRENTDEMAND_POWERDELIVERY; // [V2G-795], [V2G-593]

    if (res->ResponseCode >= iso2_responseCodeType_FAILED) {
        res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0;
        res->MeterInfo_isUsed = 0;
        res->MeterInfo.MeterReading_isUsed = 0;
        res->EVSEMaximumVoltageLimit_isUsed = 0;
        res->EVSEMaximumCurrentLimit_isUsed = 0;
        res->EVSEMaximumPowerLimit_isUsed = 0;
    }

    return next_event;
}

/*!
 * \brief handle_iso_welding_detection This function handles the iso_welding_detection msg pair. It analyzes the request
 * msg and fills the response msg. The request and response msg based on the open V2G structures. This structures must
 * be provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_welding_detection(struct v2g_connection* conn) {
    struct iso2_WeldingDetectionReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.WeldingDetectionReq;
    struct iso2_WeldingDetectionResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.WeldingDetectionRes;
    enum v2g_event next_event = V2G_EVENT_NO_EVENT;

    /* At first, publish the received EV request message to the MQTT interface */
    publish_iso_welding_detection_req(conn->ctx, req);

    // TODO: Wait for CP state B, before transmitting of the response, or signal intl_emergency_shutdown in conn->ctx
    // ([V2G2-920], [V2G2-921]).

    res->DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status;
    res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used;
    res->DC_EVSEStatus.EVSENotification =
        static_cast<iso2_EVSENotificationType>(conn->ctx->evse_v2g_data.evse_notification);
    res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_WELDING);
    res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay;
    res->EVSEPresentVoltage = conn->ctx->evse_v2g_data.evse_present_voltage;
    res->ResponseCode = iso2_responseCodeType_OK;

    /* Check the current response code and check if no external error has occurred */
    next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn);

    /* Set next expected req msg */
    conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_WELDINGDETECTION_SESSIONSTOP; // [V2G-597]

    if (res->ResponseCode >= iso2_responseCodeType_FAILED) {
        res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0;
    }

    return next_event;
}

/*!
 * \brief handle_iso_session_stop This function handles the iso_session_stop msg pair. It analyses the request msg and
 * fills the response msg. The request and response msg based on the open V2G structures. This structures must be
 * provided within the \c conn structure.
 * \param conn holds the structure with the V2G msg pair.
 * \return Returns the next V2G-event.
 */
static enum v2g_event handle_iso_session_stop(struct v2g_connection* conn) {
    struct iso2_SessionStopReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.SessionStopReq;
    struct iso2_SessionStopResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.SessionStopRes;

    res->ResponseCode = iso2_responseCodeType_OK;

    /* Check the current response code and check if no external error has occurred */
    auto event = iso_validate_response_code(&res->ResponseCode, conn);

    /* In case of an error, ignore payload of the request message and trigger a d-link error */
    if (event != V2G_EVENT_NO_EVENT) {
        conn->d_link_action = dLinkAction::D_LINK_ACTION_ERROR;
        conn->ctx->hlc_pause_active = false;
        /* Set next expected req msg */
        conn->ctx->state = static_cast<int>(iso_dc_state_id::WAIT_FOR_TERMINATED_SESSION);
        return event;
    }

    /* Set the next charging state */
    switch (req->ChargingSession) {
    case iso2_chargingSessionType_Terminate:
        conn->d_link_action = dLinkAction::D_LINK_ACTION_TERMINATE;
        conn->ctx->hlc_pause_active = false;
        /* Set next expected req msg */
        conn->ctx->state = static_cast<int>(iso_dc_state_id::WAIT_FOR_TERMINATED_SESSION);
        break;

    case iso2_chargingSessionType_Pause:
        /* Set next expected req msg */
        /* Check if the EV is allowed to request the sleep mode. TODO: Remove "true" if sleep mode is supported */
        if (((conn->ctx->last_v2g_msg != V2G_POWER_DELIVERY_MSG) &&
             (conn->ctx->last_v2g_msg != V2G_WELDING_DETECTION_MSG))) {
            conn->d_link_action = dLinkAction::D_LINK_ACTION_TERMINATE;
            res->ResponseCode = iso2_responseCodeType_FAILED;
            conn->ctx->hlc_pause_active = false;
            conn->ctx->state = static_cast<int>(iso_dc_state_id::WAIT_FOR_TERMINATED_SESSION);
        } else {
            /* Init sleep mode for the EV */
            conn->d_link_action = dLinkAction::D_LINK_ACTION_PAUSE;
            conn->ctx->hlc_pause_active = true;
            conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_SESSIONSETUP;
        }
        break;

    default:
        /* Set next expected req msg */
        conn->d_link_action = dLinkAction::D_LINK_ACTION_TERMINATE;
        conn->ctx->state = static_cast<int>(iso_dc_state_id::WAIT_FOR_TERMINATED_SESSION);
    }

    return V2G_EVENT_SEND_AND_TERMINATE; // Charging must be terminated after sending the response message [V2G2-571]
}

enum v2g_event iso_handle_request(v2g_connection* conn) {
    struct iso2_exiDocument* exi_in = conn->exi_in.iso2EXIDocument;
    struct iso2_exiDocument* exi_out = conn->exi_out.iso2EXIDocument;
    enum v2g_event next_v2g_event = V2G_EVENT_TERMINATE_CONNECTION;

    /* extract session id */
    conn->ctx->ev_v2g_data.received_session_id = v2g_session_id_from_exi(true, exi_in);

    /* init V2G structure (document, header, body) */
    init_iso2_exiDocument(exi_out);
    init_iso2_MessageHeaderType(&exi_out->V2G_Message.Header);

    exi_out->V2G_Message.Header.SessionID.bytesLen = 8;
    init_iso2_BodyType(&exi_out->V2G_Message.Body);

    /* handle each message type individually;
     * we use a none-usual source code formatting here to optically group the individual
     * request a little bit
     */
    if (exi_in->V2G_Message.Body.CurrentDemandReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling CurrentDemandReq");
        if (conn->ctx->last_v2g_msg == V2G_POWER_DELIVERY_MSG) {
            conn->ctx->p_charger->publish_current_demand_started(nullptr);
            conn->ctx->session.is_charging = true;
        }
        conn->ctx->current_v2g_msg = V2G_CURRENT_DEMAND_MSG;
        exi_out->V2G_Message.Body.CurrentDemandRes_isUsed = 1u;
        init_iso2_CurrentDemandResType(&exi_out->V2G_Message.Body.CurrentDemandRes);
        next_v2g_event = handle_iso_current_demand(conn); //  [V2G2-592]
    } else if (exi_in->V2G_Message.Body.SessionSetupReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling SessionSetupReq");
        conn->ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG;
        exi_out->V2G_Message.Body.SessionSetupRes_isUsed = 1u;
        init_iso2_SessionSetupResType(&exi_out->V2G_Message.Body.SessionSetupRes);
        next_v2g_event = handle_iso_session_setup(conn); // [V2G2-542]
    } else if (exi_in->V2G_Message.Body.ServiceDiscoveryReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling ServiceDiscoveryReq");
        conn->ctx->current_v2g_msg = V2G_SERVICE_DISCOVERY_MSG;
        exi_out->V2G_Message.Body.ServiceDiscoveryRes_isUsed = 1u;
        init_iso2_ServiceDiscoveryResType(&exi_out->V2G_Message.Body.ServiceDiscoveryRes);
        next_v2g_event = handle_iso_service_discovery(conn); // [V2G2-544]
    } else if (exi_in->V2G_Message.Body.ServiceDetailReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling ServiceDetailReq");
        conn->ctx->current_v2g_msg = V2G_SERVICE_DETAIL_MSG;
        exi_out->V2G_Message.Body.ServiceDetailRes_isUsed = 1u;
        init_iso2_ServiceDetailResType(&exi_out->V2G_Message.Body.ServiceDetailRes);
        next_v2g_event = handle_iso_service_detail(conn); // [V2G2-547]
    } else if (exi_in->V2G_Message.Body.PaymentServiceSelectionReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling PaymentServiceSelectionReq");
        conn->ctx->current_v2g_msg = V2G_PAYMENT_SERVICE_SELECTION_MSG;
        exi_out->V2G_Message.Body.PaymentServiceSelectionRes_isUsed = 1u;
        init_iso2_PaymentServiceSelectionResType(&exi_out->V2G_Message.Body.PaymentServiceSelectionRes);
        next_v2g_event = handle_iso_payment_service_selection(conn); // [V2G2-550]
    } else if (exi_in->V2G_Message.Body.PaymentDetailsReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling PaymentDetailsReq");
        conn->ctx->current_v2g_msg = V2G_PAYMENT_DETAILS_MSG;
        exi_out->V2G_Message.Body.PaymentDetailsRes_isUsed = 1u;
        init_iso2_PaymentDetailsResType(&exi_out->V2G_Message.Body.PaymentDetailsRes);
        next_v2g_event = handle_iso_payment_details(conn); // [V2G2-559]
    } else if (exi_in->V2G_Message.Body.AuthorizationReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling AuthorizationReq");
        conn->ctx->current_v2g_msg = V2G_AUTHORIZATION_MSG;
        if (conn->ctx->last_v2g_msg != V2G_AUTHORIZATION_MSG) {
            if (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_ExternalPayment) {
                conn->ctx->p_charger->publish_require_auth_eim(nullptr);
            }
        }
        exi_out->V2G_Message.Body.AuthorizationRes_isUsed = 1u;
        init_iso2_AuthorizationResType(&exi_out->V2G_Message.Body.AuthorizationRes);
        next_v2g_event = handle_iso_authorization(conn); // [V2G2-562]
    } else if (exi_in->V2G_Message.Body.ChargeParameterDiscoveryReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling ChargeParameterDiscoveryReq");
        conn->ctx->current_v2g_msg = V2G_CHARGE_PARAMETER_DISCOVERY_MSG;
        if (conn->ctx->last_v2g_msg == V2G_AUTHORIZATION_MSG) {
            dlog(DLOG_LEVEL_INFO, "Parameter-phase started");
        }
        exi_out->V2G_Message.Body.ChargeParameterDiscoveryRes_isUsed = 1u;
        init_iso2_ChargeParameterDiscoveryResType(&exi_out->V2G_Message.Body.ChargeParameterDiscoveryRes);
        next_v2g_event = handle_iso_charge_parameter_discovery(conn); // [V2G2-565]
    } else if (exi_in->V2G_Message.Body.PowerDeliveryReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling PowerDeliveryReq");
        conn->ctx->current_v2g_msg = V2G_POWER_DELIVERY_MSG;
        exi_out->V2G_Message.Body.PowerDeliveryRes_isUsed = 1u;
        init_iso2_PowerDeliveryResType(&exi_out->V2G_Message.Body.PowerDeliveryRes);
        next_v2g_event = handle_iso_power_delivery(conn); // [V2G2-589]
    } else if (exi_in->V2G_Message.Body.ChargingStatusReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling ChargingStatusReq");
        conn->ctx->current_v2g_msg = V2G_CHARGING_STATUS_MSG;

        exi_out->V2G_Message.Body.ChargingStatusRes_isUsed = 1u;
        init_iso2_ChargingStatusResType(&exi_out->V2G_Message.Body.ChargingStatusRes);
        next_v2g_event = handle_iso_charging_status(conn);
    } else if (exi_in->V2G_Message.Body.MeteringReceiptReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling MeteringReceiptReq");
        conn->ctx->current_v2g_msg = V2G_METERING_RECEIPT_MSG;
        exi_out->V2G_Message.Body.MeteringReceiptRes_isUsed = 1u;
        init_iso2_MeteringReceiptResType(&exi_out->V2G_Message.Body.MeteringReceiptRes);
        next_v2g_event = handle_iso_metering_receipt(conn); // [V2G2-796]
    } else if (exi_in->V2G_Message.Body.CertificateUpdateReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling CertificateUpdateReq");
        conn->ctx->current_v2g_msg = V2G_CERTIFICATE_UPDATE_MSG;

        exi_out->V2G_Message.Body.CertificateUpdateRes_isUsed = 1u;
        init_iso2_CertificateUpdateResType(&exi_out->V2G_Message.Body.CertificateUpdateRes);
        next_v2g_event = handle_iso_certificate_update(conn); // [V2G2-556]
    } else if (exi_in->V2G_Message.Body.CertificateInstallationReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling CertificateInstallationReq");
        conn->ctx->current_v2g_msg = V2G_CERTIFICATE_INSTALLATION_MSG;
        dlog(DLOG_LEVEL_INFO, "CertificateInstallation-phase started");

        exi_out->V2G_Message.Body.CertificateInstallationRes_isUsed = 1u;
        init_iso2_CertificateInstallationResType(&exi_out->V2G_Message.Body.CertificateInstallationRes);
        next_v2g_event = handle_iso_certificate_installation(conn); // [V2G2-553]
    } else if (exi_in->V2G_Message.Body.CableCheckReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling CableCheckReq");
        conn->ctx->current_v2g_msg = V2G_CABLE_CHECK_MSG;
        /* At first send mqtt charging phase signal to the customer interface */
        if (V2G_CHARGE_PARAMETER_DISCOVERY_MSG == conn->ctx->last_v2g_msg) {
            conn->ctx->p_charger->publish_start_cable_check(nullptr);
        }

        exi_out->V2G_Message.Body.CableCheckRes_isUsed = 1u;
        init_iso2_CableCheckResType(&exi_out->V2G_Message.Body.CableCheckRes);
        next_v2g_event = handle_iso_cable_check(conn); // [V2G2-583
    } else if (exi_in->V2G_Message.Body.PreChargeReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling PreChargeReq");
        conn->ctx->current_v2g_msg = V2G_PRE_CHARGE_MSG;
        /* At first send  mqtt charging phase signal to the customer interface */
        if (conn->ctx->last_v2g_msg == V2G_CABLE_CHECK_MSG) {
            conn->ctx->p_charger->publish_start_pre_charge(nullptr);
            dlog(DLOG_LEVEL_INFO, "Precharge-phase started");
        }

        exi_out->V2G_Message.Body.PreChargeRes_isUsed = 1u;
        init_iso2_PreChargeResType(&exi_out->V2G_Message.Body.PreChargeRes);
        next_v2g_event = handle_iso_pre_charge(conn); // [V2G2-586]
    } else if (exi_in->V2G_Message.Body.WeldingDetectionReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling WeldingDetectionReq");
        conn->ctx->current_v2g_msg = V2G_WELDING_DETECTION_MSG;
        if (conn->ctx->last_v2g_msg != V2G_WELDING_DETECTION_MSG) {
            dlog(DLOG_LEVEL_INFO, "Welding-phase started");
        }
        exi_out->V2G_Message.Body.WeldingDetectionRes_isUsed = 1u;
        init_iso2_WeldingDetectionResType(&exi_out->V2G_Message.Body.WeldingDetectionRes);
        next_v2g_event = handle_iso_welding_detection(conn); // [V2G2-596]
    } else if (exi_in->V2G_Message.Body.SessionStopReq_isUsed) {
        dlog(DLOG_LEVEL_TRACE, "Handling SessionStopReq");
        conn->ctx->current_v2g_msg = V2G_SESSION_STOP_MSG;
        exi_out->V2G_Message.Body.SessionStopRes_isUsed = 1u;
        init_iso2_SessionStopResType(&exi_out->V2G_Message.Body.SessionStopRes);
        next_v2g_event = handle_iso_session_stop(conn); // [V2G2-570]
    } else {
        dlog(DLOG_LEVEL_ERROR, "create_response_message: request type not found");
        next_v2g_event = V2G_EVENT_IGNORE_MSG;
    }
    dlog(DLOG_LEVEL_TRACE, "Current state: %s",
         conn->ctx->is_dc_charger ? iso_dc_states[conn->ctx->state].description
                                  : iso_ac_states[conn->ctx->state].description);

    // If next_v2g_event == V2G_EVENT_IGNORE_MSG, keep the current state and ignore msg
    if (next_v2g_event != V2G_EVENT_IGNORE_MSG) {
        conn->ctx->last_v2g_msg = conn->ctx->current_v2g_msg;

        /* Configure session id */
        memcpy(exi_out->V2G_Message.Header.SessionID.bytes, &conn->ctx->evse_v2g_data.session_id,
               iso2_sessionIDType_BYTES_SIZE);

        /* We always set bytesLen to iso2_MessageHeaderType_SessionID_BYTES_SIZE */
        exi_out->V2G_Message.Header.SessionID.bytesLen = iso2_sessionIDType_BYTES_SIZE;
    }

    return next_v2g_event;
}
