|
|
|
@ -19,13 +19,20 @@
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
#include <srslte/srslte.h>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include "srslte/asn1/liblte_m2ap.h"
|
|
|
|
|
#include "srslte/common/log_filter.h"
|
|
|
|
|
|
|
|
|
|
void m2_setup_request_test()
|
|
|
|
|
#define TESTASSERT(cond) \
|
|
|
|
|
{ \
|
|
|
|
|
if (!(cond)) { \
|
|
|
|
|
std::cout << "[" << __FUNCTION__ << "][Line " << __LINE__ << "]: FAIL at " << (#cond) << std::endl; \
|
|
|
|
|
return -1; \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int m2_setup_request_test()
|
|
|
|
|
{
|
|
|
|
|
srslte::log_filter log1("M2AP");
|
|
|
|
|
log1.set_level(srslte::LOG_LEVEL_DEBUG);
|
|
|
|
@ -47,19 +54,19 @@ void m2_setup_request_test()
|
|
|
|
|
log1.info_hex(tst_msg.msg, tst_msg.N_bytes, "M2 Setup Request original message\n");
|
|
|
|
|
|
|
|
|
|
liblte_m2ap_unpack_m2ap_pdu(&tst_msg, &m2ap_pdu);
|
|
|
|
|
assert(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_INITIATINGMESSAGE);
|
|
|
|
|
TESTASSERT(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_INITIATINGMESSAGE);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_INITIATINGMESSAGE_STRUCT *in_msg = &m2ap_pdu.choice.initiatingMessage;
|
|
|
|
|
assert(in_msg->choice_type == LIBLTE_M2AP_INITIATINGMESSAGE_CHOICE_M2SETUPREQUEST);
|
|
|
|
|
TESTASSERT(in_msg->choice_type == LIBLTE_M2AP_INITIATINGMESSAGE_CHOICE_M2SETUPREQUEST);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_MESSAGE_M2SETUPREQUEST_STRUCT *m2_setup = &in_msg->choice.M2SetupRequest;
|
|
|
|
|
assert(m2_setup->ext == false);
|
|
|
|
|
TESTASSERT(m2_setup->ext == false);
|
|
|
|
|
|
|
|
|
|
/*Global eNB-Id*/
|
|
|
|
|
// PLMN Identity (MCC = 001 , MNC = 01)
|
|
|
|
|
uint8_t *plmn_id = m2_setup->Global_ENB_ID.pLMNidentity.buffer;
|
|
|
|
|
assert((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
assert((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
TESTASSERT((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
TESTASSERT((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
|
|
|
|
|
// eNB Identity
|
|
|
|
|
uint8_t enb_id_bits[32];
|
|
|
|
@ -69,37 +76,37 @@ void m2_setup_request_test()
|
|
|
|
|
LIBLTE_M2AP_ENBID_BIT_STRING_LEN);
|
|
|
|
|
liblte_pack(enb_id_bits, 32, (uint8_t *)&enb_id);
|
|
|
|
|
enb_id = ntohl(enb_id);
|
|
|
|
|
assert(enb_id == 0x1a2d0);
|
|
|
|
|
TESTASSERT(enb_id == 0x1a2d0);
|
|
|
|
|
|
|
|
|
|
// eNB Name
|
|
|
|
|
assert(m2_setup->eNBname_present == true);
|
|
|
|
|
assert(m2_setup->eNBname.n_octets == 8);
|
|
|
|
|
assert(strncmp((const char *)m2_setup->eNBname.buffer, "enb1a2d0", m2_setup->eNBname.n_octets) == 0);
|
|
|
|
|
TESTASSERT(m2_setup->eNBname_present == true);
|
|
|
|
|
TESTASSERT(m2_setup->eNBname.n_octets == 8);
|
|
|
|
|
TESTASSERT(strncmp((const char*)m2_setup->eNBname.buffer, "enb1a2d0", m2_setup->eNBname.n_octets) == 0);
|
|
|
|
|
|
|
|
|
|
// eNB MBMS Configuration Data List
|
|
|
|
|
assert(m2_setup->configurationDataList.len == 1);
|
|
|
|
|
TESTASSERT(m2_setup->configurationDataList.len == 1);
|
|
|
|
|
|
|
|
|
|
// eNB MBMS Configuration Data Item
|
|
|
|
|
LIBLTE_M2AP_ENB_MBMS_CONFIGURATION_DATA_ITEM_STRUCT *conf_item = &m2_setup->configurationDataList.buffer[0];
|
|
|
|
|
|
|
|
|
|
// eCGI
|
|
|
|
|
plmn_id = conf_item->eCGI.pLMN_Identity.buffer;
|
|
|
|
|
assert((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
assert((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
TESTASSERT((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
TESTASSERT((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
|
|
|
|
|
// E-UTRAN Cell Identifier
|
|
|
|
|
assert(conf_item->eCGI.EUTRANCellIdentifier.eUTRANCellIdentifier == 27447297);
|
|
|
|
|
TESTASSERT(conf_item->eCGI.EUTRANCellIdentifier.eUTRANCellIdentifier == 27447297);
|
|
|
|
|
|
|
|
|
|
// MBSFN Synchronization Area
|
|
|
|
|
assert(conf_item->mbsfnSynchronisationArea.mbsfn_synchronisation_area_id == 10000);
|
|
|
|
|
TESTASSERT(conf_item->mbsfnSynchronisationArea.mbsfn_synchronisation_area_id == 10000);
|
|
|
|
|
|
|
|
|
|
// MBMS Service Area
|
|
|
|
|
assert(conf_item->mbmsServiceAreaList.len == 2);
|
|
|
|
|
assert(conf_item->mbmsServiceAreaList.buffer[0].n_octets == 2); // Service Area 1
|
|
|
|
|
assert((conf_item->mbmsServiceAreaList.buffer[0].buffer[0] == 0) &&
|
|
|
|
|
TESTASSERT(conf_item->mbmsServiceAreaList.len == 2);
|
|
|
|
|
TESTASSERT(conf_item->mbmsServiceAreaList.buffer[0].n_octets == 2); // Service Area 1
|
|
|
|
|
TESTASSERT((conf_item->mbmsServiceAreaList.buffer[0].buffer[0] == 0) &&
|
|
|
|
|
(conf_item->mbmsServiceAreaList.buffer[0].buffer[1] == 1)); // Service Area 1
|
|
|
|
|
assert(conf_item->mbmsServiceAreaList.buffer[1].n_octets == 2); // Service Area 2
|
|
|
|
|
assert((conf_item->mbmsServiceAreaList.buffer[1].buffer[0] == 0) &&
|
|
|
|
|
TESTASSERT(conf_item->mbmsServiceAreaList.buffer[1].n_octets == 2); // Service Area 2
|
|
|
|
|
TESTASSERT((conf_item->mbmsServiceAreaList.buffer[1].buffer[0] == 0) &&
|
|
|
|
|
(conf_item->mbmsServiceAreaList.buffer[1].buffer[1] == 2)); // Service Area 2
|
|
|
|
|
|
|
|
|
|
/*M2AP Setup Request Pack Test*/
|
|
|
|
@ -107,12 +114,14 @@ void m2_setup_request_test()
|
|
|
|
|
log1.info_hex(out_msg.msg, out_msg.N_bytes, "M2 Setup Request Packed message\n");
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < m2ap_message_len; i++) {
|
|
|
|
|
assert(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
TESTASSERT(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
}
|
|
|
|
|
printf("Test M2SetupRequest successfull\n");
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void m2_setup_response_test()
|
|
|
|
|
int m2_setup_response_test()
|
|
|
|
|
{
|
|
|
|
|
srslte::log_filter log1("M2AP");
|
|
|
|
|
log1.set_level(srslte::LOG_LEVEL_DEBUG);
|
|
|
|
@ -132,94 +141,96 @@ void m2_setup_response_test()
|
|
|
|
|
memcpy(tst_msg.msg, m2ap_message, m2ap_message_len);
|
|
|
|
|
|
|
|
|
|
LIBLTE_ERROR_ENUM err = liblte_m2ap_unpack_m2ap_pdu(&tst_msg, &m2ap_pdu);
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
assert(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_SUCCESSFULOUTCOME);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_SUCCESSFULOUTCOME);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_SUCCESSFULOUTCOME_STRUCT *succ_out = &m2ap_pdu.choice.successfulOutcome;
|
|
|
|
|
assert(succ_out->choice_type == LIBLTE_M2AP_SUCCESSFULOUTCOME_CHOICE_M2SETUPRESPONSE);
|
|
|
|
|
TESTASSERT(succ_out->choice_type == LIBLTE_M2AP_SUCCESSFULOUTCOME_CHOICE_M2SETUPRESPONSE);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_MESSAGE_M2SETUPRESPONSE_STRUCT *m2_setup = &succ_out->choice.M2SetupResponse;
|
|
|
|
|
assert(m2_setup->ext == false);
|
|
|
|
|
TESTASSERT(m2_setup->ext == false);
|
|
|
|
|
|
|
|
|
|
/*Global MCE-Id*/
|
|
|
|
|
// PLMN Identity (MCC = 001 , MNC = 01)
|
|
|
|
|
uint8_t *plmn_id = m2_setup->Global_MCE_ID.pLMN_Identity.buffer;
|
|
|
|
|
assert((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
assert((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
TESTASSERT((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
TESTASSERT((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
|
|
|
|
|
// MCE Identity
|
|
|
|
|
uint16_t mce_id;
|
|
|
|
|
memcpy(&mce_id, m2_setup->Global_MCE_ID.mCE_ID.buffer, LIBLTE_M2AP_MCEID_OCTET_STRING_LEN);
|
|
|
|
|
mce_id = ntohs(mce_id);
|
|
|
|
|
assert(mce_id == 0x0050);
|
|
|
|
|
TESTASSERT(mce_id == 0x0050);
|
|
|
|
|
|
|
|
|
|
/*MCE Name*/
|
|
|
|
|
assert(m2_setup->MCEname_present == false); // FIXME Test with MCE name
|
|
|
|
|
TESTASSERT(m2_setup->MCEname_present == false); // FIXME Test with MCE name
|
|
|
|
|
|
|
|
|
|
/*MCCHrelatedBCCH-ConfigPerMBSFNArea*/
|
|
|
|
|
// Length
|
|
|
|
|
assert(m2_setup->MCCHrelatedBCCHConfigPerMBSFNArea.len == 1);
|
|
|
|
|
TESTASSERT(m2_setup->MCCHrelatedBCCHConfigPerMBSFNArea.len == 1);
|
|
|
|
|
|
|
|
|
|
// MCCH Related BCCH Config Per MBSFN Area Configuration Item
|
|
|
|
|
LIBLTE_M2AP_MCCH_RELATED_BCCH_CONFIG_PER_MBSFN_AREA_ITEM_STRUCT *conf_item =
|
|
|
|
|
&m2_setup->MCCHrelatedBCCHConfigPerMBSFNArea.buffer[0];
|
|
|
|
|
|
|
|
|
|
// MBSFN Area
|
|
|
|
|
assert(conf_item->mbsfnArea.mbsfn_area_id == 1);
|
|
|
|
|
TESTASSERT(conf_item->mbsfnArea.mbsfn_area_id == 1);
|
|
|
|
|
|
|
|
|
|
// PDCCH Length
|
|
|
|
|
assert(conf_item->pdcchLength.ext == false);
|
|
|
|
|
assert(conf_item->pdcchLength.pdcchLength == LIBLTE_M2AP_PDCCH_LENGTH_S2);
|
|
|
|
|
TESTASSERT(conf_item->pdcchLength.ext == false);
|
|
|
|
|
TESTASSERT(conf_item->pdcchLength.pdcchLength == LIBLTE_M2AP_PDCCH_LENGTH_S2);
|
|
|
|
|
|
|
|
|
|
// Repetition Period
|
|
|
|
|
assert(conf_item->repetitionPeriod.repetitionPeriod == LIBLTE_M2AP_REPETITION_PERIOD_RF64);
|
|
|
|
|
TESTASSERT(conf_item->repetitionPeriod.repetitionPeriod == LIBLTE_M2AP_REPETITION_PERIOD_RF64);
|
|
|
|
|
|
|
|
|
|
// Offset
|
|
|
|
|
assert(conf_item->offset.offset == 0);
|
|
|
|
|
TESTASSERT(conf_item->offset.offset == 0);
|
|
|
|
|
|
|
|
|
|
// Modification Period
|
|
|
|
|
assert(conf_item->modificationPeriod.modificationPeriod == LIBLTE_M2AP_MODIFICATION_PERIOD_RF512);
|
|
|
|
|
TESTASSERT(conf_item->modificationPeriod.modificationPeriod == LIBLTE_M2AP_MODIFICATION_PERIOD_RF512);
|
|
|
|
|
|
|
|
|
|
// Subframe Allocation Info
|
|
|
|
|
assert(conf_item->subframeAllocationInfo.buffer[0] == 1);
|
|
|
|
|
assert(conf_item->subframeAllocationInfo.buffer[1] == 0);
|
|
|
|
|
assert(conf_item->subframeAllocationInfo.buffer[2] == 0);
|
|
|
|
|
assert(conf_item->subframeAllocationInfo.buffer[3] == 0);
|
|
|
|
|
assert(conf_item->subframeAllocationInfo.buffer[4] == 0);
|
|
|
|
|
assert(conf_item->subframeAllocationInfo.buffer[5] == 0);
|
|
|
|
|
TESTASSERT(conf_item->subframeAllocationInfo.buffer[0] == 1);
|
|
|
|
|
TESTASSERT(conf_item->subframeAllocationInfo.buffer[1] == 0);
|
|
|
|
|
TESTASSERT(conf_item->subframeAllocationInfo.buffer[2] == 0);
|
|
|
|
|
TESTASSERT(conf_item->subframeAllocationInfo.buffer[3] == 0);
|
|
|
|
|
TESTASSERT(conf_item->subframeAllocationInfo.buffer[4] == 0);
|
|
|
|
|
TESTASSERT(conf_item->subframeAllocationInfo.buffer[5] == 0);
|
|
|
|
|
|
|
|
|
|
// Modulation and Coding Scheme
|
|
|
|
|
assert(conf_item->modulationAndCodingScheme.mcs == LIBLTE_M2AP_MODULATION_AND_CODING_SCHEME_N2);
|
|
|
|
|
TESTASSERT(conf_item->modulationAndCodingScheme.mcs == LIBLTE_M2AP_MODULATION_AND_CODING_SCHEME_N2);
|
|
|
|
|
|
|
|
|
|
/*Cell Information List*/
|
|
|
|
|
assert(conf_item->cellInformationList_present);
|
|
|
|
|
assert(conf_item->cellInformationList.len == 1);
|
|
|
|
|
TESTASSERT(conf_item->cellInformationList_present);
|
|
|
|
|
TESTASSERT(conf_item->cellInformationList.len == 1);
|
|
|
|
|
|
|
|
|
|
/*Cell Information*/
|
|
|
|
|
LIBLTE_M2AP_CELL_INFORMATION_STRUCT *cell_info = &conf_item->cellInformationList.buffer[0];
|
|
|
|
|
|
|
|
|
|
// eCGI
|
|
|
|
|
plmn_id = cell_info->eCGI.pLMN_Identity.buffer;
|
|
|
|
|
assert((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
assert((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
TESTASSERT((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
TESTASSERT((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
// E-UTRAN Cell Identifier
|
|
|
|
|
assert(cell_info->eCGI.EUTRANCellIdentifier.eUTRANCellIdentifier == 27447297);
|
|
|
|
|
TESTASSERT(cell_info->eCGI.EUTRANCellIdentifier.eUTRANCellIdentifier == 27447297);
|
|
|
|
|
// Cell Reservation
|
|
|
|
|
assert(cell_info->cellReservationInfo.e == LIBLTE_M2AP_CELL_RESERVATION_INFO_NON_RESERVED_CELL);
|
|
|
|
|
TESTASSERT(cell_info->cellReservationInfo.e == LIBLTE_M2AP_CELL_RESERVATION_INFO_NON_RESERVED_CELL);
|
|
|
|
|
|
|
|
|
|
/*M2AP Setup Request Pack Test*/
|
|
|
|
|
err = liblte_m2ap_pack_m2ap_pdu(&m2ap_pdu, &out_msg);
|
|
|
|
|
log1.info_hex(tst_msg.msg, tst_msg.N_bytes, "M2 Setup Request original message\n");
|
|
|
|
|
log1.info_hex(out_msg.msg, out_msg.N_bytes, "M2 Setup Request Packed message\n");
|
|
|
|
|
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
for (uint32_t i = 0; i < m2ap_message_len; i++) {
|
|
|
|
|
assert(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
TESTASSERT(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
}
|
|
|
|
|
printf("Test M2SetupResponse successfull\n");
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mbms_session_start_request_test()
|
|
|
|
|
int mbms_session_start_request_test()
|
|
|
|
|
{
|
|
|
|
|
srslte::log_filter log1("M2AP");
|
|
|
|
|
log1.set_level(srslte::LOG_LEVEL_DEBUG);
|
|
|
|
@ -240,59 +251,61 @@ void mbms_session_start_request_test()
|
|
|
|
|
|
|
|
|
|
/*M2AP MBMS Session Start Request Unpack Test*/
|
|
|
|
|
LIBLTE_ERROR_ENUM err = liblte_m2ap_unpack_m2ap_pdu(&tst_msg, &m2ap_pdu);
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
assert(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_INITIATINGMESSAGE);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_INITIATINGMESSAGE);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_INITIATINGMESSAGE_STRUCT *in_msg = &m2ap_pdu.choice.initiatingMessage;
|
|
|
|
|
assert(in_msg->choice_type == LIBLTE_M2AP_INITIATINGMESSAGE_CHOICE_MBMSSESSIONSTARTREQUEST);
|
|
|
|
|
TESTASSERT(in_msg->choice_type == LIBLTE_M2AP_INITIATINGMESSAGE_CHOICE_MBMSSESSIONSTARTREQUEST);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_MESSAGE_MBMSSESSIONSTARTREQUEST_STRUCT *mbms_sess = &in_msg->choice.MbmsSessionStartRequest;
|
|
|
|
|
|
|
|
|
|
/*ProtocolIE MCE-MBMS-M2AP-ID*/
|
|
|
|
|
assert(mbms_sess->MceMbmsM2apId.mce_mbms_m2ap_id == 0);
|
|
|
|
|
TESTASSERT(mbms_sess->MceMbmsM2apId.mce_mbms_m2ap_id == 0);
|
|
|
|
|
|
|
|
|
|
/*ProtocolIE TMGI*/
|
|
|
|
|
uint8_t *plmn_id = mbms_sess->Tmgi.pLMN_Identity.buffer;
|
|
|
|
|
assert((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
assert((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
assert(mbms_sess->Tmgi.serviceID.buffer[0] == 0);
|
|
|
|
|
assert(mbms_sess->Tmgi.serviceID.buffer[1] == 0);
|
|
|
|
|
assert(mbms_sess->Tmgi.serviceID.buffer[2] == 1);
|
|
|
|
|
TESTASSERT((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
TESTASSERT((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
TESTASSERT(mbms_sess->Tmgi.serviceID.buffer[0] == 0);
|
|
|
|
|
TESTASSERT(mbms_sess->Tmgi.serviceID.buffer[1] == 0);
|
|
|
|
|
TESTASSERT(mbms_sess->Tmgi.serviceID.buffer[2] == 1);
|
|
|
|
|
|
|
|
|
|
/*Service Area*/
|
|
|
|
|
assert(mbms_sess->MbmsServiceArea.n_octets == 2);
|
|
|
|
|
assert(mbms_sess->MbmsServiceArea.buffer[0] == 0);
|
|
|
|
|
assert(mbms_sess->MbmsServiceArea.buffer[1] == 1);
|
|
|
|
|
TESTASSERT(mbms_sess->MbmsServiceArea.n_octets == 2);
|
|
|
|
|
TESTASSERT(mbms_sess->MbmsServiceArea.buffer[0] == 0);
|
|
|
|
|
TESTASSERT(mbms_sess->MbmsServiceArea.buffer[1] == 1);
|
|
|
|
|
|
|
|
|
|
/* TNL Information */
|
|
|
|
|
// IPMC Address
|
|
|
|
|
assert(mbms_sess->TnlInformation.iPMCAddress.len == 4);
|
|
|
|
|
TESTASSERT(mbms_sess->TnlInformation.iPMCAddress.len == 4);
|
|
|
|
|
int32_t addr;
|
|
|
|
|
memcpy(&addr, mbms_sess->TnlInformation.iPMCAddress.buffer, 4);
|
|
|
|
|
assert(ntohl(addr) == 0x7F000201);
|
|
|
|
|
TESTASSERT(ntohl(addr) == 0x7F000201);
|
|
|
|
|
// Source Address
|
|
|
|
|
assert(mbms_sess->TnlInformation.iPSourceAddress.len == 4);
|
|
|
|
|
TESTASSERT(mbms_sess->TnlInformation.iPSourceAddress.len == 4);
|
|
|
|
|
memcpy(&addr, mbms_sess->TnlInformation.iPSourceAddress.buffer, 4);
|
|
|
|
|
assert(ntohl(addr) == 0x7F0001C8);
|
|
|
|
|
TESTASSERT(ntohl(addr) == 0x7F0001C8);
|
|
|
|
|
|
|
|
|
|
// TEID
|
|
|
|
|
int32_t teid;
|
|
|
|
|
memcpy(&teid, mbms_sess->TnlInformation.gtpTeid.buffer, 4);
|
|
|
|
|
assert(ntohl(teid) == 0x00000001);
|
|
|
|
|
TESTASSERT(ntohl(teid) == 0x00000001);
|
|
|
|
|
|
|
|
|
|
/*M2AP Setup Request Pack Test*/
|
|
|
|
|
err = liblte_m2ap_pack_m2ap_pdu(&m2ap_pdu, &out_msg);
|
|
|
|
|
log1.info_hex(tst_msg.msg, tst_msg.N_bytes, "MBMS Session Start Request original message\n");
|
|
|
|
|
log1.info_hex(out_msg.msg, out_msg.N_bytes, "MBMS Session Start Request Packed message\n");
|
|
|
|
|
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
for (uint32_t i = 0; i < m2ap_message_len; i++) {
|
|
|
|
|
assert(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
TESTASSERT(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
}
|
|
|
|
|
printf("Test MBMSSessionStartRequest successfull\n");
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mbms_session_start_response_test()
|
|
|
|
|
int mbms_session_start_response_test()
|
|
|
|
|
{
|
|
|
|
|
srslte::log_filter log1("M2AP");
|
|
|
|
|
log1.set_level(srslte::LOG_LEVEL_DEBUG);
|
|
|
|
@ -312,33 +325,35 @@ void mbms_session_start_response_test()
|
|
|
|
|
|
|
|
|
|
/*M2AP MBMS Session Start Request Unpack Test*/
|
|
|
|
|
LIBLTE_ERROR_ENUM err = liblte_m2ap_unpack_m2ap_pdu(&tst_msg, &m2ap_pdu);
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
assert(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_SUCCESSFULOUTCOME);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_SUCCESSFULOUTCOME);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_SUCCESSFULOUTCOME_STRUCT *succ_msg = &m2ap_pdu.choice.successfulOutcome;
|
|
|
|
|
assert(succ_msg->choice_type == LIBLTE_M2AP_SUCCESSFULOUTCOME_CHOICE_MBMSSESSIONSTARTRESPONSE);
|
|
|
|
|
TESTASSERT(succ_msg->choice_type == LIBLTE_M2AP_SUCCESSFULOUTCOME_CHOICE_MBMSSESSIONSTARTRESPONSE);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_MESSAGE_MBMSSESSIONSTARTRESPONSE_STRUCT *mbms_sess = &succ_msg->choice.MbmsSessionStartResponse;
|
|
|
|
|
|
|
|
|
|
/*ProtocolIE MCE-MBMS-M2AP-ID*/
|
|
|
|
|
assert(mbms_sess->MceMbmsM2apId.mce_mbms_m2ap_id == 0);
|
|
|
|
|
TESTASSERT(mbms_sess->MceMbmsM2apId.mce_mbms_m2ap_id == 0);
|
|
|
|
|
|
|
|
|
|
/*ProtocolIE ENB-MBMS-M2AP-ID*/
|
|
|
|
|
assert(mbms_sess->EnbMbmsM2apId.enb_mbms_m2ap_id == 0);
|
|
|
|
|
TESTASSERT(mbms_sess->EnbMbmsM2apId.enb_mbms_m2ap_id == 0);
|
|
|
|
|
|
|
|
|
|
/*M2AP Setup Request Pack Test*/
|
|
|
|
|
err = liblte_m2ap_pack_m2ap_pdu(&m2ap_pdu, &out_msg);
|
|
|
|
|
log1.info_hex(tst_msg.msg, tst_msg.N_bytes, "MBMS Session Start Response original message\n");
|
|
|
|
|
log1.info_hex(out_msg.msg, out_msg.N_bytes, "MBMS Session Start Response Packed message\n");
|
|
|
|
|
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
for (uint32_t i = 0; i < m2ap_message_len; i++) {
|
|
|
|
|
assert(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
TESTASSERT(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
}
|
|
|
|
|
printf("Test MBMSSessionStartRequest successfull\n");
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mbms_scheduling_information_test()
|
|
|
|
|
int mbms_scheduling_information_test()
|
|
|
|
|
{
|
|
|
|
|
srslte::log_filter log1("M2AP");
|
|
|
|
|
log1.set_level(srslte::LOG_LEVEL_DEBUG);
|
|
|
|
@ -361,88 +376,90 @@ void mbms_scheduling_information_test()
|
|
|
|
|
|
|
|
|
|
/*M2AP MBMS Scheduling Information Unpack Test*/
|
|
|
|
|
LIBLTE_ERROR_ENUM err = liblte_m2ap_unpack_m2ap_pdu(&tst_msg, &m2ap_pdu);
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
assert(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_INITIATINGMESSAGE);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_INITIATINGMESSAGE);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_INITIATINGMESSAGE_STRUCT *in_msg = &m2ap_pdu.choice.initiatingMessage;
|
|
|
|
|
assert(in_msg->choice_type == LIBLTE_M2AP_INITIATINGMESSAGE_CHOICE_MBMSSCHEDULINGINFORMATION);
|
|
|
|
|
TESTASSERT(in_msg->choice_type == LIBLTE_M2AP_INITIATINGMESSAGE_CHOICE_MBMSSCHEDULINGINFORMATION);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_MESSAGE_MBMSSCHEDULINGINFORMATION_STRUCT *sched_info = &in_msg->choice.MbmsSchedulingInformation;
|
|
|
|
|
|
|
|
|
|
/*ProtocolIE MCCH-Update-Time*/
|
|
|
|
|
assert(sched_info->MCCHUpdateTime.mcchUpdateTime == 0);
|
|
|
|
|
TESTASSERT(sched_info->MCCHUpdateTime.mcchUpdateTime == 0);
|
|
|
|
|
|
|
|
|
|
/*ProtocolIE MBSFN-Area-Configuration-Item*/
|
|
|
|
|
assert(sched_info->MbsfnAreaConfigurationList.len == 1);
|
|
|
|
|
TESTASSERT(sched_info->MbsfnAreaConfigurationList.len == 1);
|
|
|
|
|
LIBLTE_M2AP_MBSFN_AREA_CONFIGURATION_ITEM_STRUCT *area_conf = &sched_info->MbsfnAreaConfigurationList.buffer[0];
|
|
|
|
|
|
|
|
|
|
//PMCH Configuration List/Item
|
|
|
|
|
assert(area_conf->PMCHConfigurationList.len == 1);
|
|
|
|
|
TESTASSERT(area_conf->PMCHConfigurationList.len == 1);
|
|
|
|
|
LIBLTE_M2AP_PMCH_CONFIGURATION_ITEM_STRUCT *pmch_conf_item = &area_conf->PMCHConfigurationList.buffer[0];
|
|
|
|
|
|
|
|
|
|
// PMCH Configuration
|
|
|
|
|
LIBLTE_M2AP_PMCH_CONFIGURATION_STRUCT *pmch_conf = &area_conf->PMCHConfigurationList.buffer[0].PMCHConfiguration;
|
|
|
|
|
|
|
|
|
|
//Allocated Subframes End
|
|
|
|
|
assert(pmch_conf->allocatedSubframesEnd.allocated_subframes_end == 63);
|
|
|
|
|
TESTASSERT(pmch_conf->allocatedSubframesEnd.allocated_subframes_end == 63);
|
|
|
|
|
|
|
|
|
|
//Data MCS
|
|
|
|
|
assert(pmch_conf->dataMCS.dataMCS == 2);
|
|
|
|
|
TESTASSERT(pmch_conf->dataMCS.dataMCS == 2);
|
|
|
|
|
|
|
|
|
|
//MCH Scheduling Period
|
|
|
|
|
assert(pmch_conf->mchSchedulingPeriod.e == LIBLTE_M2AP_MCH_SCHEDULING_PERIOD_RF64);
|
|
|
|
|
TESTASSERT(pmch_conf->mchSchedulingPeriod.e == LIBLTE_M2AP_MCH_SCHEDULING_PERIOD_RF64);
|
|
|
|
|
|
|
|
|
|
//MBMS Session List Per PMCH Item
|
|
|
|
|
assert(pmch_conf_item->MBMSSessionListPerPMCHItem.len == 1);
|
|
|
|
|
TESTASSERT(pmch_conf_item->MBMSSessionListPerPMCHItem.len == 1);
|
|
|
|
|
LIBLTE_M2AP_MBMS_SESSION_LIST_PER_PMCH_ITEM_STRUCT *mbms_sess = &pmch_conf_item->MBMSSessionListPerPMCHItem;
|
|
|
|
|
|
|
|
|
|
//TMGI
|
|
|
|
|
uint8_t *plmn_id = mbms_sess->buffer[0].Tmgi.pLMN_Identity.buffer;
|
|
|
|
|
assert((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
assert((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
assert(mbms_sess->buffer[0].Tmgi.serviceID.buffer[0] == 0);
|
|
|
|
|
assert(mbms_sess->buffer[0].Tmgi.serviceID.buffer[1] == 0);
|
|
|
|
|
assert(mbms_sess->buffer[0].Tmgi.serviceID.buffer[2] == 1);
|
|
|
|
|
TESTASSERT((plmn_id[0] & 0x0F) == 0 && (plmn_id[0] & 0xF0) >> 4 == 0 && (plmn_id[1] & 0x0F) == 1); // MCC
|
|
|
|
|
TESTASSERT((plmn_id[1] & 0xF0) >> 4 == 0xF && (plmn_id[2] & 0x0F) == 0 && (plmn_id[2] & 0xF0) >> 4 == 1); // MNC
|
|
|
|
|
TESTASSERT(mbms_sess->buffer[0].Tmgi.serviceID.buffer[0] == 0);
|
|
|
|
|
TESTASSERT(mbms_sess->buffer[0].Tmgi.serviceID.buffer[1] == 0);
|
|
|
|
|
TESTASSERT(mbms_sess->buffer[0].Tmgi.serviceID.buffer[2] == 1);
|
|
|
|
|
|
|
|
|
|
//LCID
|
|
|
|
|
assert(mbms_sess->buffer[0].Lcid.lcid == 1);
|
|
|
|
|
TESTASSERT(mbms_sess->buffer[0].Lcid.lcid == 1);
|
|
|
|
|
|
|
|
|
|
/*MBSFN Subframe Configuration List*/
|
|
|
|
|
assert(area_conf->MBSFNSubframeConfigurationList.len == 1);
|
|
|
|
|
TESTASSERT(area_conf->MBSFNSubframeConfigurationList.len == 1);
|
|
|
|
|
LIBLTE_M2AP_MBSFN_SUBFRAME_CONFIGURATION_STRUCT *sub_conf = &area_conf->MBSFNSubframeConfigurationList.buffer[0];
|
|
|
|
|
|
|
|
|
|
/*MBSFN Subframe configuration*/
|
|
|
|
|
//Radioframe Allocation period
|
|
|
|
|
assert(sub_conf->radioFrameAllocationPeriod.e == LIBLTE_M2AP_RADIOFRAME_ALLOCATION_PERIOD_N1);
|
|
|
|
|
TESTASSERT(sub_conf->radioFrameAllocationPeriod.e == LIBLTE_M2AP_RADIOFRAME_ALLOCATION_PERIOD_N1);
|
|
|
|
|
//Radioframe Allocation Offset
|
|
|
|
|
assert(sub_conf->radioFrameAllocationOffset.radioframeAllocationOffset == 0);
|
|
|
|
|
TESTASSERT(sub_conf->radioFrameAllocationOffset.radioframeAllocationOffset == 0);
|
|
|
|
|
// Subframe Allocation
|
|
|
|
|
assert(sub_conf->subframeAllocation.choice_type == LIBLTE_M2AP_SUBFRAME_ALLOCATION_ONE_FRAME);
|
|
|
|
|
assert(sub_conf->subframeAllocation.choice.oneFrame[0] == 1);
|
|
|
|
|
assert(sub_conf->subframeAllocation.choice.oneFrame[1] == 0);
|
|
|
|
|
assert(sub_conf->subframeAllocation.choice.oneFrame[2] == 0);
|
|
|
|
|
assert(sub_conf->subframeAllocation.choice.oneFrame[3] == 0);
|
|
|
|
|
assert(sub_conf->subframeAllocation.choice.oneFrame[4] == 0);
|
|
|
|
|
assert(sub_conf->subframeAllocation.choice.oneFrame[5] == 0);
|
|
|
|
|
TESTASSERT(sub_conf->subframeAllocation.choice_type == LIBLTE_M2AP_SUBFRAME_ALLOCATION_ONE_FRAME);
|
|
|
|
|
TESTASSERT(sub_conf->subframeAllocation.choice.oneFrame[0] == 1);
|
|
|
|
|
TESTASSERT(sub_conf->subframeAllocation.choice.oneFrame[1] == 0);
|
|
|
|
|
TESTASSERT(sub_conf->subframeAllocation.choice.oneFrame[2] == 0);
|
|
|
|
|
TESTASSERT(sub_conf->subframeAllocation.choice.oneFrame[3] == 0);
|
|
|
|
|
TESTASSERT(sub_conf->subframeAllocation.choice.oneFrame[4] == 0);
|
|
|
|
|
TESTASSERT(sub_conf->subframeAllocation.choice.oneFrame[5] == 0);
|
|
|
|
|
|
|
|
|
|
/* Common Subframe Allocation Period*/
|
|
|
|
|
assert(area_conf->CommonSubframeAllocationPeriod.e == LIBLTE_M2AP_COMMON_SUBFRAME_ALLOCATION_PERIOD_RF64);
|
|
|
|
|
TESTASSERT(area_conf->CommonSubframeAllocationPeriod.e == LIBLTE_M2AP_COMMON_SUBFRAME_ALLOCATION_PERIOD_RF64);
|
|
|
|
|
|
|
|
|
|
/*MBSFN Area Id*/
|
|
|
|
|
assert(area_conf->MBSFNAreaId.mbsfn_area_id == 1);
|
|
|
|
|
TESTASSERT(area_conf->MBSFNAreaId.mbsfn_area_id == 1);
|
|
|
|
|
|
|
|
|
|
/*M2AP Setup Request Pack Test*/
|
|
|
|
|
err = liblte_m2ap_pack_m2ap_pdu(&m2ap_pdu, &out_msg);
|
|
|
|
|
log1.info_hex(out_msg.msg, out_msg.N_bytes, "MBMS Scheduling Information message\n");
|
|
|
|
|
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
for (uint32_t i = 0; i < m2ap_message_len; i++) {
|
|
|
|
|
assert(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
TESTASSERT(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
}
|
|
|
|
|
printf("Test MBMS Scheduling Information successfull\n");
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mbms_scheduling_information_response_test()
|
|
|
|
|
int mbms_scheduling_information_response_test()
|
|
|
|
|
{
|
|
|
|
|
srslte::log_filter log1("M2AP");
|
|
|
|
|
log1.set_level(srslte::LOG_LEVEL_DEBUG);
|
|
|
|
@ -460,31 +477,49 @@ void mbms_scheduling_information_response_test()
|
|
|
|
|
|
|
|
|
|
/*M2AP MBMS Scheduling Information Unpack Test*/
|
|
|
|
|
LIBLTE_ERROR_ENUM err = liblte_m2ap_unpack_m2ap_pdu(&tst_msg, &m2ap_pdu);
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
assert(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_SUCCESSFULOUTCOME);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(m2ap_pdu.choice_type == LIBLTE_M2AP_M2AP_PDU_CHOICE_SUCCESSFULOUTCOME);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_SUCCESSFULOUTCOME_STRUCT *succ_out = &m2ap_pdu.choice.successfulOutcome;
|
|
|
|
|
assert(succ_out->choice_type == LIBLTE_M2AP_SUCCESSFULOUTCOME_CHOICE_MBMSSCHEDULINGINFORMATIONRESPONSE);
|
|
|
|
|
TESTASSERT(succ_out->choice_type == LIBLTE_M2AP_SUCCESSFULOUTCOME_CHOICE_MBMSSCHEDULINGINFORMATIONRESPONSE);
|
|
|
|
|
|
|
|
|
|
LIBLTE_M2AP_MESSAGE_MBMSSCHEDULINGINFORMATIONRESPONSE_STRUCT *sched_info = &succ_out->choice.MbmsSchedulingInformationResponse;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*M2AP Setup Request Pack Test*/
|
|
|
|
|
err = liblte_m2ap_pack_m2ap_pdu(&m2ap_pdu, &out_msg);
|
|
|
|
|
log1.info_hex(out_msg.msg, out_msg.N_bytes, "MBMS Scheduling Information message\n");
|
|
|
|
|
|
|
|
|
|
assert(err == LIBLTE_SUCCESS);
|
|
|
|
|
TESTASSERT(err == LIBLTE_SUCCESS);
|
|
|
|
|
for (uint32_t i = 0; i < m2ap_message_len; i++) {
|
|
|
|
|
assert(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
TESTASSERT(tst_msg.msg[i] == out_msg.msg[i]);
|
|
|
|
|
}
|
|
|
|
|
printf("Test MBMS Scheduling Information successfull\n");
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
|
{
|
|
|
|
|
m2_setup_request_test();
|
|
|
|
|
m2_setup_response_test();
|
|
|
|
|
mbms_session_start_request_test();
|
|
|
|
|
mbms_session_start_response_test();
|
|
|
|
|
mbms_scheduling_information_test();
|
|
|
|
|
mbms_scheduling_information_response_test();
|
|
|
|
|
if (m2_setup_request_test()) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m2_setup_response_test()) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mbms_session_start_request_test()) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mbms_session_start_response_test()) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mbms_scheduling_information_test()) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mbms_scheduling_information_response_test()) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|