Commit 56db03f7 authored by Rohit Gupta's avatar Rohit Gupta

Merge branch 'feature-44-dedicated-drb' of...

Merge branch 'feature-44-dedicated-drb' of gitlab.eurecom.fr:oai/openairinterface5g into feature-44-dedicated-drb
parents de51b52b 94801f79
...@@ -68,6 +68,7 @@ typedef uint32_t frame_t; ...@@ -68,6 +68,7 @@ typedef uint32_t frame_t;
typedef int32_t sframe_t; typedef int32_t sframe_t;
typedef uint32_t sub_frame_t; typedef uint32_t sub_frame_t;
typedef uint8_t module_id_t; typedef uint8_t module_id_t;
typedef uint8_t configured_t;
typedef uint8_t eNB_index_t; typedef uint8_t eNB_index_t;
typedef uint16_t ue_id_t; typedef uint16_t ue_id_t;
typedef int16_t smodule_id_t; typedef int16_t smodule_id_t;
...@@ -215,6 +216,7 @@ typedef struct protocol_ctxt_s { ...@@ -215,6 +216,7 @@ typedef struct protocol_ctxt_s {
frame_t frame; /*!< \brief LTE frame number.*/ frame_t frame; /*!< \brief LTE frame number.*/
sub_frame_t subframe; /*!< \brief LTE sub frame number.*/ sub_frame_t subframe; /*!< \brief LTE sub frame number.*/
eNB_index_t eNB_index; /*!< \brief valid for UE indicating the index of connected eNB(s) */ eNB_index_t eNB_index; /*!< \brief valid for UE indicating the index of connected eNB(s) */
configured_t configured; /*!< \brief flag indicating whether the instance is configured or not */
} protocol_ctxt_t; } protocol_ctxt_t;
// warning time hardcoded // warning time hardcoded
#define PROTOCOL_CTXT_TIME_MILLI_SECONDS(CtXt_h) ((CtXt_h)->frame*10+(CtXt_h)->subframe) #define PROTOCOL_CTXT_TIME_MILLI_SECONDS(CtXt_h) ((CtXt_h)->frame*10+(CtXt_h)->subframe)
......
...@@ -255,6 +255,8 @@ static uint32_t eNB_app_register(uint32_t enb_id_start, uint32_t enb_id_end, con ...@@ -255,6 +255,8 @@ static uint32_t eNB_app_register(uint32_t enb_id_start, uint32_t enb_id_end, con
str = inet_ntoa(addr); str = inet_ntoa(addr);
strcpy(s1ap_register_eNB->enb_ip_address.ipv4_address, str); strcpy(s1ap_register_eNB->enb_ip_address.ipv4_address, str);
LOG_I(ENB_APP,"[eNB %d] eNB_app_register for instance %d\n", enb_id, ENB_MODULE_ID_TO_INSTANCE(enb_id));
itti_send_msg_to_task (TASK_S1AP, ENB_MODULE_ID_TO_INSTANCE(enb_id), msg_p); itti_send_msg_to_task (TASK_S1AP, ENB_MODULE_ID_TO_INSTANCE(enb_id), msg_p);
register_enb_pending++; register_enb_pending++;
......
...@@ -29,8 +29,9 @@ ...@@ -29,8 +29,9 @@
/*! \file pdcp.c /*! \file pdcp.c
* \brief pdcp interface with RLC * \brief pdcp interface with RLC
* \author Lionel GAUTHIER and Navid Nikaein * \author Navid Nikaein and Lionel GAUTHIER
* \date 2009-2012 * \date 2009-2012
* \email navid.nikaein@eurecom.fr
* \version 1.0 * \version 1.0
*/ */
...@@ -83,7 +84,7 @@ extern int otg_enabled; ...@@ -83,7 +84,7 @@ extern int otg_enabled;
* code at targets/TEST/PDCP/test_pdcp.c:test_pdcp_data_req() * code at targets/TEST/PDCP/test_pdcp.c:test_pdcp_data_req()
*/ */
boolean_t pdcp_data_req( boolean_t pdcp_data_req(
const protocol_ctxt_t* const ctxt_pP, protocol_ctxt_t* ctxt_pP,
const srb_flag_t srb_flagP, const srb_flag_t srb_flagP,
const rb_id_t rb_idP, const rb_id_t rb_idP,
const mui_t muiP, const mui_t muiP,
...@@ -105,7 +106,6 @@ boolean_t pdcp_data_req( ...@@ -105,7 +106,6 @@ boolean_t pdcp_data_req(
rlc_op_status_t rlc_status; rlc_op_status_t rlc_status;
boolean_t ret = TRUE; boolean_t ret = TRUE;
hash_key_t key = HASHTABLE_NOT_A_KEY_VALUE; hash_key_t key = HASHTABLE_NOT_A_KEY_VALUE;
hashtable_rc_t h_rc; hashtable_rc_t h_rc;
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_DATA_REQ,VCD_FUNCTION_IN); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_DATA_REQ,VCD_FUNCTION_IN);
...@@ -116,6 +116,22 @@ boolean_t pdcp_data_req( ...@@ -116,6 +116,22 @@ boolean_t pdcp_data_req(
T(T_ENB_PDCP_DL, T_INT(ctxt_pP->module_id), T_INT(ctxt_pP->rnti), T_INT(rb_idP), T_INT(sdu_buffer_sizeP)); T(T_ENB_PDCP_DL, T_INT(ctxt_pP->module_id), T_INT(ctxt_pP->rnti), T_INT(rb_idP), T_INT(sdu_buffer_sizeP));
#endif #endif
if (sdu_buffer_sizeP == 0) {
LOG_W(PDCP, "Handed SDU is of size 0! Ignoring...\n");
return FALSE;
}
/*
* XXX MAX_IP_PACKET_SIZE is 4096, shouldn't this be MAX SDU size, which is 8188 bytes?
*/
if (sdu_buffer_sizeP > MAX_IP_PACKET_SIZE) {
LOG_E(PDCP, "Requested SDU size (%d) is bigger than that can be handled by PDCP (%u)!\n",
sdu_buffer_sizeP, MAX_IP_PACKET_SIZE);
// XXX What does following call do?
mac_xface->macphy_exit("PDCP sdu buffer size > MAX_IP_PACKET_SIZE");
}
if (modeP == PDCP_TRANSMISSION_MODE_TRANSPARENT) { if (modeP == PDCP_TRANSMISSION_MODE_TRANSPARENT) {
AssertError (rb_idP < NB_RB_MBMS_MAX, return FALSE, "RB id is too high (%u/%d) %u %u!\n", rb_idP, NB_RB_MBMS_MAX, ctxt_pP->module_id, ctxt_pP->rnti); AssertError (rb_idP < NB_RB_MBMS_MAX, return FALSE, "RB id is too high (%u/%d) %u %u!\n", rb_idP, NB_RB_MBMS_MAX, ctxt_pP->module_id, ctxt_pP->rnti);
} else { } else {
...@@ -131,36 +147,24 @@ boolean_t pdcp_data_req( ...@@ -131,36 +147,24 @@ boolean_t pdcp_data_req(
if (h_rc != HASH_TABLE_OK) { if (h_rc != HASH_TABLE_OK) {
if (modeP != PDCP_TRANSMISSION_MODE_TRANSPARENT) { if (modeP != PDCP_TRANSMISSION_MODE_TRANSPARENT) {
LOG_W(PDCP, PROTOCOL_CTXT_FMT" Instance is not configured for rb_id %d Ignoring SDU...\n", if ((ctxt_pP->configured == 0) && (ctxt_pP->frame%10 == 0))
PROTOCOL_CTXT_ARGS(ctxt_pP), LOG_W(PDCP, PROTOCOL_CTXT_FMT" Instance is not configured for rb_id %d Ignoring SDU...\n",
rb_idP); PROTOCOL_CTXT_ARGS(ctxt_pP),
return FALSE; rb_idP);
ctxt_pP->configured=0;
return FALSE;
} }
}else{
// instance for a given RB is configured
ctxt_pP->configured=1;
} }
if (sdu_buffer_sizeP == 0) {
LOG_W(PDCP, "Handed SDU is of size 0! Ignoring...\n");
return FALSE;
}
/*
* XXX MAX_IP_PACKET_SIZE is 4096, shouldn't this be MAX SDU size, which is 8188 bytes?
*/
if (sdu_buffer_sizeP > MAX_IP_PACKET_SIZE) {
LOG_E(PDCP, "Requested SDU size (%d) is bigger than that can be handled by PDCP (%u)!\n",
sdu_buffer_sizeP, MAX_IP_PACKET_SIZE);
// XXX What does following call do?
mac_xface->macphy_exit("PDCP sdu buffer size > MAX_IP_PACKET_SIZE");
}
if (ctxt_pP->enb_flag == ENB_FLAG_NO) { if (ctxt_pP->enb_flag == ENB_FLAG_NO) {
start_meas(&eNB_pdcp_stats[ctxt_pP->module_id].data_req); start_meas(&eNB_pdcp_stats[ctxt_pP->module_id].data_req);
} else { } else {
start_meas(&UE_pdcp_stats[ctxt_pP->module_id].data_req); start_meas(&UE_pdcp_stats[ctxt_pP->module_id].data_req);
} }
// PDCP transparent mode for MBMS traffic // PDCP transparent mode for MBMS traffic
if (modeP == PDCP_TRANSMISSION_MODE_TRANSPARENT) { if (modeP == PDCP_TRANSMISSION_MODE_TRANSPARENT) {
......
...@@ -221,7 +221,7 @@ typedef struct pdcp_mbms_s { ...@@ -221,7 +221,7 @@ typedef struct pdcp_mbms_s {
* @ingroup _pdcp * @ingroup _pdcp
*/ */
public_pdcp(boolean_t pdcp_data_req( public_pdcp(boolean_t pdcp_data_req(
const protocol_ctxt_t* const ctxt_pP, protocol_ctxt_t* ctxt_pP,
const srb_flag_t srb_flagP, const srb_flag_t srb_flagP,
const rb_id_t rb_id, const rb_id_t rb_id,
const mui_t muiP, const mui_t muiP,
......
...@@ -29,9 +29,10 @@ ...@@ -29,9 +29,10 @@
/*! \file pdcp_fifo.c /*! \file pdcp_fifo.c
* \brief pdcp interface with linux IP interface, have a look at http://man7.org/linux/man-pages/man7/netlink.7.html for netlink * \brief pdcp interface with linux IP interface, have a look at http://man7.org/linux/man-pages/man7/netlink.7.html for netlink
* \author Lionel GAUTHIER and Navid Nikaein * \author Navid Nikaein and Lionel GAUTHIER
* \date 2009 * \date 2009 - 2016
* \version 0.5 * \version 0.5
* \email navid.nikaein@eurecom.fr
* \warning This component can be runned only in user-space * \warning This component can be runned only in user-space
* @ingroup pdcp * @ingroup pdcp
*/ */
......
...@@ -75,7 +75,7 @@ public_mem_block(void check_mem_area (void);) ...@@ -75,7 +75,7 @@ public_mem_block(void check_mem_area (void);)
private_mem_block(void check_free_mem_block (mem_block_t * leP);) private_mem_block(void check_free_mem_block (mem_block_t * leP);)
# endif # endif
#ifdef USER_MODE #ifdef USER_MODE
# define MEM_SCALE /*MAX_RG */ MAX_MOBILES_PER_ENB # define MEM_SCALE MAX_MOBILES_PER_ENB*NB_RB_MAX
#else #else
# ifdef NODE_RG # ifdef NODE_RG
# define MEM_SCALE 2 # define MEM_SCALE 2
......
...@@ -113,7 +113,8 @@ static void s1ap_eNB_register_mme(s1ap_eNB_instance_t *instance_p, ...@@ -113,7 +113,8 @@ static void s1ap_eNB_register_mme(s1ap_eNB_instance_t *instance_p,
MessageDef *message_p = NULL; MessageDef *message_p = NULL;
sctp_new_association_req_t *sctp_new_association_req_p = NULL; sctp_new_association_req_t *sctp_new_association_req_p = NULL;
s1ap_eNB_mme_data_t *s1ap_mme_data_p = NULL; s1ap_eNB_mme_data_t *s1ap_mme_data_p = NULL;
struct s1ap_eNB_mme_data_s *mme = NULL;
DevAssert(instance_p != NULL); DevAssert(instance_p != NULL);
DevAssert(mme_ip_address != NULL); DevAssert(mme_ip_address != NULL);
...@@ -134,27 +135,56 @@ static void s1ap_eNB_register_mme(s1ap_eNB_instance_t *instance_p, ...@@ -134,27 +135,56 @@ static void s1ap_eNB_register_mme(s1ap_eNB_instance_t *instance_p,
memcpy(&sctp_new_association_req_p->local_address, memcpy(&sctp_new_association_req_p->local_address,
local_ip_addr, local_ip_addr,
sizeof(*local_ip_addr)); sizeof(*local_ip_addr));
/* Create new MME descriptor */ S1AP_INFO("[eNB %d] check the mme registration state\n",instance_p->instance);
s1ap_mme_data_p = calloc(1, sizeof(*s1ap_mme_data_p));
DevAssert(s1ap_mme_data_p != NULL); mme = s1ap_eNB_get_MME_from_instance(instance_p);
s1ap_mme_data_p->cnx_id = s1ap_eNB_fetch_add_global_cnx_id(); if ( mme == NULL ) {
sctp_new_association_req_p->ulp_cnx_id = s1ap_mme_data_p->cnx_id;
/* Create new MME descriptor */
s1ap_mme_data_p->assoc_id = -1; s1ap_mme_data_p = calloc(1, sizeof(*s1ap_mme_data_p));
s1ap_mme_data_p->s1ap_eNB_instance = instance_p; DevAssert(s1ap_mme_data_p != NULL);
STAILQ_INIT(&s1ap_mme_data_p->served_gummei); s1ap_mme_data_p->cnx_id = s1ap_eNB_fetch_add_global_cnx_id();
sctp_new_association_req_p->ulp_cnx_id = s1ap_mme_data_p->cnx_id;
/* Insert the new descriptor in list of known MME
* but not yet associated. s1ap_mme_data_p->assoc_id = -1;
*/ s1ap_mme_data_p->s1ap_eNB_instance = instance_p;
RB_INSERT(s1ap_mme_map, &instance_p->s1ap_mme_head, s1ap_mme_data_p);
s1ap_mme_data_p->state = S1AP_ENB_STATE_WAITING; STAILQ_INIT(&s1ap_mme_data_p->served_gummei);
instance_p->s1ap_mme_nb ++;
instance_p->s1ap_mme_pending_nb ++; /* Insert the new descriptor in list of known MME
* but not yet associated.
*/
RB_INSERT(s1ap_mme_map, &instance_p->s1ap_mme_head, s1ap_mme_data_p);
s1ap_mme_data_p->state = S1AP_ENB_STATE_WAITING;
instance_p->s1ap_mme_nb ++;
instance_p->s1ap_mme_pending_nb ++;
} else if (mme->state == S1AP_ENB_STATE_WAITING) {
instance_p->s1ap_mme_pending_nb ++;
sctp_new_association_req_p->ulp_cnx_id = mme->cnx_id;
S1AP_INFO("[eNB %d] MME already registered, retrive the data (state %d, cnx %d, mme_nb %d, mme_pending_nb %d)\n",
instance_p->instance,
mme->state, mme->cnx_id,
instance_p->s1ap_mme_nb, instance_p->s1ap_mme_pending_nb);
/*s1ap_mme_data_p->cnx_id = mme->cnx_id;
sctp_new_association_req_p->ulp_cnx_id = mme->cnx_id;
s1ap_mme_data_p->assoc_id = -1;
s1ap_mme_data_p->s1ap_eNB_instance = instance_p;
*/
} else {
S1AP_WARN("[eNB %d] MME already registered but not in the waiting state, retrive the data (state %d, cnx %d, mme_nb %d, mme_pending_nb %d)\n",
instance_p->instance,
mme->state, mme->cnx_id,
instance_p->s1ap_mme_nb, instance_p->s1ap_mme_pending_nb);
}
itti_send_msg_to_task(TASK_SCTP, instance_p->instance, message_p); itti_send_msg_to_task(TASK_SCTP, instance_p->instance, message_p);
} }
...@@ -163,13 +193,14 @@ void s1ap_eNB_handle_register_eNB(instance_t instance, s1ap_register_enb_req_t * ...@@ -163,13 +193,14 @@ void s1ap_eNB_handle_register_eNB(instance_t instance, s1ap_register_enb_req_t *
{ {
s1ap_eNB_instance_t *new_instance; s1ap_eNB_instance_t *new_instance;
uint8_t index; uint8_t index;
DevAssert(s1ap_register_eNB != NULL); DevAssert(s1ap_register_eNB != NULL);
/* Look if the provided instance already exists */ /* Look if the provided instance already exists */
new_instance = s1ap_eNB_get_instance(instance); new_instance = s1ap_eNB_get_instance(instance);
if (new_instance != NULL) {
if (new_instance != NULL) {
/* Checks if it is a retry on the same eNB */ /* Checks if it is a retry on the same eNB */
DevCheck(new_instance->eNB_id == s1ap_register_eNB->eNB_id, new_instance->eNB_id, s1ap_register_eNB->eNB_id, 0); DevCheck(new_instance->eNB_id == s1ap_register_eNB->eNB_id, new_instance->eNB_id, s1ap_register_eNB->eNB_id, 0);
DevCheck(new_instance->cell_type == s1ap_register_eNB->cell_type, new_instance->cell_type, s1ap_register_eNB->cell_type, 0); DevCheck(new_instance->cell_type == s1ap_register_eNB->cell_type, new_instance->cell_type, s1ap_register_eNB->cell_type, 0);
...@@ -199,7 +230,7 @@ void s1ap_eNB_handle_register_eNB(instance_t instance, s1ap_register_enb_req_t * ...@@ -199,7 +230,7 @@ void s1ap_eNB_handle_register_eNB(instance_t instance, s1ap_register_enb_req_t *
/* Add the new instance to the list of eNB (meaningfull in virtual mode) */ /* Add the new instance to the list of eNB (meaningfull in virtual mode) */
s1ap_eNB_insert_new_instance(new_instance); s1ap_eNB_insert_new_instance(new_instance);
S1AP_DEBUG("Registered new eNB[%d] and %s eNB id %u\n", S1AP_INFO("Registered new eNB[%d] and %s eNB id %u\n",
instance, instance,
s1ap_register_eNB->cell_type == CELL_MACRO_ENB ? "macro" : "home", s1ap_register_eNB->cell_type == CELL_MACRO_ENB ? "macro" : "home",
s1ap_register_eNB->eNB_id); s1ap_register_eNB->eNB_id);
...@@ -211,7 +242,7 @@ void s1ap_eNB_handle_register_eNB(instance_t instance, s1ap_register_enb_req_t * ...@@ -211,7 +242,7 @@ void s1ap_eNB_handle_register_eNB(instance_t instance, s1ap_register_enb_req_t *
/* Trying to connect to provided list of MME ip address */ /* Trying to connect to provided list of MME ip address */
for (index = 0; index < s1ap_register_eNB->nb_mme; index++) { for (index = 0; index < s1ap_register_eNB->nb_mme; index++) {
s1ap_eNB_register_mme(new_instance, s1ap_eNB_register_mme(new_instance,
&s1ap_register_eNB->mme_ip_address[index], &s1ap_register_eNB->mme_ip_address[index],
&s1ap_register_eNB->enb_ip_address, &s1ap_register_eNB->enb_ip_address,
s1ap_register_eNB->sctp_in_streams, s1ap_register_eNB->sctp_in_streams,
s1ap_register_eNB->sctp_out_streams); s1ap_register_eNB->sctp_out_streams);
......
...@@ -27,6 +27,15 @@ ...@@ -27,6 +27,15 @@
*******************************************************************************/ *******************************************************************************/
/*! \file s1ap_eNB_management_procedures.c
* \brief S1AP eNB task
* \author S. Roux and Navid Nikaein
* \date 2010 - 2016
* \email: navid.nikaein@eurecom.fr
* \version 1.0
* @ingroup _s1ap
*/
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
...@@ -116,6 +125,23 @@ struct s1ap_eNB_mme_data_s *s1ap_eNB_get_MME( ...@@ -116,6 +125,23 @@ struct s1ap_eNB_mme_data_s *s1ap_eNB_get_MME(
return NULL; return NULL;
} }
struct s1ap_eNB_mme_data_s *s1ap_eNB_get_MME_from_instance(
s1ap_eNB_instance_t *instance_p)
{
struct s1ap_eNB_mme_data_s *mme = NULL;
struct s1ap_eNB_mme_data_s *mme_next = NULL;
for (mme = RB_MIN(s1ap_mme_map, &instance_p->s1ap_mme_head); mme!=NULL ; mme = mme_next) {
mme_next = RB_NEXT(s1ap_mme_map, &instance_p->s1ap_mme_head, mme);
if (mme->s1ap_eNB_instance == instance_p) {
return mme;
}
}
return NULL;
}
s1ap_eNB_instance_t *s1ap_eNB_get_instance(instance_t instance) s1ap_eNB_instance_t *s1ap_eNB_get_instance(instance_t instance)
{ {
s1ap_eNB_instance_t *temp = NULL; s1ap_eNB_instance_t *temp = NULL;
...@@ -130,3 +156,41 @@ s1ap_eNB_instance_t *s1ap_eNB_get_instance(instance_t instance) ...@@ -130,3 +156,41 @@ s1ap_eNB_instance_t *s1ap_eNB_get_instance(instance_t instance)
return NULL; return NULL;
} }
void s1ap_eNB_remove_mme_desc(s1ap_eNB_instance_t * instance)
{
struct s1ap_eNB_mme_data_s *mme = NULL;
struct s1ap_eNB_mme_data_s *mmeNext = NULL;
struct plmn_identity_s* plmnInfo;
struct served_group_id_s* groupInfo;
struct served_gummei_s* gummeiInfo;
struct mme_code_s* mmeCode;
for (mme = RB_MIN(s1ap_mme_map, &instance->s1ap_mme_head); mme; mme = mmeNext) {
mmeNext = RB_NEXT(s1ap_mme_map, &instance->s1ap_mme_head, mme);
RB_REMOVE(s1ap_mme_map, &instance->s1ap_mme_head, mme);
while (!STAILQ_EMPTY(&mme->served_gummei)) {
gummeiInfo = STAILQ_FIRST(&mme->served_gummei);
STAILQ_REMOVE_HEAD(&mme->served_gummei, next);
while (!STAILQ_EMPTY(&gummeiInfo->served_plmns)) {
plmnInfo = STAILQ_FIRST(&gummeiInfo->served_plmns);
STAILQ_REMOVE_HEAD(&gummeiInfo->served_plmns, next);
free(plmnInfo);
}
while (!STAILQ_EMPTY(&gummeiInfo->served_group_ids)) {
groupInfo = STAILQ_FIRST(&gummeiInfo->served_group_ids);
STAILQ_REMOVE_HEAD(&gummeiInfo->served_group_ids, next);
free(groupInfo);
}
while (!STAILQ_EMPTY(&gummeiInfo->mme_codes)) {
mmeCode = STAILQ_FIRST(&gummeiInfo->mme_codes);
STAILQ_REMOVE_HEAD(&gummeiInfo->mme_codes, next);
free(mmeCode);
}
free(gummeiInfo);
}
free(mme);
}
}
...@@ -34,6 +34,10 @@ struct s1ap_eNB_mme_data_s *s1ap_eNB_get_MME( ...@@ -34,6 +34,10 @@ struct s1ap_eNB_mme_data_s *s1ap_eNB_get_MME(
s1ap_eNB_instance_t *instance_p, s1ap_eNB_instance_t *instance_p,
int32_t assoc_id, uint16_t cnx_id); int32_t assoc_id, uint16_t cnx_id);
struct s1ap_eNB_mme_data_s *s1ap_eNB_get_MME_from_instance(s1ap_eNB_instance_t *instance_p);
void s1ap_eNB_remove_mme_desc(s1ap_eNB_instance_t * instance);
void s1ap_eNB_insert_new_instance(s1ap_eNB_instance_t *new_instance_p); void s1ap_eNB_insert_new_instance(s1ap_eNB_instance_t *new_instance_p);
s1ap_eNB_instance_t *s1ap_eNB_get_instance(uint8_t mod_id); s1ap_eNB_instance_t *s1ap_eNB_get_instance(uint8_t mod_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