Commit 3992eb9e authored by Javier Morgade's avatar Javier Morgade

-MBMS RRC procedures for M2AP

	-On the fligh MBMS recofiguration for MBSFN, sib13,sib2 and MCCH enabled through M2 interface (MCE)
	-RRC NAS update for MBMS TUN interfaces configuration
	-UE RRC modified to handle dynamic SIB2 (MBSFN Subframe Config Lists), SIB13 (MCCH) reconfigurations (TO CAREFULLY BE CHECKED)

	ACKNOWLEDGEMENT:
 	1. This commit was developed at Vicomtech (https://www.vicomtech.org) under UE project CDN-X-ALL: "CDN edge-cloud computing for efficient cache and reliable streaming aCROSS Aggregated unicast-multicast LinkS"
 	2. Project funded by Fed4FIRE+ OC5 (https://www.fed4fire.eu)
Signed-off-by: default avatarJavier Morgade <javier.morgade@ieee.org>
parent 97bc5e92
......@@ -77,7 +77,7 @@ mac_rrc_data_req(
carrier = &rrc->carrier[0];
mib = &carrier->mib;
if(Srb_id == BCCH_SI_MBMS){
if((Srb_id & RAB_OFFSET )== BCCH_SI_MBMS){
if (frameP%4 == 0){
memcpy(&buffer_pP[0],
RC.rrc[Mod_idP]->carrier[CC_id].SIB1_MBMS,
......@@ -208,7 +208,7 @@ mac_rrc_data_req(
RC.rrc[Mod_idP]->carrier[CC_id].sizeof_MCCH_MESSAGE[mbsfn_sync_area]);
if (LOG_DEBUGFLAG(DEBUG_RRC)) {
LOG_D(RRC,"[eNB %d] Frame %d : MCCH request => MCCH_MESSAGE \n",Mod_idP,frameP);
LOG_W(RRC,"[eNB %d] Frame %d : MCCH request => MCCH_MESSAGE \n",Mod_idP,frameP);
for (int i=0; i<RC.rrc[Mod_idP]->carrier[CC_id].sizeof_MCCH_MESSAGE[mbsfn_sync_area]; i++) {
LOG_T(RRC,"%x.",buffer_pP[i]);
......
......@@ -244,7 +244,7 @@ uint8_t do_MIB_FeMBMS(rrc_eNB_carrier_data_t *carrier, uint32_t N_RB_DL, uint32_
//TODO additionalNonBMSFNSubframes-r14 INTEGER (0..3) ?
//if ( LOG_DEBUGFLAG(DEBUG_ASN1) ) {
xer_fprint(stdout, &asn_DEF_LTE_BCCH_BCH_Message_MBMS, (void *)mib_fembms);
//xer_fprint(stdout, &asn_DEF_LTE_BCCH_BCH_Message_MBMS, (void *)mib_fembms);
//}
......@@ -602,7 +602,7 @@ uint8_t do_SIB1_MBMS(rrc_eNB_carrier_data_t *carrier,
//if ( LOG_DEBUGFLAG(DEBUG_ASN1) ) {
xer_fprint(stdout, &asn_DEF_LTE_BCCH_DL_SCH_Message_MBMS, (void *)bcch_message);
//xer_fprint(stdout, &asn_DEF_LTE_BCCH_DL_SCH_Message_MBMS, (void *)bcch_message);
//}
enc_rval = uper_encode_to_buffer(&asn_DEF_LTE_BCCH_DL_SCH_Message_MBMS,
......@@ -755,8 +755,8 @@ uint8_t do_SIB1(rrc_eNB_carrier_data_t *carrier,
LTE_PLMN_IdentityInfo_t PLMN_identity_info[num_plmn];
LTE_MCC_MNC_Digit_t dummy_mcc[num_plmn][3], dummy_mnc[num_plmn][3];
asn_enc_rval_t enc_rval;
LTE_SchedulingInfo_t schedulingInfo;
LTE_SIB_Type_t sib_type;
LTE_SchedulingInfo_t schedulingInfo,schedulingInfo2;
LTE_SIB_Type_t sib_type,sib_type2;
uint8_t *buffer;
LTE_BCCH_DL_SCH_Message_t *bcch_message;
......@@ -785,6 +785,12 @@ uint8_t do_SIB1(rrc_eNB_carrier_data_t *carrier,
memset(PLMN_identity_info,0,num_plmn * sizeof(LTE_PLMN_IdentityInfo_t));
memset(&schedulingInfo,0,sizeof(LTE_SchedulingInfo_t));
memset(&sib_type,0,sizeof(LTE_SIB_Type_t));
if(configuration->eMBMS_M2_configured){
memset(&schedulingInfo2,0,sizeof(LTE_SchedulingInfo_t));
memset(&sib_type2,0,sizeof(LTE_SIB_Type_t));
}
/* as per TS 36.311, up to 6 PLMN_identity_info are allowed in list -> add one by one */
for (int i = 0; i < configuration->num_plmn; ++i) {
......@@ -882,10 +888,18 @@ uint8_t do_SIB1(rrc_eNB_carrier_data_t *carrier,
7;
#endif
schedulingInfo.si_Periodicity=LTE_SchedulingInfo__si_Periodicity_rf8;
if(configuration->eMBMS_M2_configured){
schedulingInfo2.si_Periodicity=LTE_SchedulingInfo__si_Periodicity_rf8;
}
// This is for SIB2/3
sib_type=LTE_SIB_Type_sibType3;
ASN_SEQUENCE_ADD(&schedulingInfo.sib_MappingInfo.list,&sib_type);
ASN_SEQUENCE_ADD(&(*sib1)->schedulingInfoList.list,&schedulingInfo);
if(configuration->eMBMS_M2_configured){
sib_type2=LTE_SIB_Type_sibType13_v920;
ASN_SEQUENCE_ADD(&schedulingInfo2.sib_MappingInfo.list,&sib_type2);
ASN_SEQUENCE_ADD(&(*sib1)->schedulingInfoList.list,&schedulingInfo2);
}
// ASN_SEQUENCE_ADD(&schedulingInfo.sib_MappingInfo.list,NULL);
#if defined(ENABLE_ITTI)
......
......@@ -4277,7 +4277,7 @@ int decode_SI( const protocol_ctxt_t *const ctxt_pP, const uint8_t eNB_index ) {
case LTE_SystemInformation_r8_IEs__sib_TypeAndInfo__Member_PR_sib2:
if ((UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIStatus&2) == 0) {
UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIStatus|=2;
new_sib=1;
//new_sib=1;
memcpy( UE_rrc_inst[ctxt_pP->module_id].sib2[eNB_index], &typeandinfo->choice.sib2, sizeof(LTE_SystemInformationBlockType2_t) );
LOG_I( RRC, "[UE %"PRIu8"] Frame %"PRIu32" Found SIB2 from eNB %"PRIu8"\n", ctxt_pP->module_id, ctxt_pP->frame, eNB_index );
dump_sib2( UE_rrc_inst[ctxt_pP->module_id].sib2[eNB_index] );
......@@ -4362,7 +4362,58 @@ int decode_SI( const protocol_ctxt_t *const ctxt_pP, const uint8_t eNB_index ) {
}
#endif
}
}
}else{
//LOG_W( RRC, "[UE %d] Received new SIB1/SIB2/SIB3 with MBMSs %d\n", ctxt_pP->module_id, ((&typeandinfo->choice.sib2)->mbsfn_SubframeConfigList == NULL ? 0:1) );
if((&typeandinfo->choice.sib2)->mbsfn_SubframeConfigList != NULL && (UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIStatus&4096) == 0){
LOG_W( RRC, "[UE %d] Received SIB2 with MBSFN SF Config\n", ctxt_pP->module_id );
memcpy( UE_rrc_inst[ctxt_pP->module_id].sib2[eNB_index], &typeandinfo->choice.sib2, sizeof(LTE_SystemInformationBlockType2_t) );
LOG_I( RRC, "[FRAME %05"PRIu32"][RRC_UE][MOD %02"PRIu8"][][--- MAC_CONFIG_REQ (SIB2 params eNB %"PRIu8") --->][MAC_UE][MOD %02"PRIu8"][]\n",
ctxt_pP->frame, ctxt_pP->module_id, eNB_index, ctxt_pP->module_id );
rrc_mac_config_req_ue(ctxt_pP->module_id, 0, eNB_index,
(LTE_RadioResourceConfigCommonSIB_t *)NULL,
(struct LTE_PhysicalConfigDedicated *)NULL,
#if (LTE_RRC_VERSION >= MAKE_VERSION(10, 0, 0))
(LTE_SCellToAddMod_r10_t *)NULL,
#endif
(LTE_MeasObjectToAddMod_t **)NULL,
(LTE_MAC_MainConfig_t *)NULL,
0,
(struct LTE_LogicalChannelConfig *)NULL,
(LTE_MeasGapConfig_t *)NULL,
(LTE_TDD_Config_t *)NULL,
(LTE_MobilityControlInfo_t *)NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
UE_rrc_inst[ctxt_pP->module_id].sib2[eNB_index]->mbsfn_SubframeConfigList
#if (LTE_RRC_VERSION >= MAKE_VERSION(9, 0, 0))
,0,
(LTE_MBSFN_AreaInfoList_r9_t *)NULL,
(LTE_PMCH_InfoList_r9_t *)NULL
#endif
#ifdef CBA
,0,
0
#endif
#if (LTE_RRC_VERSION >= MAKE_VERSION(14, 0, 0))
,
0,
NULL,
NULL
#endif
#if (LTE_RRC_VERSION >= MAKE_VERSION(14, 0, 0))
,
0,
(struct LTE_NonMBSFN_SubframeConfig_r14 *)NULL,
(LTE_MBSFN_AreaInfoList_r9_t *)NULL
#endif
);
}
}
break; // case SystemInformation_r8_IEs__sib_TypeAndInfo__Member_PR_sib2
......@@ -4516,8 +4567,8 @@ int decode_SI( const protocol_ctxt_t *const ctxt_pP, const uint8_t eNB_index ) {
(LTE_MBSFN_AreaInfoList_r9_t *)NULL
#endif
);
break;
}
break;
#endif
#if (LTE_RRC_VERSION >= MAKE_VERSION(10, 0, 0))
......@@ -4587,19 +4638,30 @@ int decode_SI( const protocol_ctxt_t *const ctxt_pP, const uint8_t eNB_index ) {
default:
break;
}
if (new_sib == 1) {
UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIcnt++;
if (UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIcnt == sib1->schedulingInfoList.list.count)
rrc_set_sub_state( ctxt_pP->module_id, RRC_SUB_STATE_IDLE_SIB_COMPLETE );
LOG_I(RRC,"SIStatus %x, SIcnt %d/%d\n",
UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIStatus,
UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIcnt,
sib1->schedulingInfoList.list.count);
}
}
if (new_sib == 1) {
UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIcnt++;
//if (new_sib == 1) {
// UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIcnt++;
if (UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIcnt == sib1->schedulingInfoList.list.count)
rrc_set_sub_state( ctxt_pP->module_id, RRC_SUB_STATE_IDLE_SIB_COMPLETE );
// if (UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIcnt == sib1->schedulingInfoList.list.count)
// rrc_set_sub_state( ctxt_pP->module_id, RRC_SUB_STATE_IDLE_SIB_COMPLETE );
LOG_I(RRC,"SIStatus %x, SIcnt %d/%d\n",
UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIStatus,
UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIcnt,
sib1->schedulingInfoList.list.count);
}
// LOG_I(RRC,"SIStatus %x, SIcnt %d/%d\n",
// UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIStatus,
// UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].SIcnt,
// sib1->schedulingInfoList.list.count);
//}
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_RRC_UE_DECODE_SI, VCD_FUNCTION_OUT);
return 0;
......@@ -4927,7 +4989,7 @@ int decode_MCCH_Message( const protocol_ctxt_t *const ctxt_pP, const uint8_t eNB
ctxt_pP->frame,
mbsfn_sync_area);
return 0; // avoid decoding to prevent memory bloating
} else {
} else if(UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].State >= RRC_CONNECTED /*|| UE_rrc_inst[ctxt_pP->module_id].Info[eNB_index].State == RRC_RECONFIGURED*/){
dec_rval = uper_decode_complete(NULL,
&asn_DEF_LTE_MCCH_Message,
(void **)&mcch,
......@@ -5043,7 +5105,8 @@ void decode_MBSFNAreaConfiguration( module_id_t ue_mod_idP, uint8_t eNB_index, f
#endif
);
UE_rrc_inst[ue_mod_idP].Info[eNB_index].MCCHStatus[mbsfn_sync_area] = 1;
if(UE_rrc_inst[ue_mod_idP].Info[eNB_index].State >= RRC_CONNECTED /*|| UE_rrc_inst[ue_mod_idP].Info[eNB_index].State == RRC_RECONFIGURED*/)
UE_rrc_inst[ue_mod_idP].Info[eNB_index].MCCHStatus[mbsfn_sync_area] = 1;
PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, ue_mod_idP, ENB_FLAG_NO, UE_rrc_inst[ue_mod_idP].Info[eNB_index].rnti, frameP, 0,eNB_index);
// Config Radio Bearer for MBMS user data (similar way to configure for eNB side in init_MBMS function)
rrc_pdcp_config_asn1_req(&ctxt,
......
......@@ -86,6 +86,7 @@
#include "rrc_eNB_S1AP.h"
#include "rrc_eNB_GTPV1U.h"
#include "rrc_eNB_M2AP.h"
#include "pdcp.h"
#include "gtpv1u_eNB_task.h"
......@@ -9098,6 +9099,43 @@ void *rrc_enb_process_itti_msg(void *notUsed) {
rrc_subframe_process(&RRC_SUBFRAME_PROCESS(msg_p).ctxt, RRC_SUBFRAME_PROCESS(msg_p).CC_id);
break;
case M2AP_SETUP_RESP:
rrc_eNB_process_M2AP_SETUP_RESP(&ctxt,0/*CC_id*/,ENB_INSTANCE_TO_MODULE_ID(instance),&M2AP_SETUP_RESP(msg_p));
break;
case M2AP_MBMS_SCHEDULING_INFORMATION:
rrc_eNB_process_M2AP_MBMS_SCHEDULING_INFORMATION(&ctxt,0/*CC_id*/,ENB_INSTANCE_TO_MODULE_ID(instance),&M2AP_MBMS_SCHEDULING_INFORMATION(msg_p));
break;
case M2AP_MBMS_SESSION_START_REQ:
rrc_eNB_process_M2AP_MBMS_SESSION_START_REQ(&ctxt,0/*CC_id*/,ENB_INSTANCE_TO_MODULE_ID(instance),&M2AP_MBMS_SESSION_START_REQ(msg_p));
break;
case M2AP_MBMS_SESSION_STOP_REQ:
rrc_eNB_process_M2AP_MBMS_SESSION_STOP_REQ(&ctxt,&M2AP_MBMS_SESSION_STOP_REQ(msg_p));
break;
case M2AP_RESET:
rrc_eNB_process_M2AP_RESET(&ctxt,&M2AP_RESET(msg_p));
break;
case M2AP_ENB_CONFIGURATION_UPDATE_ACK:
rrc_eNB_process_M2AP_ENB_CONFIGURATION_UPDATE_ACK(&ctxt,&M2AP_ENB_CONFIGURATION_UPDATE_ACK(msg_p));
break;
case M2AP_ERROR_INDICATION:
rrc_eNB_process_M2AP_ERROR_INDICATION(&ctxt,&M2AP_ERROR_INDICATION(msg_p));
break;
case M2AP_MBMS_SERVICE_COUNTING_REQ:
rrc_eNB_process_M2AP_MBMS_SERVICE_COUNTING_REQ(&ctxt,&M2AP_MBMS_SERVICE_COUNTING_REQ(msg_p));
break;
case M2AP_MCE_CONFIGURATION_UPDATE:
rrc_eNB_process_M2AP_MCE_CONFIGURATION_UPDATE(&ctxt,&M2AP_MCE_CONFIGURATION_UPDATE(msg_p));
break;
default:
LOG_E(RRC, "[eNB %d] Received unexpected message %s\n", instance, msg_name_p);
break;
......
This diff is collapsed.
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
/*! \file rrc_eNB_M2AP.h
* \brief rrc M2AP procedures for eNB
* \author Javier Morgade
* \version 0.1
* \company Vicomtech, Spain
* \email: javier.morgade@ieee.org
*/
#ifndef RRC_ENB_M2AP_H_
#define RRC_ENB_M2AP_H_
int
rrc_eNB_process_M2AP_SETUP_RESP(
const protocol_ctxt_t *const ctxt_pP,
int CC_id,
instance_t instance,
const m2ap_setup_resp_t *const m2ap_setup_resp
);
int
rrc_eNB_process_M2AP_MBMS_SCHEDULING_INFORMATION(
const protocol_ctxt_t *const ctxt_pP,
int CC_id,
instance_t instance,
const m2ap_mbms_scheduling_information_t *const m2ap_mbms_scheduling_information
);
int
rrc_eNB_process_M2AP_MBMS_SESSION_START_REQ(
const protocol_ctxt_t *const ctxt_pP,
int CC_id,
instance_t instance,
const m2ap_session_start_req_t *const m2ap_session_start_req
);
int
rrc_eNB_process_M2AP_MBMS_SESSION_STOP_REQ(
const protocol_ctxt_t *const ctxt_pP,
const m2ap_session_stop_req_t *const m2ap_session_stop_req
);
int
rrc_eNB_process_M2AP_RESET(
const protocol_ctxt_t *const ctxt_pP,
const m2ap_reset_t *const m2ap_reset
);
int
rrc_eNB_process_M2AP_ENB_CONFIGURATION_UPDATE_ACK(
const protocol_ctxt_t *const ctxt_pP,
const m2ap_enb_configuration_update_ack_t *const m2ap_enb_configuration_update_ack
);
int
rrc_eNB_process_M2AP_ERROR_INDICATION(
const protocol_ctxt_t *const ctxt_pP,
const m2ap_error_indication_t *const m2ap_error_indication
);
int
rrc_eNB_process_M2AP_MBMS_SERVICE_COUNTING_REQ(
const protocol_ctxt_t *const ctxt_pP,
const m2ap_mbms_service_counting_req_t *const m2ap_mbms_service_counting_req
);
int
rrc_eNB_process_M2AP_MCE_CONFIGURATION_UPDATE(
const protocol_ctxt_t *const ctxt_pP,
const m2ap_mce_configuration_update_t *const m2ap_mce_configuration_update
);
void rrc_eNB_send_M2AP_MBMS_SCHEDULING_INFORMATION_RESP(
const protocol_ctxt_t *const ctxt_pP
//,const rrc_eNB_mbms_context_t *const rrc_eNB_mbms_context
);
void rrc_eNB_send_M2AP_MBMS_SESSION_START_RESP(
const protocol_ctxt_t *const ctxt_pP
//,const rrc_eNB_mbms_context_t *const rrc_eNB_mbms_context
);
void rrc_eNB_send_M2AP_MBMS_SESSION_STOP_RESP(
const protocol_ctxt_t *const ctxt_pP
//,const rrc_eNB_mbms_context_t *const rrc_eNB_mbms_context
);
void rrc_eNB_send_M2AP_MBMS_SESSION_UPDATE_RESP(
const protocol_ctxt_t *const ctxt_pP
//,const rrc_eNB_mbms_context_t *const rrc_eNB_mbms_context
);
#endif /* RRC_ENB_M2AP_H_ */
......@@ -230,6 +230,45 @@ int NAS_config(char *interfaceName, char *ipAddress, char *networkMask, char *br
return returnValue;
}
int nas_config_mbms(int interface_id, int thirdOctet, int fourthOctet, char *ifname) {
//char buf[5];
char ipAddress[20];
char broadcastAddress[20];
char interfaceName[20];
int returnValue;
//if(strcmp(ifname,"ue") == 0)
//sprintf(ipAddress, "%s.%d.%d", "20.0",thirdOctet,fourthOctet);
////else
sprintf(ipAddress, "%s.%d.%d",baseNetAddress,thirdOctet,fourthOctet);
sprintf(broadcastAddress, "%s.%d.255",baseNetAddress, thirdOctet);
sprintf(interfaceName, "%s%s%d", (UE_NAS_USE_TUN || ENB_NAS_USE_TUN)?"oaitun_":ifname,
UE_NAS_USE_TUN?ifname/*"ue"*/: (ENB_NAS_USE_TUN?ifname/*"enb"*/:""),interface_id);
bringInterfaceUp(interfaceName, 0);
// sets the machine address
returnValue= setInterfaceParameter(interfaceName, ipAddress,SIOCSIFADDR);
// sets the machine network mask
if(!returnValue)
returnValue= setInterfaceParameter(interfaceName, netMask,SIOCSIFNETMASK);
// sets the machine broadcast address
if(!returnValue)
returnValue= setInterfaceParameter(interfaceName, broadcastAddress,SIOCSIFBRDADDR);
if(!returnValue)
bringInterfaceUp(interfaceName, 1);
if(!returnValue)
LOG_I(OIP,"Interface %s successfuly configured, ip address %s, mask %s broadcast address %s\n",
interfaceName, ipAddress, netMask, broadcastAddress);
else
LOG_E(OIP,"Interface %s couldn't be configured (ip address %s, mask %s broadcast address %s)\n",
interfaceName, ipAddress, netMask, broadcastAddress);
return returnValue;
}
// non blocking full configuration of the interface (address, and the two lest octets of the address)
int nas_config(int interface_id, int thirdOctet, int fourthOctet, char *ifname) {
//char buf[5];
......
......@@ -63,6 +63,20 @@ int NAS_config(char *interfaceName, char *ipAddress, char *networkMask, char *br
*/
int nas_config(int interface_id, int thirdOctet, int fourthOctet, char *ifsuffix);
/*! \fn int nas_config_mbms(char*, int, int)
* \brief This function initializes the nasmesh interface using the basic values,
* basic address, network mask and broadcast address, as the default configured
* ones
* \param[in] interfaceName, the name of the interface, e.g. nasmesh0 or nasmesh1
* \param[in] third octet of the ip address e.g. for the 10.1.2.3 address would be 2
* \param[in] fourth octet of the ip address e.g. for the 10.1.2.3 address would be 3
* \return 0 on success, otherwise 1, if couldn't open a socket and 2 if the ioctl fails
* \note
* @ingroup ?????
*/
int nas_config_mbms(int interface_id, int thirdOctet, int fourthOctet, char *ifsuffix);
/*! \fn int blocking_NAS_config(char*, char*, char*, char*)
* \brief This function initializes the nasmesh interface, in a blocking way,
* the system calls are interrupted
......
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