Commit c2c583c3 authored by gauthier's avatar gauthier

Paging in progress (last step to do in SPGW-U)

parent 6a06fd2e
......@@ -29,6 +29,8 @@
#ifndef FILE_ENDPOINT_HPP_SEEN
#define FILE_ENDPOINT_HPP_SEEN
#include "conversions.hpp"
#include <arpa/inet.h>
#include <inttypes.h>
#include <sys/socket.h>
......@@ -47,10 +49,10 @@ public :
addr_in->sin_family = AF_INET;
addr_in->sin_port = htons(port);
addr_in->sin_addr.s_addr = addr.s_addr;
addr_storage_len = sizeof(struct sockaddr_in);
};
endpoint(const struct in6_addr& addr6, const uint16_t port)
{
struct sockaddr_in6 * addr_in6 = (struct sockaddr_in6 *)&addr_storage;
......@@ -59,20 +61,36 @@ public :
addr_in6->sin6_flowinfo = 0;
memcpy(&addr_in6->sin6_addr, &addr6, sizeof(struct in6_addr));
addr_in6->sin6_scope_id = 0;
addr_storage_len = sizeof(struct sockaddr_in6);
};
uint16_t port() const
uint16_t port() const
{
return ntohs(((struct sockaddr_in *)&addr_storage)->sin_port);
}
sa_family_t family() const
sa_family_t family() const
{
return addr_storage.ss_family;
}
std::string toString() const
{
std::string str;
if (addr_storage.ss_family == AF_INET) {
struct sockaddr_in * addr_in = (struct sockaddr_in *)&addr_storage;
str.append(conv::toString(addr_in->sin_addr));
str.append(":").append(std::to_string(ntohs(addr_in->sin_port)));
}
else if (addr_storage.ss_family == AF_INET6) {
struct sockaddr_in6 * addr_in6 = (struct sockaddr_in6 *)&addr_storage;
str.append(conv::toString(addr_in6->sin6_addr));
str.append(":").append(std::to_string(ntohs(addr_in6->sin6_port)));
}
return str;
}
};
#endif
\ No newline at end of file
#endif
......@@ -368,6 +368,11 @@ public:
itti_s5s8_msg(i, orig, dest) {
gtp_ies = i.gtp_ies;
}
itti_s5s8_downlink_data_notification_acknowledge(const gtpv2c::gtpv2c_downlink_data_notification_acknowledge& ies, const task_id_t orig, const task_id_t dest) :
itti_s5s8_downlink_data_notification_acknowledge(orig, dest) {
gtp_ies = ies;
}
const char* get_msg_name() {return typeid(itti_s5s8_downlink_data_notification_acknowledge).name();};
gtpv2c::gtpv2c_downlink_data_notification_acknowledge gtp_ies;
......
......@@ -846,6 +846,15 @@ gtpv2c_msg::gtpv2c_msg(const gtpv2c_downlink_data_notification& gtp_ies) : gtpv2
if (gtp_ies.ie_presence_mask & DOWNLINK_DATA_NOTIFICATION_PR_IE_SENDER_FTEID_FOR_CP) {std::shared_ptr<gtpv2c_fully_qualified_teid_ie> sie(new gtpv2c_fully_qualified_teid_ie(gtp_ies.sender_fteid_for_cp)); add_ie(sie);}
if (gtp_ies.ie_presence_mask & DOWNLINK_DATA_NOTIFICATION_PR_IE_INDICATION_FLAGS) {std::shared_ptr<gtpv2c_indication_ie> sie(new gtpv2c_indication_ie(gtp_ies.indication_flags)); add_ie(sie);}
}
//------------------------------------------------------------------------------
gtpv2c_msg::gtpv2c_msg(const gtpv2c_downlink_data_notification_acknowledge& gtp_ies) : gtpv2c_msg_header() {
ies = {};
set_message_type(GTP_DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE);
if (gtp_ies.ie_presence_mask & DOWNLINK_DATA_NOTIFICATION_ACK_PR_IE_CAUSE) {std::shared_ptr<gtpv2c_cause_ie> sie(new gtpv2c_cause_ie(gtp_ies.cause)); add_ie(sie);}
if (gtp_ies.ie_presence_mask & DOWNLINK_DATA_NOTIFICATION_ACK_PR_IE_IMSI) {std::shared_ptr<gtpv2c_imsi_ie> sie(new gtpv2c_imsi_ie(gtp_ies.imsi)); add_ie(sie);}
}
//------------------------------------------------------------------------------
gtpv2c_msg::gtpv2c_msg(const gtpv2c_echo_request& gtp_ies) : gtpv2c_msg_header() {
ies = {};
......
......@@ -355,6 +355,7 @@ public:
explicit gtpv2c_msg(const gtpv2c_release_access_bearers_request& gtp_ies);
explicit gtpv2c_msg(const gtpv2c_release_access_bearers_response& gtp_ies);
explicit gtpv2c_msg(const gtpv2c_downlink_data_notification& gtp_ies);
explicit gtpv2c_msg(const gtpv2c_downlink_data_notification_acknowledge& gtp_ies);
~gtpv2c_msg() {
ies.clear();
......
......@@ -587,6 +587,34 @@ void gtpv2c_stack::send_triggered_message(const endpoint& r_endpoint, const teid
Logger::gtpv2_c().error( "Sending %s, gtp_tx_id " PROC_ID_FMT " proc not found, discarded!", gtp_ies.get_msg_name(), gtp_tx_id);
}
}
//------------------------------------------------------------------------------
void gtpv2c_stack::send_triggered_message(const endpoint& r_endpoint, const teid_t teid, const gtpv2c_downlink_data_notification_acknowledge& gtp_ies, const uint64_t gtp_tx_id, const gtpv2c_transaction_action& a)
{
std::map<uint64_t , uint32_t>::iterator it;
it = gtpc_tx_id2seq_num.find(gtp_tx_id);
if (it != gtpc_tx_id2seq_num.end()) {
std::ostringstream oss(std::ostringstream::binary);
gtpv2c_msg msg(gtp_ies);
msg.set_teid(teid);
msg.set_sequence_number(it->second);
msg.dump_to(oss);
std::string bstream = oss.str();
Logger::gtpv2_c().trace( "Sending %s, seq %d, teid " TEID_FMT ", proc " PROC_ID_FMT "", gtp_ies.get_msg_name(), msg.get_sequence_number(), msg.get_teid(), gtp_tx_id);
udp_s.async_send_to(reinterpret_cast<const char*>(bstream.c_str()), bstream.length(), r_endpoint);
if (a == DELETE_TX) {
std::map<uint32_t , gtpv2c_procedure>::iterator it_proc = pending_procedures.find(it->second);
if (it_proc != pending_procedures.end()) {
stop_proc_cleanup_timer(it_proc->second);
pending_procedures.erase(it_proc);
}
gtpc_tx_id2seq_num.erase(it);
}
} else {
Logger::gtpv2_c().error( "Sending %s, gtp_tx_id " PROC_ID_FMT " proc not found, discarded!", gtp_ies.get_msg_name(), gtp_tx_id);
}
}
//------------------------------------------------------------------------------
void gtpv2c_stack::notify_ul_error(const gtpv2c_procedure& p, const cause_value_e cause)
{
......
......@@ -146,6 +146,7 @@ public:
virtual void send_triggered_message(const endpoint& r_endpoint, const teid_t teid, const gtpv2c_delete_session_response& gtp_ies, const uint64_t gtp_tx_id, const gtpv2c_transaction_action& a = DELETE_TX);
virtual void send_triggered_message(const endpoint& r_endpoint, const teid_t teid, const gtpv2c_modify_bearer_response& gtp_ies, const uint64_t gtp_tx_id, const gtpv2c_transaction_action& a = DELETE_TX);
virtual void send_triggered_message(const endpoint& r_endpoint, const teid_t teid, const gtpv2c_release_access_bearers_response& gtp_ies, const uint64_t gtp_tx_id, const gtpv2c_transaction_action& a = DELETE_TX);
virtual void send_triggered_message(const endpoint& r_endpoint, const teid_t teid, const gtpv2c_downlink_data_notification_acknowledge& gtp_ies, const uint64_t gtp_tx_id, const gtpv2c_transaction_action& a = DELETE_TX);
void time_out_event(const uint32_t timer_id, const task_id_t& task_id, bool &error);
......
......@@ -3575,6 +3575,9 @@ public:
// Private Extension ///< optional
void set(const cause_t& v, const uint8_t instance = 0) {cause = v;ie_presence_mask |= DOWNLINK_DATA_NOTIFICATION_ACK_PR_IE_CAUSE;}
void set(const imsi_t& v, const uint8_t instance = 0) {imsi = v;ie_presence_mask |= DOWNLINK_DATA_NOTIFICATION_ACK_PR_IE_IMSI;}
bool get(cause_t& v, const uint8_t instance = 0) const {if (ie_presence_mask & DOWNLINK_DATA_NOTIFICATION_ACK_PR_IE_CAUSE) {v = cause;return true;}return false;}
bool get(imsi_t& v, const uint8_t instance = 0) const {if (ie_presence_mask & DOWNLINK_DATA_NOTIFICATION_ACK_PR_IE_IMSI) {v = imsi;return true;}return false;}
} ;
......
......@@ -1221,7 +1221,7 @@ pfcp_msg::pfcp_msg(const pfcp_session_report_request& pfcp_ies) : pfcp_msg_heade
//------------------------------------------------------------------------------
pfcp_msg::pfcp_msg(const pfcp_session_report_response& pfcp_ies) : pfcp_msg_header() {
ies = {};
set_message_type(PFCP_SESSION_REPORT_REQUEST);
set_message_type(PFCP_SESSION_REPORT_RESPONSE);
if (pfcp_ies.cause.first) {std::shared_ptr<pfcp_cause_ie> sie(new pfcp_cause_ie(pfcp_ies.cause.second)); add_ie(sie);}
if (pfcp_ies.offending_ie.first) {std::shared_ptr<pfcp_offending_ie_ie> sie(new pfcp_offending_ie_ie(pfcp_ies.offending_ie.second)); add_ie(sie);}
......
......@@ -650,6 +650,33 @@ void pfcp_l4_stack::send_response(const endpoint& dest, const uint64_t seid, con
Logger::pfcp().error( "Sending %s, trxn_id %ld proc not found, discarded!", pfcp_ies.get_msg_name(), trxn_id);
}
}
//------------------------------------------------------------------------------
void pfcp_l4_stack::send_response(const endpoint& dest, const uint64_t seid, const pfcp_session_report_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a)
{
std::map<uint64_t , uint32_t>::iterator it;
it = trxn_id2seq_num.find(trxn_id);
if (it != trxn_id2seq_num.end()) {
std::ostringstream oss(std::ostringstream::binary);
pfcp_msg msg(pfcp_ies);
msg.set_seid(seid);
msg.set_sequence_number(it->second);
msg.dump_to(oss);
std::string bstream = oss.str();
Logger::pfcp().trace( "Sending %s, seq %d seid " SEID_FMT " to %s", pfcp_ies.get_msg_name(), msg.get_sequence_number(), seid, dest.toString().c_str());
udp_s_8805.async_send_to(reinterpret_cast<const char*>(bstream.c_str()), bstream.length(), dest);
if (a == DELETE_TX) {
std::map<uint32_t , pfcp_procedure>::iterator it_proc = pending_procedures.find(it->second);
if (it_proc != pending_procedures.end()) {
stop_proc_cleanup_timer(it_proc->second);
pending_procedures.erase(it_proc);
}
trxn_id2seq_num.erase(it);
}
} else {
Logger::pfcp().error( "Sending %s, trxn_id %ld proc not found, discarded!", pfcp_ies.get_msg_name(), trxn_id);
}
}
//------------------------------------------------------------------------------
void pfcp_l4_stack::notify_ul_error(const pfcp_procedure& p, const ::cause_value_e cause)
......
......@@ -147,6 +147,7 @@ public:
virtual void send_response(const endpoint& dest, const uint64_t seid, const pfcp_session_establishment_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);
virtual void send_response(const endpoint& dest, const uint64_t seid, const pfcp_session_modification_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);
virtual void send_response(const endpoint& dest, const uint64_t seid, const pfcp_session_deletion_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);
virtual void send_response(const endpoint& dest, const uint64_t seid, const pfcp_session_report_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);
void time_out_event(const uint32_t timer_id, const task_id_t& task_id, bool &error);
};
......
......@@ -243,6 +243,12 @@ void pgw_app_task (void*)
pgw_app_inst->handle_itti_msg(std::static_pointer_cast<itti_s5s8_release_access_bearers_request>(shared_msg));
break;
case S5S8_DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE:
if (itti_s5s8_downlink_data_notification_acknowledge* m = dynamic_cast<itti_s5s8_downlink_data_notification_acknowledge*>(msg)) {
pgw_app_inst->handle_itti_msg(std::ref(*m));
}
break;
case TIME_OUT:
if (itti_msg_timeout* to = dynamic_cast<itti_msg_timeout*>(msg)) {
Logger::pgwc_app().info( "TIME-OUT event timer id %d", to->timer_id);
......@@ -388,7 +394,7 @@ void pgw_app::send_release_access_bearers_response_cause_request_accepted(const
void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_create_session_request> scsreq)
{
itti_s5s8_create_session_request* csreq = scsreq.get();
Logger::pgwc_app().debug("Received S5S8 CREATE_SESSION_REQUEST teid " TEID_FMT " gtpc_tx_id %" PRIX64" ", csreq->teid, csreq->gtpc_tx_id);
Logger::pgwc_app().debug("Received S5S8 CREATE_SESSION_REQUEST teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " ", csreq->teid, csreq->gtpc_tx_id);
if (csreq->gtp_ies.rat_type.rat_type < RAT_TYPE_E_EUTRAN_WB_EUTRAN) {
Logger::pgwc_app().warn("Received S5_S8 CREATE_SESSION_REQUEST with RAT != RAT_TYPE_E_EUTRAN_WB_EUTRAN: type %d", csreq->gtp_ies.rat_type);
......@@ -422,7 +428,7 @@ void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_create_session_request>
// imsi not authenticated
indication_t indication = {};
if ((csreq->gtp_ies.get(indication)) && (indication.uimsi)){
Logger::pgwc_app().debug("TODO S5_S8 CREATE_SESSION_REQUEST (no AUTHENTICATED IMSI) teid " TEID_FMT " gtpc_tx_id %" PRIX64" ", csreq->teid, csreq->gtpc_tx_id);
Logger::pgwc_app().debug("TODO S5_S8 CREATE_SESSION_REQUEST (no AUTHENTICATED IMSI) teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " ", csreq->teid, csreq->gtpc_tx_id);
return;
} else {
imsi64_t imsi64 = imsi.to_imsi64();
......@@ -440,12 +446,12 @@ void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_create_session_request>
if (is_s5s8c_teid_exist(csreq->teid)) {
pc = s5s8cpgw_fteid_2_pgw_context(l_fteid);
} else {
Logger::pgwc_app().debug("Discarding S5_S8 CREATE_SESSION_REQUEST teid " TEID_FMT " gtpc_tx_id %" PRIX64", invalid teid", csreq->teid, csreq->gtpc_tx_id);
Logger::pgwc_app().debug("Discarding S5_S8 CREATE_SESSION_REQUEST teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT ", invalid teid", csreq->teid, csreq->gtpc_tx_id);
return;
}
} else {
// TODO
Logger::pgwc_app().debug("TODO S5_S8 CREATE_SESSION_REQUEST (no IMSI) teid " TEID_FMT " gtpc_tx_id %" PRIX64" ", csreq->teid, csreq->gtpc_tx_id);
Logger::pgwc_app().debug("TODO S5_S8 CREATE_SESSION_REQUEST (no IMSI) teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " ", csreq->teid, csreq->gtpc_tx_id);
return;
}
}
......@@ -455,7 +461,7 @@ void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_create_session_request>
void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_modify_bearer_request> smbreq)
{
itti_s5s8_modify_bearer_request* mbreq = smbreq.get();
Logger::pgwc_app().debug("Received S5S8 MODIFY_BEARER_REQUEST teid " TEID_FMT " gtpc_tx_id %" PRIX64" ", mbreq->teid, mbreq->gtpc_tx_id);
Logger::pgwc_app().debug("Received S5S8 MODIFY_BEARER_REQUEST teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " ", mbreq->teid, mbreq->gtpc_tx_id);
fteid_t sender_fteid = {};
bool sender_fteid_present = mbreq->gtp_ies.get(sender_fteid);
......@@ -489,7 +495,7 @@ void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_modify_bearer_request>
//------------------------------------------------------------------------------
void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_release_access_bearers_request> smbreq)
{
Logger::pgwc_app().debug("Received S5S8 RELEASE_ACCESS_BEARERS_REQUEST teid " TEID_FMT " gtpc_tx_id %" PRIX64" ", smbreq->teid, smbreq->gtpc_tx_id);
Logger::pgwc_app().debug("Received S5S8 RELEASE_ACCESS_BEARERS_REQUEST teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " ", smbreq->teid, smbreq->gtpc_tx_id);
fteid_t l_fteid = build_s5s8_cp_fteid(pgw_cfg.s5s8_cp.addr4, smbreq->teid);
std::shared_ptr<pgw_context> pc = s5s8cpgw_fteid_2_pgw_context(l_fteid);
......@@ -505,7 +511,7 @@ void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_release_access_bearers_
void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_delete_session_request> sdsreq)
{
itti_s5s8_delete_session_request* dsreq = sdsreq.get();
Logger::pgwc_app().debug("Received S5S8 DELETE_SESSION_REQUEST teid " TEID_FMT " gtpc_tx_id %" PRIX64" ", dsreq->teid, dsreq->gtpc_tx_id);
Logger::pgwc_app().debug("Received S5S8 DELETE_SESSION_REQUEST teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " ", dsreq->teid, dsreq->gtpc_tx_id);
fteid_t sender_fteid = {};
bool sender_fteid_present = dsreq->gtp_ies.get(sender_fteid);
......@@ -541,6 +547,19 @@ void pgw_app::handle_itti_msg (std::shared_ptr<itti_s5s8_delete_session_request>
}
}
//------------------------------------------------------------------------------
void pgw_app::handle_itti_msg (itti_s5s8_downlink_data_notification_acknowledge& m)
{
fteid_t l_fteid = build_s5s8_cp_fteid(pgw_cfg.s5s8_cp.addr4, m.teid);
std::shared_ptr<pgw_context> pc = s5s8cpgw_fteid_2_pgw_context(l_fteid);
if (pc.get()) {
Logger::pgwc_app().debug("Received S5S8 DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE sender teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " ", m.teid, m.gtpc_tx_id);
pc.get()->handle_itti_msg(m);
} else {
Logger::pgwc_app().debug("Received S5S8 DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE sender teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " context not found!", m.teid, m.gtpc_tx_id);
}
}
//------------------------------------------------------------------------------
void pgw_app::handle_itti_msg (itti_sxab_session_establishment_response& seresp)
{
......
......@@ -124,6 +124,7 @@ public:
void handle_itti_msg (std::shared_ptr<itti_s5s8_delete_session_request> m);
void handle_itti_msg (std::shared_ptr<itti_s5s8_modify_bearer_request> m);
void handle_itti_msg (std::shared_ptr<itti_s5s8_release_access_bearers_request> m);
void handle_itti_msg (itti_s5s8_downlink_data_notification_acknowledge& m);
void handle_itti_msg (itti_sxab_session_establishment_response& m);
void handle_itti_msg (itti_sxab_session_modification_response& m);
void handle_itti_msg (itti_sxab_session_deletion_response& m);
......
......@@ -780,6 +780,20 @@ void pgw_context::handle_itti_msg (std::shared_ptr<itti_s5s8_delete_session_requ
std::cout << toString() << std::endl;
}
//------------------------------------------------------------------------------
void pgw_context::handle_itti_msg (itti_s5s8_downlink_data_notification_acknowledge& ack)
{
std::shared_ptr<pgw_procedure> proc = {};
std::shared_lock<std::shared_mutex> lp;
if (find_procedure(ack.gtpc_tx_id, proc, lp)) {
Logger::pgwc_app().debug("Received S5S8 DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE sender teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " ", ack.teid, ack.gtpc_tx_id);
proc->handle_itti_msg(ack);
lp.unlock();
remove_procedure(proc.get());
} else {
Logger::pgwc_app().debug("Received S5S8 DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE sender teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT ", pgw_procedure not found, discarded!", ack.teid, ack.gtpc_tx_id);
}
}
//------------------------------------------------------------------------------
void pgw_context::handle_itti_msg (std::shared_ptr<itti_s5s8_modify_bearer_request> s5_trigger)
......@@ -888,12 +902,12 @@ void pgw_context::handle_itti_msg (itti_sxab_session_establishment_response& ser
std::shared_ptr<pgw_procedure> proc = {};
std::shared_lock<std::shared_mutex> lp;
if (find_procedure(seresp.trxn_id, proc, lp)) {
Logger::pgwc_app().debug("Received SXAB SESSION ESTABLISHMENT RESPONSE sender teid %" PRIX64 " pfcp_tx_id %" PRIX64"\n", seresp.seid, seresp.trxn_id);
Logger::pgwc_app().debug("Received SXAB SESSION ESTABLISHMENT RESPONSE sender teid " TEID_FMT " pfcp_tx_id %" PRIX64"\n", seresp.seid, seresp.trxn_id);
proc->handle_itti_msg(seresp);
lp.unlock();
remove_procedure(proc.get());
} else {
Logger::pgwc_app().debug("Received SXAB SESSION ESTABLISHMENT RESPONSE sender teid %" PRIX64 " pfcp_tx_id %" PRIX64", pgw_procedure not found, discarded!", seresp.seid, seresp.trxn_id);
Logger::pgwc_app().debug("Received SXAB SESSION ESTABLISHMENT RESPONSE sender teid " TEID_FMT " pfcp_tx_id %" PRIX64", pgw_procedure not found, discarded!", seresp.seid, seresp.trxn_id);
}
}
//------------------------------------------------------------------------------
......@@ -902,12 +916,12 @@ void pgw_context::handle_itti_msg (itti_sxab_session_modification_response& smre
std::shared_ptr<pgw_procedure> proc = {};
std::shared_lock<std::shared_mutex> lp;
if (find_procedure(smresp.trxn_id, proc, lp)) {
Logger::pgwc_app().debug("Received SXAB SESSION MODIFICATION RESPONSE sender teid %" PRIX64 " pfcp_tx_id %" PRIX64"\n", smresp.seid, smresp.trxn_id);
Logger::pgwc_app().debug("Received SXAB SESSION MODIFICATION RESPONSE sender teid " TEID_FMT " pfcp_tx_id %" PRIX64"\n", smresp.seid, smresp.trxn_id);
proc->handle_itti_msg(smresp);
lp.unlock();
remove_procedure(proc.get());
} else {
Logger::pgwc_app().debug("Received SXAB SESSION MODIFICATION RESPONSE sender teid %" PRIX64 " pfcp_tx_id %" PRIX64", pgw_procedure not found, discarded!", smresp.seid, smresp.trxn_id);
Logger::pgwc_app().debug("Received SXAB SESSION MODIFICATION RESPONSE sender teid " TEID_FMT " pfcp_tx_id %" PRIX64", pgw_procedure not found, discarded!", smresp.seid, smresp.trxn_id);
}
std::cout << toString() << std::endl;
}
......@@ -917,12 +931,12 @@ void pgw_context::handle_itti_msg (itti_sxab_session_deletion_response& sdresp)
std::shared_ptr<pgw_procedure> proc = {};
std::shared_lock<std::shared_mutex> lp;
if (find_procedure(sdresp.trxn_id, proc, lp)) {
Logger::pgwc_app().debug("Received SXAB SESSION DELETION RESPONSE sender teid %" PRIX64 " pfcp_tx_id %" PRIX64"\n", sdresp.seid, sdresp.trxn_id);
Logger::pgwc_app().debug("Received SXAB SESSION DELETION RESPONSE sender teid " TEID_FMT " pfcp_tx_id %" PRIX64"\n", sdresp.seid, sdresp.trxn_id);
proc->handle_itti_msg(sdresp);
lp.unlock();
remove_procedure(proc.get());
} else {
Logger::pgwc_app().debug("Received SXAB SESSION MODIFICATION RESPONSE sender teid %" PRIX64 " pfcp_tx_id %" PRIX64", pgw_procedure not found, discarded!", sdresp.seid, sdresp.trxn_id);
Logger::pgwc_app().debug("Received SXAB SESSION MODIFICATION RESPONSE sender teid " TEID_FMT " pfcp_tx_id %" PRIX64", pgw_procedure not found, discarded!", sdresp.seid, sdresp.trxn_id);
}
std::cout << toString() << std::endl;
}
......
......@@ -306,6 +306,7 @@ public:
void handle_itti_msg (std::shared_ptr<itti_s5s8_delete_session_request> s5_trigger);
void handle_itti_msg (std::shared_ptr<itti_s5s8_modify_bearer_request> s5_trigger);
void handle_itti_msg (std::shared_ptr<itti_s5s8_release_access_bearers_request> s5_trigger);
void handle_itti_msg (itti_s5s8_downlink_data_notification_acknowledge& );
void handle_itti_msg (itti_sxab_session_establishment_response& );
void handle_itti_msg (itti_sxab_session_modification_response& );
void handle_itti_msg (itti_sxab_session_deletion_response& );
......
......@@ -235,6 +235,30 @@ void pgw_s5s8::handle_receive_release_access_bearers_request(gtpv2c_msg& msg, co
}
// else ignore
}
//------------------------------------------------------------------------------
void pgw_s5s8::handle_receive_downlink_data_notification_acknowledge(gtpv2c_msg& msg, const endpoint& remote_endpoint)
{
bool error = true;
uint64_t gtpc_tx_id = 0;
gtpv2c_downlink_data_notification_acknowledge msg_ies_container = {};
msg.to_core_type(msg_ies_container);
handle_receive_message_cb(msg, remote_endpoint, TASK_SGWC_S11, error, gtpc_tx_id);
if (!error) {
itti_s5s8_downlink_data_notification_acknowledge *itti_msg = new itti_s5s8_downlink_data_notification_acknowledge(TASK_PGWC_S5S8, TASK_PGWC_APP);
itti_msg->gtp_ies = msg_ies_container;
itti_msg->r_endpoint = remote_endpoint;
itti_msg->gtpc_tx_id = gtpc_tx_id;
itti_msg->teid = msg.get_teid();
std::shared_ptr<itti_s5s8_downlink_data_notification_acknowledge> i = std::shared_ptr<itti_s5s8_downlink_data_notification_acknowledge>(itti_msg);
int ret = itti_inst->send_msg(i);
if (RETURNok != ret) {
Logger::pgwc_s5s8().error( "Could not send ITTI message %s to task TASK_PGWC_APP", i->get_msg_name());
}
}
// else ignore
}
//------------------------------------------------------------------------------
void pgw_s5s8::handle_receive_gtpv2c_msg(gtpv2c_msg& msg, const endpoint& remote_endpoint)
{
......@@ -259,6 +283,10 @@ void pgw_s5s8::handle_receive_gtpv2c_msg(gtpv2c_msg& msg, const endpoint& remote
handle_receive_delete_session_request(msg, remote_endpoint);
}
break;
case GTP_DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE: {
handle_receive_downlink_data_notification_acknowledge(msg, remote_endpoint);
}
break;
case GTP_DELETE_SESSION_RESPONSE:
case GTP_CHANGE_NOTIFICATION_REQUEST:
case GTP_CHANGE_NOTIFICATION_RESPONSE:
......@@ -327,7 +355,6 @@ void pgw_s5s8::handle_receive_gtpv2c_msg(gtpv2c_msg& msg, const endpoint& remote
case GTP_RELEASE_ACCESS_BEARERS_RESPONSE:
case GTP_DOWNLINK_DATA_NOTIFICATION:
case GTP_DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE:
case GTP_PGW_RESTART_NOTIFICATION:
case GTP_PGW_RESTART_NOTIFICATION_ACKNOWLEDGE:
case GTP_UPDATE_PDN_CONNECTION_SET_REQUEST:
......
......@@ -45,6 +45,7 @@ private:
void handle_receive_delete_session_request(gtpv2c::gtpv2c_msg& msg, const endpoint& r_endpoint);
void handle_receive_modify_bearer_request(gtpv2c::gtpv2c_msg& msg, const endpoint& r_endpoint);
void handle_receive_release_access_bearers_request(gtpv2c::gtpv2c_msg& msg, const endpoint& r_endpoint);
void handle_receive_downlink_data_notification_acknowledge(gtpv2c::gtpv2c_msg& msg, const endpoint& remote_endpoint);
public:
pgw_s5s8();
......
......@@ -959,3 +959,35 @@ int downlink_data_report_procedure::run(std::shared_ptr<pgwc::pgw_context> conte
return RETURNok;
}
//------------------------------------------------------------------------------
void downlink_data_report_procedure::handle_itti_msg (itti_s5s8_downlink_data_notification_acknowledge& ack)
{
::cause_t gtp_cause = {};
pfcp::cause_t pfcp_cause = {.cause_value = pfcp::CAUSE_VALUE_REQUEST_ACCEPTED};
if (ack.gtp_ies.get(gtp_cause)) {
switch (gtp_cause.cause_value) {
case REQUEST_ACCEPTED:
break;
default:
pfcp_cause.cause_value = CAUSE_VALUE_REQUEST_REJECTED;
}
} else {
Logger::pgwc_app().error( "downlink_data_report_procedure: Could not get cause from itti_s5s8_downlink_data_notification_acknowledge");
return;
}
itti_sxab_session_report_response *sx = new itti_sxab_session_report_response(TASK_PGWC_APP, TASK_PGWC_SX);
sx->seid = ppc->up_fseid.seid;
sx->trxn_id = this->trxn_id;
sx->r_endpoint = endpoint(ppc->up_fseid.ipv4_address, pgw_cfg.sx.port);
std::shared_ptr<itti_sxab_session_report_response> sx_triggered = std::shared_ptr<itti_sxab_session_report_response>(sx);
sx->pfcp_ies.set(pfcp_cause);
int ret = itti_inst->send_msg(sx_triggered);
if (RETURNok != ret) {
Logger::pgwc_app().error( "Could not send ITTI message %s to task TASK_PGWC_SX", sx->pfcp_ies.get_msg_name());
}
}
......@@ -65,6 +65,7 @@ public:
virtual void handle_itti_msg (itti_sxab_session_establishment_response& resp) {}
virtual void handle_itti_msg (itti_sxab_session_modification_response& resp) {}
virtual void handle_itti_msg (itti_sxab_session_deletion_response& resp) {}
virtual void handle_itti_msg (itti_s5s8_downlink_data_notification_acknowledge& resp) {}
};
......@@ -171,7 +172,7 @@ public:
pc(), sx_trigger(req), s5_triggered(), ebi() {}
int run(std::shared_ptr<pgwc::pgw_context> context,
std::shared_ptr<pgwc::pgw_pdn_connection> pdn, const ebi_t& e);
//void handle_itti_msg (itti_sxab_session_report_response& resp);
void handle_itti_msg (itti_s5s8_downlink_data_notification_acknowledge& resp);
void notify_failure_to_peer(const pfcp::cause_t& cause);
//~downlink_data_report_procedure() {}
......
......@@ -143,7 +143,7 @@ void pgwc_sxab_task (void *args_p)
case SXAB_SESSION_REPORT_RESPONSE:
if (itti_sxab_session_report_response* m = dynamic_cast<itti_sxab_session_report_response*>(msg)) {
pgwc_sxab_inst->handle_itti_msg(ref(*m));
pgwc_sxab_inst->send_sx_msg(ref(*m));
}
break;
......@@ -446,6 +446,11 @@ void pgwc_sxab::send_sx_msg(itti_sxab_association_setup_response& i)
send_response(i.r_endpoint, i.pfcp_ies, i.trxn_id);
}
//------------------------------------------------------------------------------
void pgwc_sxab::send_sx_msg(itti_sxab_session_report_response& i)
{
send_response(i.r_endpoint, i.seid, i.pfcp_ies, i.trxn_id);
}
//------------------------------------------------------------------------------
void pgwc_sxab::send_heartbeat_request(std::shared_ptr<pfcp_association>& a)
{
pfcp::pfcp_heartbeat_request h = {};
......
......@@ -85,7 +85,7 @@ public:
void send_sx_msg (itti_sxab_session_establishment_request& s);
void send_sx_msg (itti_sxab_session_modification_request& s);
void send_sx_msg (itti_sxab_session_deletion_request& s);
void send_sx_msg (itti_sxab_session_report_response& s) {};
void send_sx_msg (itti_sxab_session_report_response& s);
void send_heartbeat_request(std::shared_ptr<pfcp_association>& a);
void send_heartbeat_response(const endpoint& r_endpoint, const uint64_t trxn_id);
......
......@@ -238,6 +238,12 @@ void sgwc_app_task (void *args_p)
}
break;
case S11_DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE:
if (itti_s11_downlink_data_notification_acknowledge* m = dynamic_cast<itti_s11_downlink_data_notification_acknowledge*>(msg)) {
sgwc_app_inst->handle_itti_msg(ref(*m));
}
break;
case TIME_OUT:
if (itti_msg_timeout* to = dynamic_cast<itti_msg_timeout*>(msg)) {
Logger::sgwc_app().info( "TIME-OUT event timer id %d", to->timer_id);
......@@ -428,6 +434,23 @@ void sgwc_app::handle_itti_msg (itti_s11_release_access_bearers_request& m)
return;
}
}
//------------------------------------------------------------------------------
void sgwc_app::handle_itti_msg (itti_s11_downlink_data_notification_acknowledge& m)
{
Logger::sgwc_app().debug("Received S11 DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE sender teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT " ", m.teid, m.gtpc_tx_id);
if (m.teid) {
if (is_s11sgw_teid_2_sgw_eps_bearer_context(m.teid)) {
shared_ptr<sgw_eps_bearer_context> ebc = s11sgw_teid_2_sgw_eps_bearer_context(m.teid);
ebc->handle_itti_msg(m);
} else {
Logger::sgwc_app().debug("Discarding S11 DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE sender teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT ", invalid teid", m.teid, m.gtpc_tx_id);
return;
}
} else {
Logger::sgwc_app().debug("Discarding S11 DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE sender teid " TEID_FMT " gtpc_tx_id " PROC_ID_FMT ", invalid teid", m.teid, m.gtpc_tx_id);
}
}
//------------------------------------------------------------------------------
void sgwc_app::handle_itti_msg (itti_s5s8_create_session_response& m)
{
......@@ -524,3 +547,4 @@ void sgwc_app::handle_itti_msg (itti_s5s8_downlink_data_notification& m)
}
}
......@@ -114,6 +114,7 @@ public:
void handle_itti_msg (itti_s11_delete_session_request& m);
void handle_itti_msg (itti_s11_modify_bearer_request& m);
void handle_itti_msg (itti_s11_release_access_bearers_request& m);
void handle_itti_msg (itti_s11_downlink_data_notification_acknowledge& m);
void handle_itti_msg (itti_s5s8_create_session_response& m);
void handle_itti_msg (itti_s5s8_delete_session_response& m);
void handle_itti_msg (itti_s5s8_modify_bearer_response& m);
......
......@@ -320,6 +320,17 @@ void sgw_eps_bearer_context::handle_itti_msg (itti_s11_delete_session_request& d
Logger::sgwc_app().info("S11 DELETE_SESSION_REQUEST TODO delete session locally");
}
}
//------------------------------------------------------------------------------
void sgw_eps_bearer_context::handle_itti_msg (itti_s11_downlink_data_notification_acknowledge& ddn)
{
shared_ptr<sebc_procedure> sp = find_procedure(ddn.gtpc_tx_id);
if (sp.get()) {
sp->handle_itti_msg(ddn);
return;
} else {
Logger::sgwc_app().error("S11 DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE ignored, no procedure found gtpc_tx_id " PROC_ID_FMT "!", ddn.gtpc_tx_id);
}
}
//------------------------------------------------------------------------------
void sgw_eps_bearer_context::handle_itti_msg (itti_s5s8_create_session_response& csresp, std::shared_ptr<sgw_pdn_connection> spc)
......
......@@ -224,6 +224,7 @@ public:
void handle_itti_msg (itti_s11_modify_bearer_request& m);
void handle_itti_msg (itti_s11_release_access_bearers_request& m);
void handle_itti_msg (itti_s11_delete_session_request& m);
void handle_itti_msg (itti_s11_downlink_data_notification_acknowledge& m);
void handle_itti_msg (itti_s5s8_create_session_response& m, std::shared_ptr<sgw_pdn_connection> spc);
void handle_itti_msg (itti_s5s8_delete_session_response& m, std::shared_ptr<sgw_pdn_connection> spc);
void handle_itti_msg (itti_s5s8_modify_bearer_response& m, std::shared_ptr<sgw_pdn_connection> spc);
......
......@@ -59,6 +59,10 @@ void sebc_procedure::handle_itti_msg (itti_s5s8_downlink_data_notification& resp
{
Logger::sgwc_app().error( "Unhandled message itti_s5s8_downlink_data_notification");
}
void sebc_procedure::handle_itti_msg (itti_s11_downlink_data_notification_acknowledge& resp)
{
Logger::sgwc_app().error( "Unhandled message itti_s11_downlink_data_notification_acknowledge");
}
//------------------------------------------------------------------------------
int create_session_request_procedure::run(shared_ptr<sgw_eps_bearer_context> c)
......@@ -404,7 +408,7 @@ int modify_bearer_request_procedure::run(shared_ptr<sgw_eps_bearer_context> c)
if (it.get_s1_u_enb_fteid(v)) {
if (pdn->sgw_pdn_connection::get_eps_bearer(it.eps_bearer_id, sb)) {
if (v == sb->enb_fteid_s1u) {
Logger::pgwc_app().debug( "modify_bearer_procedure: ebi %d enb_fteid_s1u unchanged", it.eps_bearer_id.ebi);
Logger::sgwc_app().debug( "modify_bearer_procedure: ebi %d enb_fteid_s1u unchanged", it.eps_bearer_id.ebi);
bearer_context_modified_within_modify_bearer_response bm = {};
bm.set(it.eps_bearer_id);
cause_t cause = {};
......@@ -853,12 +857,28 @@ int downlink_data_notification_procedure::run(std::shared_ptr<sgw_eps_bearer_con
s11_triggered = std::shared_ptr<itti_s11_downlink_data_notification>(s11);
Logger::pgwc_app().info( "Sending ITTI message %s to task TASK_SGWC_S11", s11->gtp_ies.get_msg_name());
Logger::sgwc_app().info( "Sending ITTI message %s to task TASK_SGWC_S11", s11->gtp_ies.get_msg_name());
int ret = itti_inst->send_msg(s11_triggered);
if (RETURNok != ret) {
Logger::pgwc_app().error( "Could not send ITTI message %s to task TASK_SGWC_S11", s11->gtp_ies.get_msg_name());
Logger::sgwc_app().error( "Could not send ITTI message %s to task TASK_SGWC_S11", s11->gtp_ies.get_msg_name());
return RETURNerror;
}
return RETURNok;
}
}
//------------------------------------------------------------------------------
void downlink_data_notification_procedure::handle_itti_msg (itti_s11_downlink_data_notification_acknowledge& s11resp)
{
itti_s5s8_downlink_data_notification_acknowledge *s5 = new itti_s5s8_downlink_data_notification_acknowledge(s11resp.gtp_ies, TASK_SGWC_APP, TASK_SGWC_S5S8);
s5->teid = pdn_connection->pgw_fteid_s5_s8_cp.teid_gre_key;
s5->gtpc_tx_id = get_trxn_id();
s5->r_endpoint = endpoint(pdn_connection->pgw_fteid_s5_s8_cp.ipv4_address, gtpv2c::default_port);
std::shared_ptr<itti_s5s8_downlink_data_notification_acknowledge> s5_response = std::shared_ptr<itti_s5s8_downlink_data_notification_acknowledge>(s5);
Logger::sgwc_app().info( "Sending ITTI message %s to task TASK_SGWC_S5S8", s5->gtp_ies.get_msg_name());
int ret = itti_inst->send_msg(s5_response);
if (RETURNok != ret) {
Logger::sgwc_app().error( "Could not send ITTI message %s to task TASK_SGWC_S5S8", s5->gtp_ies.get_msg_name());
}
}
......@@ -64,6 +64,7 @@ public:
virtual void handle_itti_msg (itti_s5s8_modify_bearer_response& resp, std::shared_ptr<sgw_eps_bearer_context> ebc, std::shared_ptr<sgw_pdn_connection> spc);
virtual void handle_itti_msg (itti_s5s8_release_access_bearers_response& resp, std::shared_ptr<sgw_eps_bearer_context> ebc, std::shared_ptr<sgw_pdn_connection> spc);
virtual void handle_itti_msg (itti_s5s8_downlink_data_notification& resp, std::shared_ptr<sgw_eps_bearer_context> ebc, std::shared_ptr<sgw_pdn_connection> spc);
virtual void handle_itti_msg (itti_s11_downlink_data_notification_acknowledge& resp);
};
//------------------------------------------------------------------------------
......@@ -172,7 +173,7 @@ public:
s11_triggered(), ebc(), pdn_connection() {}
int run(std::shared_ptr<sgw_eps_bearer_context> context,
std::shared_ptr<sgw_pdn_connection> spc);
//void handle_itti_msg (itti_sxab_session_report_response& resp);
void handle_itti_msg (itti_s11_downlink_data_notification_acknowledge& resp);
//~downlink_data_notification_procedure() {}
......
......@@ -250,6 +250,30 @@ void sgw_s11::handle_receive_release_access_bearers_request(gtpv2c_msg& msg, con
}
// else ignore
}
//------------------------------------------------------------------------------
void sgw_s11::handle_receive_downlink_data_notification_acknowledge(gtpv2c_msg& msg, const endpoint& remote_endpoint)
{
bool error = true;
uint64_t gtpc_tx_id = 0;
gtpv2c_downlink_data_notification_acknowledge msg_ies_container = {};
msg.to_core_type(msg_ies_container);
handle_receive_message_cb(msg, remote_endpoint, TASK_SGWC_S11, error, gtpc_tx_id);
if (!error) {
itti_s11_downlink_data_notification_acknowledge *itti_msg = new itti_s11_downlink_data_notification_acknowledge(TASK_SGWC_S11, TASK_SGWC_APP);
itti_msg->gtp_ies = msg_ies_container;
itti_msg->r_endpoint = remote_endpoint;
itti_msg->gtpc_tx_id = gtpc_tx_id;
itti_msg->teid = msg.get_teid();
std::shared_ptr<itti_s11_downlink_data_notification_acknowledge> i = std::shared_ptr<itti_s11_downlink_data_notification_acknowledge>(itti_msg);
int ret = itti_inst->send_msg(i);
if (RETURNok != ret) {
Logger::sgwc_s11().error( "Could not send ITTI message %s to task TASK_SGWC_APP", i->get_msg_name());
}
}
// else ignore
}
//------------------------------------------------------------------------------
void sgw_s11::handle_receive_echo_request(gtpv2c_msg& msg, const endpoint& remote_endpoint)
{
......@@ -318,6 +342,10 @@ void sgw_s11::handle_receive_gtpv2c_msg(gtpv2c_msg& msg, const endpoint& remote_
handle_receive_release_access_bearers_request(msg, remote_endpoint);
}
break;
case GTP_DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE: {
handle_receive_downlink_data_notification_acknowledge(msg, remote_endpoint);
}
break;
case GTP_ECHO_REQUEST: {
handle_receive_echo_request(msg, remote_endpoint);
}
......@@ -391,7 +419,6 @@ void sgw_s11::handle_receive_gtpv2c_msg(gtpv2c_msg& msg, const endpoint& remote_
case GTP_DELETE_INDIRECT_DATA_FORWARDING_TUNNEL_RESPONSE:
case GTP_RELEASE_ACCESS_BEARERS_RESPONSE:
case GTP_DOWNLINK_DATA_NOTIFICATION:
case GTP_DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE:
case GTP_PGW_RESTART_NOTIFICATION:
case GTP_PGW_RESTART_NOTIFICATION_ACKNOWLEDGE:
case GTP_UPDATE_PDN_CONNECTION_SET_REQUEST:
......
......@@ -47,6 +47,7 @@ private:
void handle_receive_delete_session_request(gtpv2c::gtpv2c_msg& msg, const endpoint& remote_endpoint);
void handle_receive_modify_bearer_request(gtpv2c::gtpv2c_msg& msg, const endpoint& remote_endpoint);
void handle_receive_release_access_bearers_request(gtpv2c::gtpv2c_msg& msg, const endpoint& remote_endpoint);
void handle_receive_downlink_data_notification_acknowledge(gtpv2c::gtpv2c_msg& msg, const endpoint& remote_endpoint);
public:
sgw_s11();
......
......@@ -79,6 +79,12 @@ void sgw_s5s8_task (void *args_p)
}
break;
case S5S8_DOWNLINK_DATA_NOTIFICATION_ACKNOWLEDGE:
if (itti_s5s8_downlink_data_notification_acknowledge* m = dynamic_cast<itti_s5s8_downlink_data_notification_acknowledge*>(msg)) {
sgw_s5s8_inst->send_msg(ref(*m));
}
break;
case TIME_OUT:
if (itti_msg_timeout* to = dynamic_cast<itti_msg_timeout*>(msg)) {
Logger::sgwc_s5s8().debug( "TIME-OUT event timer id %d", to->timer_id);
......@@ -130,6 +136,11 @@ void sgw_s5s8::send_msg(itti_s5s8_release_access_bearers_request& i)
send_initial_message(i.r_endpoint, i.teid, i.gtp_ies, TASK_SGWC_S5S8, i.gtpc_tx_id);
}
//------------------------------------------------------------------------------
void sgw_s5s8::send_msg(itti_s5s8_downlink_data_notification_acknowledge& i)
{
send_triggered_message(i.r_endpoint, i.teid, i.gtp_ies, i.gtpc_tx_id);
}
//------------------------------------------------------------------------------
void sgw_s5s8::handle_receive_create_session_response(gtpv2c_msg& msg, const endpoint& remote_endpoint)
{
bool error = true;
......
......@@ -58,6 +58,7 @@ public:
void send_msg(itti_s5s8_delete_session_request& m);
void send_msg(itti_s5s8_modify_bearer_request& m);
void send_msg(itti_s5s8_release_access_bearers_request& m);
void send_msg(itti_s5s8_downlink_data_notification_acknowledge& m);
void time_out_itti_event(const uint32_t timer_id);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment