Commit f1406a2f authored by Tien-Thinh Nguyen's avatar Tien-Thinh Nguyen

Bug fixes for PDU Session Create SM Context (multiparser dealing with 0x00 in...

Bug fixes for PDU Session Create SM Context (multiparser dealing with 0x00 in the message, sending PDU session establishment reject) and memory leak
parent 4f5976b4
......@@ -32,11 +32,6 @@
*/
#include "IndividualSMContextApi.h"
#include "logger.hpp"
#include "Helpers.h"
extern "C" {
#include "multipartparser.h"
}
#include <cassert>
#include <cstring>
......@@ -46,6 +41,13 @@ extern "C" {
#include <map>
#include <string>
#include "logger.hpp"
#include "Helpers.h"
extern "C" {
#include "multipartparser.h"
#include "dynamic_memory_check.h"
}
namespace oai {
namespace smf_server {
namespace api {
......@@ -128,11 +130,10 @@ void IndividualSMContextApi::release_sm_context_handler(
//response.send(Pistache::Http::Code::Bad_Request, "");
//return;
}
free(data);
data = nullptr;
uint8_t size = g_parts.size();
free_wrapper((void **) &data);
uint8_t size = g_parts.size();
Logger::smf_api_server().debug("Number of g_parts %d", g_parts.size());
part p0 = g_parts.front();
g_parts.pop_front();
......@@ -245,8 +246,8 @@ void IndividualSMContextApi::update_sm_context_handler(
//response.send(Pistache::Http::Code::Bad_Request, "");
//return;
}
free(data);
data = nullptr;
free_wrapper((void **) &data);
uint8_t size = g_parts.size();
......@@ -280,7 +281,7 @@ void IndividualSMContextApi::update_sm_context_handler(
if (smContextUpdateData.n1SmMsgIsSet()) {
//N1 SM (for session modification, UE-initiated)
Logger::smf_api_server().debug("N1 SM message is set");
smContextUpdateMessage.setBinaryDataN1SmMessage(p1.body.c_str());
smContextUpdateMessage.setBinaryDataN1SmMessage(p1.body);
}
}
// Getting the path params
......
......@@ -32,11 +32,6 @@
*/
#include "SMContextsCollectionApi.h"
#include "logger.hpp"
#include "Helpers.h"
extern "C" {
#include "multipartparser.h"
}
#include <cassert>
#include <cstring>
......@@ -46,6 +41,14 @@ extern "C" {
#include <map>
#include <string>
#include "logger.hpp"
#include "Helpers.h"
extern "C" {
#include "multipartparser.h"
#include "dynamic_memory_check.h"
}
namespace oai {
namespace smf_server {
namespace api {
......@@ -107,8 +110,15 @@ void SMContextsCollectionApi::post_sm_contexts_handler(
init_globals();
multipartparser_init(&parser,
reinterpret_cast<const char*>(boundary_str.c_str()));
if ((multipartparser_execute(&parser, &g_callbacks, request.body().c_str(),
strlen(request.body().c_str()))
unsigned int str_len = request.body().length();
unsigned char *data = (unsigned char*) malloc(str_len + 1);
memset(data, 0, str_len + 1);
memcpy((void*) data, (void*) request.body().c_str(), str_len);
//if ((multipartparser_execute(&parser, &g_callbacks, request.body().c_str(), strlen(request.body().c_str())) != strlen(request.body().c_str())) or (!g_body_begin_called)){
if ((multipartparser_execute(&parser, &g_callbacks,
reinterpret_cast<const char*>(data), str_len)
!= strlen(request.body().c_str())) or (!g_body_begin_called)) {
Logger::smf_api_server().warn(
"The received message can not be parsed properly!");
......@@ -117,6 +127,9 @@ void SMContextsCollectionApi::post_sm_contexts_handler(
//return;
}
free_wrapper((void**) &data);
uint8_t size = g_parts.size();
Logger::smf_api_server().debug("Number of g_parts %d", g_parts.size());
//at least 2 parts for Json data and N1 (+ N2)
if (g_parts.size() < 2) {
......@@ -142,7 +155,7 @@ void SMContextsCollectionApi::post_sm_contexts_handler(
try {
nlohmann::json::parse(p0.body.c_str()).get_to(smContextCreateData);
smContextMessage.setJsonData(smContextCreateData);
smContextMessage.setBinaryDataN1SmMessage(p1.body.c_str());
smContextMessage.setBinaryDataN1SmMessage(p1.body);
this->post_sm_contexts(smContextMessage, response);
} catch (nlohmann::detail::exception &e) {
//send a 400 error
......@@ -156,7 +169,6 @@ void SMContextsCollectionApi::post_sm_contexts_handler(
response.send(Pistache::Http::Code::Internal_Server_Error, e.what());
return;
}
}
void SMContextsCollectionApi::sm_contexts_collection_api_default_handler(
......
......@@ -69,13 +69,10 @@ void IndividualSMContextApiImpl::release_sm_context(
//N2 SM (for Session establishment)
std::string n2_sm_information = smContextReleaseMessage
.getBinaryDataN2SmInformation();
//std::string n2_sm_information = (smContextUpdateData.getN2SmInfo()).getContentId();
std::string n2_sm_msg_hex;
m_smf_app->convert_string_2_hex(n2_sm_information, n2_sm_msg_hex);
Logger::smf_api_server().debug("smContextMessage, n2 sm information %s",
n2_sm_information.c_str());
std::string n2_sm_info_type = smContextReleaseData.getN2SmInfoType();
sm_context_req_msg.set_n2_sm_information(n2_sm_msg_hex);
sm_context_req_msg.set_n2_sm_information(n2_sm_information);
sm_context_req_msg.set_n2_sm_info_type(n2_sm_info_type);
}
//Step 2. TODO: initialize necessary values for sm context req from smContextReleaseData
......
......@@ -66,8 +66,6 @@ void SMContextsCollectionApiImpl::post_sm_contexts(
SmContextCreateData smContextCreateData = smContextMessage.getJsonData();
std::string n1_sm_msg = smContextMessage.getBinaryDataN1SmMessage();
std::string n1_sm_msg_hex;
m_smf_app->convert_string_2_hex(n1_sm_msg, n1_sm_msg_hex);
Logger::smf_api_server().debug("smContextMessage, N1 SM message: %s",
n1_sm_msg.c_str());
......@@ -77,7 +75,7 @@ void SMContextsCollectionApiImpl::post_sm_contexts(
smf::pdu_session_create_sm_context_request sm_context_req_msg = { };
//set N1 SM Message
sm_context_req_msg.set_n1_sm_message(n1_sm_msg_hex);
sm_context_req_msg.set_n1_sm_message(n1_sm_msg);
//set api root to be used as location header in HTTP response
sm_context_req_msg.set_api_root(m_address + base + "/sm-contexts");
......
......@@ -223,4 +223,8 @@ static const std::vector<std::string> multipart_related_content_part_e2str = {
#define N1_SM_CONTENT_ID "n1SmMsg"
#define N1N2_MESSAGE_CLASS "SM"
#define N2_SM_CONTENT_ID "n2SmMsg"
//for CURL
#define AMF_CURL_TIMEOUT_MS 100L
#endif
......@@ -59,6 +59,7 @@
extern "C" {
#include "nas_message.h"
#include "dynamic_memory_check.h"
}
using namespace smf;
......@@ -732,9 +733,6 @@ void smf_app::handle_pdu_session_update_sm_context_request(
"Handle a PDU Session Update SM Context Request from an AMF");
oai::smf_server::model::SmContextUpdateError smContextUpdateError = { };
oai::smf_server::model::ProblemDetails problem_details = { };
oai::smf_server::model::RefToBinaryData refToBinaryData = { };
std::string n1_sm_message, n1_sm_message_hex; //N1 SM container
smf_n1_n2 smf_n1_n2_inst = { }; //to encode Ngap IE
//Step 1. get supi, dnn, nssai, pdu_session id from sm_context
//SM Context ID - uint32_t in our case
......@@ -742,22 +740,15 @@ void smf_app::handle_pdu_session_update_sm_context_request(
try {
scid = std::stoi(smreq->scid);
} catch (const std::exception &err) {
//TODO: send PDUSession_SMUpdateContext Response (including PDU Session EStablishment Reject) to AMF with CAUSE: invalid context
//TODO: send PDUSession_SMUpdateContext Response to AMF with CAUSE: invalid context
Logger::smf_app().warn(
"Received a PDU Session Update SM Context Request, couldn't retrieve the corresponding SMF context, ignore message!");
problem_details.setCause(
pdu_session_application_error_e2str[PDU_SESSION_APPLICATION_ERROR_CONTEXT_NOT_FOUND]);
smContextUpdateError.setError(problem_details);
refToBinaryData.setContentId(N1_SM_CONTENT_ID);
smContextUpdateError.setN1SmMsg(refToBinaryData);
//PDU Session Establishment Reject
smf_n1_n2_inst.create_n1_sm_container(
smreq->req, PDU_SESSION_ESTABLISHMENT_REJECT, n1_sm_message,
cause_value_5gsm_e::CAUSE_54_PDU_SESSION_DOES_NOT_EXIST);
smf_app_inst->convert_string_2_hex(n1_sm_message, n1_sm_message_hex);
smf_n11_inst->send_pdu_session_update_sm_context_response(
smreq->http_response, smContextUpdateError,
Pistache::Http::Code::Forbidden, n1_sm_message_hex);
Pistache::Http::Code::Forbidden);
return;
}
......@@ -771,16 +762,9 @@ void smf_app::handle_pdu_session_update_sm_context_request(
problem_details.setCause(
pdu_session_application_error_e2str[PDU_SESSION_APPLICATION_ERROR_CONTEXT_NOT_FOUND]);
smContextUpdateError.setError(problem_details);
refToBinaryData.setContentId(N1_SM_CONTENT_ID);
smContextUpdateError.setN1SmMsg(refToBinaryData);
//PDU Session Establishment Reject
smf_n1_n2_inst.create_n1_sm_container(
smreq->req, PDU_SESSION_ESTABLISHMENT_REJECT, n1_sm_message,
cause_value_5gsm_e::CAUSE_54_PDU_SESSION_DOES_NOT_EXIST);
smf_app_inst->convert_string_2_hex(n1_sm_message, n1_sm_message_hex);
smf_n11_inst->send_pdu_session_update_sm_context_response(
smreq->http_response, smContextUpdateError,
Pistache::Http::Code::Forbidden, n1_sm_message_hex);
Pistache::Http::Code::Forbidden);
return;
}
......@@ -805,25 +789,16 @@ void smf_app::handle_pdu_session_update_sm_context_request(
Logger::smf_app().debug("Retrieve SMF context with SUPI " SUPI_64_FMT "",
supi64);
} else {
//send PDUSession_SMUpdateContext Response (including PDU Session EStablishment Reject) to AMF
//send PDUSession_SMUpdateContext Response to AMF
Logger::smf_app().warn(
"Received PDU Session Update SM Context Request with Supi " SUPI_64_FMT "couldn't retrieve the corresponding SMF context, ignore message!",
supi64);
problem_details.setCause(
pdu_session_application_error_e2str[PDU_SESSION_APPLICATION_ERROR_CONTEXT_NOT_FOUND]);
smContextUpdateError.setError(problem_details);
refToBinaryData.setContentId(N1_SM_CONTENT_ID);
smContextUpdateError.setN1SmMsg(refToBinaryData);
//Create N1 container
smf_n1_n2_inst.create_n1_sm_container(
context_req_msg,
PDU_SESSION_ESTABLISHMENT_REJECT,
n1_sm_message,
cause_value_5gsm_e::CAUSE_54_PDU_SESSION_DOES_NOT_EXIST);
smf_app_inst->convert_string_2_hex(n1_sm_message, n1_sm_message_hex);
smf_n11_inst->send_pdu_session_update_sm_context_response(
smreq->http_response, smContextUpdateError,
Pistache::Http::Code::Forbidden, n1_sm_message_hex);
Pistache::Http::Code::Forbidden);
return;
}
......@@ -833,22 +808,15 @@ void smf_app::handle_pdu_session_update_sm_context_request(
if (!sc.get()->find_dnn_context(scf.get()->nssai, dnn, sd)) {
if (nullptr == sd.get()) {
//Error, DNN context doesn't exist
// send PDUSession_SMUpdateContext Response (including PDU Session EStablishment Reject) to AMF
// send PDUSession_SMUpdateContext Response to AMF
Logger::smf_app().warn(
"Received PDU Session Update SM Context Request, couldn't retrieve the corresponding SMF context, ignore message!");
problem_details.setCause(
pdu_session_application_error_e2str[PDU_SESSION_APPLICATION_ERROR_CONTEXT_NOT_FOUND]);
smContextUpdateError.setError(problem_details);
refToBinaryData.setContentId(N1_SM_CONTENT_ID);
smContextUpdateError.setN1SmMsg(refToBinaryData);
//Create N1 container
smf_n1_n2_inst.create_n1_sm_container(
context_req_msg, PDU_SESSION_ESTABLISHMENT_REJECT, n1_sm_message,
cause_value_5gsm_e::CAUSE_27_MISSING_OR_UNKNOWN_DNN);
smf_app_inst->convert_string_2_hex(n1_sm_message, n1_sm_message_hex);
smf_n11_inst->send_pdu_session_update_sm_context_response(
smreq->http_response, smContextUpdateError,
Pistache::Http::Code::Forbidden, n1_sm_message_hex);
Pistache::Http::Code::Forbidden);
return;
}
}
......@@ -981,6 +949,11 @@ void smf_app::convert_string_2_hex(std::string &input_str,
output_str = reinterpret_cast<char*>(datahex);
Logger::smf_app().debug("Output: \n %s ", output_str.c_str());
//free memory
free_wrapper((void**) &data);
free_wrapper((void**) &datahex);
}
//---------------------------------------------------------------------------------------------
......@@ -1001,6 +974,9 @@ unsigned char* smf_app::format_string_as_hex(std::string str) {
printf(" %02x ", data_hex[i]);
printf("\n");
//free memory
free_wrapper((void**) &data);
return data_hex;
}
......
......@@ -51,6 +51,7 @@ extern "C" {
#include "Ngap_AssociatedQosFlowItem.h"
#include "Ngap_QosFlowAddOrModifyResponseList.h"
#include "Ngap_QosFlowAddOrModifyResponseItem.h"
#include "dynamic_memory_check.h"
}
using namespace smf;
......@@ -1089,17 +1090,9 @@ void smf_context::handle_pdu_session_update_sm_context_request(
problem_details.setCause(
pdu_session_application_error_e2str[PDU_SESSION_APPLICATION_ERROR_CONTEXT_NOT_FOUND]);
smContextUpdateError.setError(problem_details);
refToBinaryData.setContentId(N1_SM_CONTENT_ID);
smContextUpdateError.setN1SmMsg(refToBinaryData);
//Create N1 container
smf_n1_n2_inst.create_n1_sm_container(
sm_context_req_msg,
PDU_SESSION_ESTABLISHMENT_REJECT,
n1_sm_msg, cause_value_5gsm_e::CAUSE_54_PDU_SESSION_DOES_NOT_EXIST);
smf_app_inst->convert_string_2_hex(n1_sm_msg, n1_sm_msg_hex);
smf_n11_inst->send_pdu_session_update_sm_context_response(
smreq->http_response, smContextUpdateError,
Pistache::Http::Code::Not_Found, n1_sm_msg_hex);
Pistache::Http::Code::Not_Found);
return;
}
......@@ -1134,17 +1127,9 @@ void smf_context::handle_pdu_session_update_sm_context_request(
problem_details.setCause(
pdu_session_application_error_e2str[PDU_SESSION_APPLICATION_ERROR_N1_SM_ERROR]);
smContextUpdateError.setError(problem_details);
refToBinaryData.setContentId(N1_SM_CONTENT_ID);
smContextUpdateError.setN1SmMsg(refToBinaryData);
//PDU Session Establishment Reject
//24.501: response with a 5GSM STATUS message including cause "#95 Semantically incorrect message"
smf_n1_n2_inst.create_n1_sm_container(
sm_context_req_msg, PDU_SESSION_ESTABLISHMENT_REJECT, n1_sm_msg,
cause_value_5gsm_e::CAUSE_95_SEMANTICALLY_INCORRECT_MESSAGE);
smf_app_inst->convert_string_2_hex(n1_sm_msg, n1_sm_msg_hex);
smf_n11_inst->send_pdu_session_update_sm_context_response(
smreq->http_response, smContextUpdateError,
Pistache::Http::Code::Forbidden, n1_sm_msg_hex);
Pistache::Http::Code::Forbidden);
return;
}
......@@ -1234,6 +1219,7 @@ void smf_context::handle_pdu_session_update_sm_context_request(
// qos_rules_ie[0].segregation ;
// qos_rules_ie[0].qosflowidentifer ;
}
free_wrapper((void **) &qos_rules_ie);
//verify the PDU session ID
if (smreq->req.get_pdu_session_id()
......@@ -1370,11 +1356,6 @@ void smf_context::handle_pdu_session_update_sm_context_request(
n2_sm_info_type_e n2_sm_info_type = smf_app_inst->n2_sm_info_type_str2e(
n2_sm_info_type_str);
unsigned int data_len = n2_sm_information.length();
unsigned char *data = (unsigned char*) malloc(data_len + 1);
memset(data, 0, data_len + 1);
memcpy((void*) data, (void*) n2_sm_information.c_str(), data_len);
//decode N2 SM Info
switch (n2_sm_info_type) {
......@@ -1401,6 +1382,7 @@ void smf_context::handle_pdu_session_update_sm_context_request(
problem_details.setCause(
pdu_session_application_error_e2str[PDU_SESSION_APPLICATION_ERROR_N2_SM_ERROR]);
smContextUpdateError.setError(problem_details);
//TODO: need to verify with/without N1 SM
refToBinaryData.setContentId(N1_SM_CONTENT_ID);
smContextUpdateError.setN1SmMsg(refToBinaryData);
//PDU Session Establishment Reject
......@@ -1411,7 +1393,7 @@ void smf_context::handle_pdu_session_update_sm_context_request(
smf_app_inst->convert_string_2_hex(n1_sm_msg, n1_sm_msg_hex);
smf_n11_inst->send_pdu_session_update_sm_context_response(
smreq->http_response, smContextUpdateError,
Pistache::Http::Code::Forbidden, n1_sm_msg_hex);
Pistache::Http::Code::Forbidden);
return;
}
......@@ -1487,6 +1469,7 @@ void smf_context::handle_pdu_session_update_sm_context_request(
problem_details.setCause(
pdu_session_application_error_e2str[PDU_SESSION_APPLICATION_ERROR_N2_SM_ERROR]);
smContextUpdateError.setError(problem_details);
//TODO: need to verify with/without N1 SM
refToBinaryData.setContentId(N1_SM_CONTENT_ID);
smContextUpdateError.setN1SmMsg(refToBinaryData);
//PDU Session Establishment Reject
......@@ -1548,9 +1531,6 @@ void smf_context::handle_pdu_session_update_sm_context_request(
}
} //end switch
//free memory
free(data);
data = nullptr;
}
//Step 3. For Service Request
......@@ -1592,6 +1572,7 @@ void smf_context::handle_pdu_session_update_sm_context_request(
problem_details.setCause(
pdu_session_application_error_e2str[PDU_SESSION_APPLICATION_ERROR_NETWORK_FAILURE]);
smContextUpdateError.setError(problem_details);
//TODO: need to verify with/without N1 SM
refToBinaryData.setContentId(N1_SM_CONTENT_ID);
smContextUpdateError.setN1SmMsg(refToBinaryData);
//PDU Session Establishment Reject
......
......@@ -47,10 +47,6 @@
using namespace Pistache::Http;
using namespace Pistache::Http::Mime;
//TODO: move to a common file
#define AMF_CURL_TIMEOUT_MS 100L
#define AMF_NUMBER_RETRIES 3
using namespace smf;
using namespace std;
using json = nlohmann::json;
......@@ -61,7 +57,7 @@ extern smf::smf_app *smf_app_inst;
extern smf_config smf_cfg;
void smf_n11_task(void*);
// To read content of the response from UDM
// To read content of the response from AMF
static std::size_t callback(const char *in, std::size_t size, std::size_t num,
std::string *out) {
const std::size_t totalBytes(size * num);
......@@ -221,7 +217,7 @@ void smf_n11::send_n1n2_message_transfer_request(
curl_easy_setopt(curl, CURLOPT_URL, context_res_msg.get_amf_url().c_str());
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, AMF_CURL_TIMEOUT_MS);
//curl_easy_setopt(curl, CURLOPT_INTERFACE, "eno1:sn11"); //Only for testing in all-in-one scenario
//curl_easy_setopt(curl, CURLOPT_INTERFACE, "eno1:sn11"); //TODO: enable this only for testing in all-in-one scenario
mime = curl_mime_init(curl);
alt = curl_mime_init(curl);
......@@ -255,8 +251,7 @@ void smf_n11::send_n1n2_message_transfer_request(
"Add N2 SM Information (NGAP) into the message: %s (bytes %d)",
n2_message.c_str(), n2_message.length() / 2);
part = curl_mime_addpart(mime);
//curl_mime_data(part, reinterpret_cast<const char*>(n2_msg_hex), context_res_msg.get_n2_sm_information().length()/2); //TODO: ISSUE need to be solved
curl_mime_data(part, reinterpret_cast<const char*>(n2_msg_hex), 80); //TODO: ISSUE need to be solved
curl_mime_data(part, reinterpret_cast<const char*>(n2_msg_hex), n2_message.length()/2); //TODO: ISSUE need to be solved
curl_mime_type(part, "application/vnd.3gpp.ngap");
curl_mime_name(
part,
......
This diff is collapsed.
......@@ -895,11 +895,6 @@ void session_update_sm_context_procedure::handle_itti_msg(
int ret = itti_inst->send_msg(itti_msg);
*/
//SHOULD BE REMOVED, FOR TESTING PURPOSE
//change value here to test the corresponding message
//session_procedure_type =
// session_management_procedures_type_e::PDU_SESSION_TEST;
nlohmann::json sm_context_updated_data = { };
sm_context_updated_data["n1MessageContainer"]["n1MessageClass"] =
N1N2_MESSAGE_CLASS;
......@@ -912,6 +907,11 @@ void session_update_sm_context_procedure::handle_itti_msg(
sm_context_updated_data["n2InfoContainer"]["smInfo"]["n2InfoContent"]["ngapData"]["contentId"] =
N2_SM_CONTENT_ID;
//SHOULD BE REMOVED, FOR TESTING PURPOSE
//change value here to test the corresponding message
//session_procedure_type =
// session_management_procedures_type_e::PDU_SESSION_TEST;
switch (session_procedure_type) {
//FOR TESTING PURPOSE
......
......@@ -45,7 +45,7 @@ void N1N2MessageCollectionDocumentApi::n1_n2_message_transfer_handler(const Pist
// Getting the body param
//N1N2MessageTransferReqData n1N2MessageTransferReqData;
nlohmann::json response_data;
response_data["cause"] = "504 Gateway Timeout";
response_data["cause"] = "N1_N2_TRANSFER_INITIATED";
response_data.dump().c_str();
response.headers().add<Pistache::Http::Header::ContentType>(Pistache::Http::Mime::MediaType("application/json"));
response.send(Pistache::Http::Code::Ok, response_data.dump().c_str());
......
......@@ -29,10 +29,25 @@
#include <stdlib.h>
#include "dynamic_memory_check.h"
#include "assertions.h"
//------------------------------------------------------------------------------
void free_wrapper(void **ptr)
{
free(*ptr);
*ptr = NULL;
// for debug only
AssertFatal(ptr, "Trying to free NULL ptr");
if (ptr) {
free(*ptr);
*ptr = NULL;
}
}
//------------------------------------------------------------------------------
void bdestroy_wrapper(bstring *b)
{
if ((b) && (*b)) {
bdestroy(*b);
*b = NULL;
}
}
......@@ -20,18 +20,17 @@
*/
/*! \file dynamic_memory_check.h
\brief
\author Lionel Gauthier
\company Eurecom
\email: lionel.gauthier@eurecom.fr
*/
\brief
\author Lionel Gauthier
\company Eurecom
\email: lionel.gauthier@eurecom.fr
*/
#ifndef FILE_DYNAMIC_MEMORY_CHECK_SEEN
#define FILE_DYNAMIC_MEMORY_CHECK_SEEN
# include "bstrlib.h"
void free_wrapper(void **ptr) __attribute__ ((hot));
void free_wrapper(void **ptr) __attribute__ ((hot));
void bdestroy_wrapper(bstring *b);
#endif /* FILE_DYNAMIC_MEMORY_CHECK_SEEN */
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