diff --git a/lib/include/srsran/interfaces/gnb_ngap_interfaces.h b/lib/include/srsran/interfaces/gnb_ngap_interfaces.h index ff39c7ffe..eed86ca4d 100644 --- a/lib/include/srsran/interfaces/gnb_ngap_interfaces.h +++ b/lib/include/srsran/interfaces/gnb_ngap_interfaces.h @@ -42,17 +42,14 @@ public: uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, srsran::unique_byte_buffer_t pdu, - uint32_t m_tmsi, - uint8_t mmec) = 0; + uint32_t m_tmsi) = 0; virtual void write_pdu(uint16_t rnti, srsran::unique_byte_buffer_t pdu) = 0; virtual bool user_exists(uint16_t rnti) = 0; virtual void user_mod(uint16_t old_rnti, uint16_t new_rnti) = 0; virtual bool user_release(uint16_t rnti, asn1::ngap_nr::cause_radio_network_e cause_radio) = 0; virtual bool is_amf_connected() = 0; - - /// TS 36.413, 8.3.1 - Initial Context Setup - virtual void ue_ctxt_setup_complete(uint16_t rnti) = 0; + virtual void ue_ctxt_setup_complete(uint16_t rnti) = 0; }; } // namespace srsenb diff --git a/srsenb/hdr/stack/ngap/ngap.h b/srsenb/hdr/stack/ngap/ngap.h index 99b7af3b8..737d6022d 100644 --- a/srsenb/hdr/stack/ngap/ngap.h +++ b/srsenb/hdr/stack/ngap/ngap.h @@ -14,6 +14,7 @@ #include "srsenb/hdr/common/common_enb.h" #include "srsran/adt/circular_map.h" +#include "srsran/adt/optional.h" #include "srsran/asn1/asn1_utils.h" #include "srsran/asn1/ngap.h" #include "srsran/common/bcd_helpers.h" @@ -27,8 +28,11 @@ #include "srsran/interfaces/gnb_ngap_interfaces.h" #include "srsran/interfaces/gnb_rrc_nr_interfaces.h" #include "srsran/srslog/srslog.h" +#include +#include namespace srsenb { + class ngap : public ngap_interface_rrc_nr { public: @@ -42,21 +46,22 @@ public: void initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu){}; + srsran::unique_byte_buffer_t pdu); void initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, srsran::unique_byte_buffer_t pdu, - uint32_t m_tmsi, - uint8_t mmec){}; + uint32_t s_tmsi); + void write_pdu(uint16_t rnti, srsran::unique_byte_buffer_t pdu){}; bool user_exists(uint16_t rnti) { return true; }; void user_mod(uint16_t old_rnti, uint16_t new_rnti){}; bool user_release(uint16_t rnti, asn1::ngap_nr::cause_radio_network_e cause_radio) { return true; }; bool is_amf_connected(); - - /// TS 36.413, 8.3.1 - Initial Context Setup - void ue_ctxt_setup_complete(uint16_t rnti){}; + bool send_error_indication(const asn1::ngap_nr::cause_c& cause, + srsran::optional ran_ue_ngap_id = {}, + srsran::optional amf_ue_ngap_id = {}); + void ue_ctxt_setup_complete(uint16_t rnti); // Stack interface bool @@ -82,7 +87,7 @@ private: struct sockaddr_in amf_addr = {}; // AMF address bool amf_connected = false; bool running = false; - uint32_t next_enb_ue_ngap_id = 1; // Next ENB-side UE identifier + uint32_t next_gnb_ue_ngap_id = 1; // Next GNB-side UE identifier uint16_t next_ue_stream_id = 1; // Next UE SCTP stream identifier srsran::unique_timer amf_connect_timer, ngsetup_timeout; @@ -90,8 +95,82 @@ private: asn1::ngap_nr::tai_s tai; asn1::ngap_nr::nr_cgi_s nr_cgi; + // Moved into NGAP class to avoid redifinition (Introduce new namespace?) + struct ue_ctxt_t { + static const uint32_t invalid_gnb_id = std::numeric_limits::max(); + + uint16_t rnti = SRSRAN_INVALID_RNTI; + uint32_t ran_ue_ngap_id = invalid_gnb_id; + srsran::optional amf_ue_ngap_id; + uint32_t gnb_cc_idx = 0; + struct timeval init_timestamp = {}; + + // AMF identifier + uint16_t amf_set_id; + uint8_t amf_pointer; + uint8_t amf_region_id; + }; + asn1::ngap_nr::ng_setup_resp_s ngsetupresponse; + int build_tai_cgi(); + bool connect_amf(); + bool setup_ng(); + bool sctp_send_ngap_pdu(const asn1::ngap_nr::ngap_pdu_c& tx_pdu, uint32_t rnti, const char* procedure_name); + + bool handle_ngap_rx_pdu(srsran::byte_buffer_t* pdu); + bool handle_successfuloutcome(const asn1::ngap_nr::successful_outcome_s& msg); + bool handle_unsuccessfuloutcome(const asn1::ngap_nr::unsuccessful_outcome_s& msg); + bool handle_initiatingmessage(const asn1::ngap_nr::init_msg_s& msg); + + bool handle_dlnastransport(const asn1::ngap_nr::dl_nas_transport_s& msg); + bool handle_ngsetupresponse(const asn1::ngap_nr::ng_setup_resp_s& msg); + bool handle_ngsetupfailure(const asn1::ngap_nr::ng_setup_fail_s& msg); + bool handle_initialctxtsetuprequest(const asn1::ngap_nr::init_context_setup_request_s& msg); + struct ue { + explicit ue(ngap* ngap_ptr_); + bool send_initialuemessage(asn1::ngap_nr::rrcestablishment_cause_e cause, + srsran::unique_byte_buffer_t pdu, + bool has_tmsi, + uint32_t s_tmsi = 0); + bool send_ulnastransport(srsran::unique_byte_buffer_t pdu); + bool was_uectxtrelease_requested() const { return release_requested; } + void ue_ctxt_setup_complete(); + + ue_ctxt_t ctxt = {}; + uint16_t stream_id = 1; + + private: + // args + ngap* ngap_ptr; + + // state + bool release_requested = false; + }; + class user_list + { + public: + using value_type = std::unique_ptr; + using iterator = std::unordered_map::iterator; + using const_iterator = std::unordered_map::const_iterator; + using pair_type = std::unordered_map::value_type; + + ue* find_ue_rnti(uint16_t rnti); + ue* find_ue_gnbid(uint32_t gnbid); + ue* find_ue_amfid(uint32_t amfid); + ue* add_user(value_type user); + void erase(ue* ue_ptr); + iterator begin() { return users.begin(); } + iterator end() { return users.end(); } + const_iterator cbegin() const { return users.begin(); } + const_iterator cend() const { return users.end(); } + size_t size() const { return users.size(); } + + private: + std::unordered_map > users; // maps ran_ue_ngap_id to user + }; + user_list users; + // procedures class ng_setup_proc_t { @@ -114,15 +193,7 @@ private: ngap* ngap_ptr = nullptr; }; - void build_tai_cgi(); - bool connect_amf(); - bool setup_ng(); - bool sctp_send_ngap_pdu(const asn1::ngap_nr::ngap_pdu_c& tx_pdu, uint32_t rnti, const char* procedure_name); - - bool handle_ngap_rx_pdu(srsran::byte_buffer_t* pdu); - bool handle_successfuloutcome(const asn1::ngap_nr::successful_outcome_s& msg); - - bool handle_ngsetupresponse(const asn1::ngap_nr::ng_setup_resp_s& msg); + ue* handle_ngapmsg_ue_id(uint32_t gnb_id, uint32_t amf_id); srsran::proc_t ngsetup_proc; diff --git a/srsenb/src/stack/ngap/ngap.cc b/srsenb/src/stack/ngap/ngap.cc index 7f901d8e1..75fec27c6 100644 --- a/srsenb/src/stack/ngap/ngap.cc +++ b/srsenb/src/stack/ngap/ngap.cc @@ -11,6 +11,10 @@ */ #include "srsenb/hdr/stack/ngap/ngap.h" +#include "srsran/common/int_helpers.h" + +using srsran::s1ap_mccmnc_to_plmn; +using srsran::uint32_to_uint8; #define procError(fmt, ...) ngap_ptr->logger.error("Proc \"%s\" - " fmt, name(), ##__VA_ARGS__) #define procWarning(fmt, ...) ngap_ptr->logger.warning("Proc \"%s\" - " fmt, name(), ##__VA_ARGS__) @@ -145,20 +149,137 @@ bool ngap::is_amf_connected() } // Generate common NGAP protocol IEs from config args -void ngap::build_tai_cgi() +int ngap::build_tai_cgi() { uint32_t plmn; + uint8_t shift; // TAI srsran::s1ap_mccmnc_to_plmn(args.mcc, args.mnc, &plmn); tai.plmn_id.from_number(plmn); - tai.tac.from_number(args.tac); - // nr_cgi + // NR CGI nr_cgi.plmn_id.from_number(plmn); - // TODO Check how to build nr cell id - nr_cgi.nrcell_id.from_number((uint32_t)(args.gnb_id << 8) | args.cell_id); + + // NR CELL ID (36 bits) = gnb_id (22...32 bits) + cell_id (4...14 bits) + if (((uint8_t)log2(args.gnb_id) + (uint8_t)log2(args.cell_id) + 2) > 36) { + logger.error("gNB ID and Cell ID combination greater than 36 bits"); + return SRSRAN_ERROR; + } + // Consider moving sanity checks into the parsing function of the configs. + if (((uint8_t)log2(args.gnb_id) + 1) < 22) { + shift = 14; + } else { + shift = 36 - ((uint8_t)log2(args.gnb_id) + 1); + } + + nr_cgi.nrcell_id.from_number((uint64_t)args.gnb_id << shift | args.cell_id); + return SRSRAN_SUCCESS; +} + +/******************************************************************************* +/* RRC interface +********************************************************************************/ +void ngap::initial_ue(uint16_t rnti, + uint32_t gnb_cc_idx, + asn1::ngap_nr::rrcestablishment_cause_e cause, + srsran::unique_byte_buffer_t pdu) +{ + std::unique_ptr ue_ptr{new ue{this}}; + ue_ptr->ctxt.rnti = rnti; + ue_ptr->ctxt.gnb_cc_idx = gnb_cc_idx; + ue* u = users.add_user(std::move(ue_ptr)); + if (u == nullptr) { + logger.error("Failed to add rnti=0x%x", rnti); + return; + } + u->send_initialuemessage(cause, std::move(pdu), false); +} + +void ngap::initial_ue(uint16_t rnti, + uint32_t gnb_cc_idx, + asn1::ngap_nr::rrcestablishment_cause_e cause, + srsran::unique_byte_buffer_t pdu, + uint32_t s_tmsi) +{ + std::unique_ptr ue_ptr{new ue{this}}; + ue_ptr->ctxt.rnti = rnti; + ue_ptr->ctxt.gnb_cc_idx = gnb_cc_idx; + ue* u = users.add_user(std::move(ue_ptr)); + if (u == nullptr) { + logger.error("Failed to add rnti=0x%x", rnti); + return; + } + u->send_initialuemessage(cause, std::move(pdu), true, s_tmsi); +} + +void ngap::ue_ctxt_setup_complete(uint16_t rnti) +{ + ue* u = users.find_ue_rnti(rnti); + if (u == nullptr) { + return; + } + u->ue_ctxt_setup_complete(); +} + +/********************************************************* + * ngap::user_list class + *********************************************************/ + +ngap::ue* ngap::user_list::find_ue_rnti(uint16_t rnti) +{ + if (rnti == SRSRAN_INVALID_RNTI) { + return nullptr; + } + auto it = std::find_if( + users.begin(), users.end(), [rnti](const user_list::pair_type& v) { return v.second->ctxt.rnti == rnti; }); + return it != users.end() ? it->second.get() : nullptr; +} + +ngap::ue* ngap::user_list::find_ue_gnbid(uint32_t gnbid) +{ + auto it = users.find(gnbid); + return (it != users.end()) ? it->second.get() : nullptr; +} + +ngap::ue* ngap::user_list::find_ue_amfid(uint32_t amfid) +{ + auto it = std::find_if(users.begin(), users.end(), [amfid](const user_list::pair_type& v) { + return v.second->ctxt.amf_ue_ngap_id == amfid; + }); + return it != users.end() ? it->second.get() : nullptr; +} + +ngap::ue* ngap::user_list::add_user(std::unique_ptr user) +{ + static srslog::basic_logger& logger = srslog::fetch_basic_logger("NGAP"); + // Check for ID repetitions + if (find_ue_rnti(user->ctxt.rnti) != nullptr) { + logger.error("The user to be added with rnti=0x%x already exists", user->ctxt.rnti); + return nullptr; + } + if (find_ue_gnbid(user->ctxt.ran_ue_ngap_id) != nullptr) { + logger.error("The user to be added with ran ue ngap id=%d already exists", user->ctxt.ran_ue_ngap_id); + return nullptr; + } + if (user->ctxt.amf_ue_ngap_id.has_value() and find_ue_amfid(user->ctxt.amf_ue_ngap_id.value()) != nullptr) { + logger.error("The user to be added with amf id=%d already exists", user->ctxt.amf_ue_ngap_id.value()); + return nullptr; + } + auto p = users.insert(std::make_pair(user->ctxt.ran_ue_ngap_id, std::move(user))); + return p.second ? p.first->second.get() : nullptr; +} + +void ngap::user_list::erase(ue* ue_ptr) +{ + static srslog::basic_logger& logger = srslog::fetch_basic_logger("NGAP"); + auto it = users.find(ue_ptr->ctxt.ran_ue_ngap_id); + if (it == users.end()) { + logger.error("User to be erased does not exist"); + return; + } + users.erase(it); } /******************************************************************************* @@ -191,11 +312,11 @@ bool ngap::handle_amf_rx_msg(srsran::unique_byte_buffer_t pdu, amf_socket.close(); } - // Restart MME connection procedure if we lost connection + // Restart AMF connection procedure if we lost connection if (not amf_socket.is_open()) { amf_connected = false; if (ngsetup_proc.is_busy()) { - logger.error("Failed to initiate MME connection procedure, as it is already running."); + logger.error("Failed to initiate AMF connection procedure, as it is already running."); return false; } ngsetup_proc.launch(); @@ -208,6 +329,7 @@ bool ngap::handle_amf_rx_msg(srsran::unique_byte_buffer_t pdu, bool ngap::handle_ngap_rx_pdu(srsran::byte_buffer_t* pdu) { + // TODO: // Save message to PCAP // if (pcap != nullptr) { // pcap->write_ngap(pdu->msg, pdu->N_bytes); @@ -220,18 +342,19 @@ bool ngap::handle_ngap_rx_pdu(srsran::byte_buffer_t* pdu) logger.error(pdu->msg, pdu->N_bytes, "Failed to unpack received PDU"); cause_c cause; cause.set_protocol().value = cause_protocol_opts::transfer_syntax_error; - // send_error_indication(cause); + send_error_indication(cause); return false; } + // TODO: // log_ngap_msg(rx_pdu, srsran::make_span(*pdu), true); switch (rx_pdu.type().value) { - // case ngap_pdu_c::types_opts::init_msg: - // return handle_initiatingmessage(rx_pdu.init_msg()); + case ngap_pdu_c::types_opts::init_msg: + return handle_initiatingmessage(rx_pdu.init_msg()); case ngap_pdu_c::types_opts::successful_outcome: return handle_successfuloutcome(rx_pdu.successful_outcome()); - // case ngap_pdu_c::types_opts::unsuccessful_outcome: - // return handle_unsuccessfuloutcome(rx_pdu.unsuccessful_outcome()); + case ngap_pdu_c::types_opts::unsuccessful_outcome: + return handle_unsuccessfuloutcome(rx_pdu.unsuccessful_outcome()); default: logger.error("Unhandled PDU type %d", rx_pdu.type().value); return false; @@ -240,6 +363,19 @@ bool ngap::handle_ngap_rx_pdu(srsran::byte_buffer_t* pdu) return true; } +bool ngap::handle_initiatingmessage(const asn1::ngap_nr::init_msg_s& msg) +{ + switch (msg.value.type().value) { + case ngap_elem_procs_o::init_msg_c::types_opts::dl_nas_transport: + return handle_dlnastransport(msg.value.dl_nas_transport()); + case ngap_elem_procs_o::init_msg_c::types_opts::init_context_setup_request: + return handle_initialctxtsetuprequest(msg.value.init_context_setup_request()); + default: + logger.error("Unhandled initiating message: %s", msg.value.type().to_string()); + } + return true; +} + bool ngap::handle_successfuloutcome(const successful_outcome_s& msg) { switch (msg.value.type().value) { @@ -251,6 +387,17 @@ bool ngap::handle_successfuloutcome(const successful_outcome_s& msg) return true; } +bool ngap::handle_unsuccessfuloutcome(const asn1::ngap_nr::unsuccessful_outcome_s& msg) +{ + switch (msg.value.type().value) { + case ngap_elem_procs_o::unsuccessful_outcome_c::types_opts::ng_setup_fail: + return handle_ngsetupfailure(msg.value.ng_setup_fail()); + default: + logger.error("Unhandled unsuccessful outcome message: %s", msg.value.type().to_string()); + } + return true; +} + bool ngap::handle_ngsetupresponse(const asn1::ngap_nr::ng_setup_resp_s& msg) { ngsetupresponse = msg; @@ -258,6 +405,93 @@ bool ngap::handle_ngsetupresponse(const asn1::ngap_nr::ng_setup_resp_s& msg) ng_setup_proc_t::ngsetupresult res; res.success = true; ngsetup_proc.trigger(res); + + return true; +} + +bool ngap::handle_ngsetupfailure(const asn1::ngap_nr::ng_setup_fail_s& msg) +{ + std::string cause = get_cause(msg.protocol_ies.cause.value); + logger.error("NG Setup Failure. Cause: %s", cause.c_str()); + srsran::console("NG Setup Failure. Cause: %s\n", cause.c_str()); + return true; +} + +bool ngap::handle_dlnastransport(const asn1::ngap_nr::dl_nas_transport_s& msg) +{ + if (msg.ext) { + logger.warning("Not handling NGAP message extension"); + } + ue* u = + handle_ngapmsg_ue_id(msg.protocol_ies.ran_ue_ngap_id.value.value, msg.protocol_ies.amf_ue_ngap_id.value.value); + if (u == nullptr) { + return false; + } + + if (msg.protocol_ies.old_amf_present) { + logger.warning("Not handling OldAMF"); + } + + if (msg.protocol_ies.ran_paging_prio_present) { + logger.warning("Not handling RANPagingPriority"); + } + + if (msg.protocol_ies.mob_restrict_list_present) { + logger.warning("Not handling MobilityRestrictionList"); + } + + if (msg.protocol_ies.idx_to_rfsp_present) { + logger.warning("Not handling IndexToRFSP"); + } + + if (msg.protocol_ies.ue_aggregate_maximum_bit_rate_present) { + logger.warning("Not handling UEAggregateMaximumBitRate"); + } + + if (msg.protocol_ies.allowed_nssai_present) { + logger.warning("Not handling AllowedNSSAI"); + } + + // TODO: Pass NAS PDU once RRC interface is ready + /* srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + if (pdu == nullptr) { + logger.error("Fatal Error: Couldn't allocate buffer in ngap::run_thread()."); + return false; + } + memcpy(pdu->msg, msg.protocol_ies.nas_pdu.value.data(), msg.protocol_ies.nas_pdu.value.size()); + pdu->N_bytes = msg.protocol_ies.nas_pdu.value.size(); + rrc->write_dl_info(u->ctxt.rnti, std::move(pdu)); */ + return true; +} + +bool ngap::handle_initialctxtsetuprequest(const asn1::ngap_nr::init_context_setup_request_s& msg) +{ + ue* u = + handle_ngapmsg_ue_id(msg.protocol_ies.ran_ue_ngap_id.value.value, msg.protocol_ies.amf_ue_ngap_id.value.value); + if (u == nullptr) { + return false; + } + + u->ctxt.amf_pointer = msg.protocol_ies.guami.value.amf_pointer.to_number(); + u->ctxt.amf_set_id = msg.protocol_ies.guami.value.amf_set_id.to_number(); + u->ctxt.amf_region_id = msg.protocol_ies.guami.value.amf_region_id.to_number(); + + // Setup UE ctxt in RRC once interface is ready + /* if (not rrc->setup_ue_ctxt(u->ctxt.rnti, msg)) { + return false; + } */ + + /* if (msg.protocol_ies.nas_pdu_present) { + srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + if (pdu == nullptr) { + logger.error("Fatal Error: Couldn't allocate buffer in ngap::run_thread()."); + return false; + } + memcpy(pdu->msg, msg.protocol_ies.nas_pdu.value.data(), msg.protocol_ies.nas_pdu.value.size()); + pdu->N_bytes = msg.protocol_ies.nas_pdu.value.size(); + rrc->write_dl_info(u->ctxt.rnti, std::move(pdu)); + } */ + return true; } @@ -344,6 +578,134 @@ bool ngap::setup_ng() return sctp_send_ngap_pdu(pdu, 0, "ngSetupRequest"); } +/******************************************************************************* +/* NGAP message senders +********************************************************************************/ + +bool ngap::ue::send_initialuemessage(asn1::ngap_nr::rrcestablishment_cause_e cause, + srsran::unique_byte_buffer_t pdu, + bool has_tmsi, + uint32_t s_tmsi) +{ + if (not ngap_ptr->amf_connected) { + return false; + } + + ngap_pdu_c tx_pdu; + tx_pdu.set_init_msg().load_info_obj(ASN1_NGAP_NR_ID_INIT_UE_MSG); + init_ue_msg_ies_container& container = tx_pdu.init_msg().value.init_ue_msg().protocol_ies; + + // 5G-S-TMSI + if (has_tmsi) { + container.five_g_s_tmsi_present = true; + srsran::uint32_to_uint8(s_tmsi, container.five_g_s_tmsi.value.five_g_tmsi.data()); + container.five_g_s_tmsi.value.amf_set_id.from_number(ctxt.amf_set_id); + container.five_g_s_tmsi.value.amf_pointer.from_number(ctxt.amf_pointer); + } + + // RAN_UE_NGAP_ID + container.ran_ue_ngap_id.value = ctxt.ran_ue_ngap_id; + + // NAS_PDU + container.nas_pdu.value.resize(pdu->N_bytes); + memcpy(container.nas_pdu.value.data(), pdu->msg, pdu->N_bytes); + + // RRC Establishment Cause + container.rrcestablishment_cause.value = cause; + + // User Location Info + + // userLocationInformationNR + container.user_location_info.value.set_user_location_info_nr(); + container.user_location_info.value.user_location_info_nr().nr_cgi.nrcell_id = ngap_ptr->nr_cgi.nrcell_id; + container.user_location_info.value.user_location_info_nr().nr_cgi.plmn_id = ngap_ptr->nr_cgi.plmn_id; + container.user_location_info.value.user_location_info_nr().tai.plmn_id = ngap_ptr->tai.plmn_id; + container.user_location_info.value.user_location_info_nr().tai.tac = ngap_ptr->tai.tac; + + return ngap_ptr->sctp_send_ngap_pdu(tx_pdu, ctxt.rnti, "InitialUEMessage"); +} + +bool ngap::ue::send_ulnastransport(srsran::unique_byte_buffer_t pdu) +{ + if (not ngap_ptr->amf_connected) { + return false; + } + + ngap_pdu_c tx_pdu; + tx_pdu.set_init_msg().load_info_obj(ASN1_NGAP_NR_ID_UL_NAS_TRANSPORT); + asn1::ngap_nr::ul_nas_transport_ies_container& container = tx_pdu.init_msg().value.ul_nas_transport().protocol_ies; + + // AMF UE NGAP ID + container.amf_ue_ngap_id.value = ctxt.amf_ue_ngap_id.value(); + + // RAN UE NGAP ID + container.ran_ue_ngap_id.value = ctxt.ran_ue_ngap_id; + + // NAS PDU + container.nas_pdu.value.resize(pdu->N_bytes); + memcpy(container.nas_pdu.value.data(), pdu->msg, pdu->N_bytes); + + // User Location Info + // userLocationInformationNR + container.user_location_info.value.set_user_location_info_nr(); + container.user_location_info.value.user_location_info_nr().nr_cgi.nrcell_id = ngap_ptr->nr_cgi.nrcell_id; + container.user_location_info.value.user_location_info_nr().nr_cgi.plmn_id = ngap_ptr->nr_cgi.plmn_id; + container.user_location_info.value.user_location_info_nr().tai.plmn_id = ngap_ptr->tai.plmn_id; + container.user_location_info.value.user_location_info_nr().tai.tac = ngap_ptr->tai.tac; + + return ngap_ptr->sctp_send_ngap_pdu(tx_pdu, ctxt.rnti, "UplinkNASTransport"); +} + +void ngap::ue::ue_ctxt_setup_complete() +{ + ngap_pdu_c tx_pdu; + // Handle PDU Session List once RRC interface is ready + tx_pdu.set_successful_outcome().load_info_obj(ASN1_NGAP_NR_ID_INIT_CONTEXT_SETUP); + auto& container = tx_pdu.successful_outcome().value.init_context_setup_resp().protocol_ies; +} + +bool ngap::send_error_indication(const asn1::ngap_nr::cause_c& cause, + srsran::optional ran_ue_ngap_id, + srsran::optional amf_ue_ngap_id) +{ + if (not amf_connected) { + return false; + } + + ngap_pdu_c tx_pdu; + tx_pdu.set_init_msg().load_info_obj(ASN1_NGAP_NR_ID_ERROR_IND); + auto& container = tx_pdu.init_msg().value.error_ind().protocol_ies; + + uint16_t rnti = SRSRAN_INVALID_RNTI; + container.ran_ue_ngap_id_present = ran_ue_ngap_id.has_value(); + if (ran_ue_ngap_id.has_value()) { + container.ran_ue_ngap_id.value = ran_ue_ngap_id.value(); + ue* user_ptr = users.find_ue_gnbid(ran_ue_ngap_id.value()); + rnti = user_ptr != nullptr ? user_ptr->ctxt.rnti : SRSRAN_INVALID_RNTI; + } + container.amf_ue_ngap_id_present = amf_ue_ngap_id.has_value(); + if (amf_ue_ngap_id.has_value()) { + container.amf_ue_ngap_id.value = amf_ue_ngap_id.value(); + } + + container.cause_present = true; + container.cause.value = cause; + + return sctp_send_ngap_pdu(tx_pdu, rnti, "Error Indication"); +} + +/******************************************************************************* +/* ngap::ue Class +********************************************************************************/ + +ngap::ue::ue(ngap* ngap_ptr_) : ngap_ptr(ngap_ptr_) +{ + ctxt.ran_ue_ngap_id = ngap_ptr->next_gnb_ue_ngap_id++; + gettimeofday(&ctxt.init_timestamp, nullptr); + + stream_id = ngap_ptr->next_ue_stream_id; +} + /******************************************************************************* /* General helpers ********************************************************************************/ @@ -374,14 +736,14 @@ bool ngap::sctp_send_ngap_pdu(const asn1::ngap_nr::ngap_pdu_c& tx_pdu, uint32_t // } if (rnti != SRSRAN_INVALID_RNTI) { - logger.info(buf->msg, buf->N_bytes, "Tx S1AP SDU, %s, rnti=0x%x", procedure_name, rnti); + logger.info(buf->msg, buf->N_bytes, "Tx NGAP SDU, %s, rnti=0x%x", procedure_name, rnti); } else { - logger.info(buf->msg, buf->N_bytes, "Tx S1AP SDU, %s", procedure_name); + logger.info(buf->msg, buf->N_bytes, "Tx NGAP SDU, %s", procedure_name); } - // TODO: when user list is ready - // uint16_t streamid = rnti == SRSRAN_INVALID_RNTI ? NONUE_STREAM_ID : users.find_ue_rnti(rnti)->stream_id; - uint16_t streamid = 0; - ssize_t n_sent = sctp_sendmsg(amf_socket.fd(), + + uint16_t streamid = rnti == SRSRAN_INVALID_RNTI ? NONUE_STREAM_ID : users.find_ue_rnti(rnti)->stream_id; + + ssize_t n_sent = sctp_sendmsg(amf_socket.fd(), buf->msg, buf->N_bytes, (struct sockaddr*)&amf_addr, @@ -393,13 +755,87 @@ bool ngap::sctp_send_ngap_pdu(const asn1::ngap_nr::ngap_pdu_c& tx_pdu, uint32_t 0); if (n_sent == -1) { if (rnti != SRSRAN_INVALID_RNTI) { - logger.error("Error: Failure at Tx S1AP SDU, %s, rnti=0x%x", procedure_name, rnti); + logger.error("Error: Failure at Tx NGAP SDU, %s, rnti=0x%x", procedure_name, rnti); } else { - logger.error("Error: Failure at Tx S1AP SDU, %s", procedure_name); + logger.error("Error: Failure at Tx NGAP SDU, %s", procedure_name); } return false; } return true; } +/** + * Helper method to find user based on the ran_ue_ngap_id stored in an S1AP Msg, and update amf_ue_ngap_id + * @param gnb_id ran_ue_ngap_id value stored in NGAP message + * @param amf_id amf_ue_ngap_id value stored in NGAP message + * @return pointer to user if it has been found + */ +ngap::ue* ngap::handle_ngapmsg_ue_id(uint32_t gnb_id, uint32_t amf_id) +{ + ue* user_ptr = users.find_ue_gnbid(gnb_id); + ue* user_amf_ptr = nullptr; + cause_c cause; + // TODO: Introduce proper error handling for faulty ids + if (user_ptr != nullptr) { + if (user_ptr->ctxt.amf_ue_ngap_id == amf_id) { + return user_ptr; + } + + user_amf_ptr = users.find_ue_amfid(amf_id); + if (not user_ptr->ctxt.amf_ue_ngap_id.has_value() and user_amf_ptr == nullptr) { + user_ptr->ctxt.amf_ue_ngap_id = amf_id; + return user_ptr; + } + + logger.warning("AMF UE NGAP ID=%d not found - discarding message", amf_id); + if (user_amf_ptr != nullptr) { + cause.set_radio_network().value = cause_radio_network_opts::unknown_target_id; + } + + } else { + user_amf_ptr = users.find_ue_amfid(amf_id); + logger.warning("RAN UE NGAP ID=%d not found - discarding message", gnb_id); + if (user_amf_ptr != nullptr) { + cause.set_radio_network().value = cause_radio_network_opts::unknown_local_ue_ngap_id; + } + } + + send_error_indication(cause, gnb_id, amf_id); + + if (user_ptr != nullptr) { + // rrc->release_ue(user_ptr->ctxt.rnti); + } + if (user_amf_ptr != nullptr and user_amf_ptr != user_ptr) { + // rrc->release_ue(user_mme_ptr->ctxt.rnti); + } + return nullptr; +} + +std::string ngap::get_cause(const cause_c& c) +{ + std::string cause = c.type().to_string(); + cause += " - "; + switch (c.type().value) { + case cause_c::types_opts::radio_network: + cause += c.radio_network().to_string(); + break; + case cause_c::types_opts::transport: + cause += c.transport().to_string(); + break; + case cause_c::types_opts::nas: + cause += c.nas().to_string(); + break; + case cause_c::types_opts::protocol: + cause += c.protocol().to_string(); + break; + case cause_c::types_opts::misc: + cause += c.misc().to_string(); + break; + default: + cause += "unknown"; + break; + } + return cause; +} + } // namespace srsenb diff --git a/srsenb/test/CMakeLists.txt b/srsenb/test/CMakeLists.txt index b648a8e05..a7d93ea1f 100644 --- a/srsenb/test/CMakeLists.txt +++ b/srsenb/test/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(phy) add_subdirectory(upper) add_subdirectory(rrc) add_subdirectory(s1ap) +add_subdirectory(ngap) add_executable(enb_metrics_test enb_metrics_test.cc ../src/metrics_stdout.cc ../src/metrics_csv.cc) target_link_libraries(enb_metrics_test srsran_phy srsran_common) diff --git a/srsenb/test/ngap/CMakeLists.txt b/srsenb/test/ngap/CMakeLists.txt new file mode 100644 index 000000000..0824d8aac --- /dev/null +++ b/srsenb/test/ngap/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright 2013-2021 Software Radio Systems Limited +# +# By using this file, you agree to the terms and conditions set +# forth in the LICENSE file which can be found at the top level of +# the distribution. +# + +add_executable(ngap_test ngap_test.cc) +target_link_libraries(ngap_test srsran_common ngap_nr_asn1 srsenb_upper srsran_upper ngap_nr_asn1 srsgnb_upper srsgnb_ngap ${SCTP_LIBRARIES}) + +add_test(ngap_test ngap_test) + diff --git a/srsenb/test/ngap/ngap_test.cc b/srsenb/test/ngap/ngap_test.cc new file mode 100644 index 000000000..d21bf7223 --- /dev/null +++ b/srsenb/test/ngap/ngap_test.cc @@ -0,0 +1,152 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsenb/hdr/stack/ngap/ngap.h" +#include "srsran/common/network_utils.h" +#include "srsran/common/test_common.h" + +using namespace srsenb; + +struct amf_dummy { + amf_dummy(const char* addr_str_, int port_) : addr_str(addr_str_), port(port_) + { + srsran::net_utils::set_sockaddr(&amf_sockaddr, addr_str, port); + { + using namespace srsran::net_utils; + fd = open_socket(addr_family::ipv4, socket_type::seqpacket, protocol_type::SCTP); + TESTASSERT(fd > 0); + TESTASSERT(bind_addr(fd, amf_sockaddr)); + } + + int success = listen(fd, SOMAXCONN); + srsran_assert(success == 0, "Failed to listen to incoming SCTP connections"); + } + + ~amf_dummy() + { + if (fd > 0) { + close(fd); + } + } + + srsran::unique_byte_buffer_t read_msg(sockaddr_in* sockfrom = nullptr) + { + srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + sockaddr_in from = {}; + socklen_t fromlen = sizeof(from); + sctp_sndrcvinfo sri = {}; + int flags = 0; + ssize_t n_recv = sctp_recvmsg(fd, pdu->msg, pdu->get_tailroom(), (struct sockaddr*)&from, &fromlen, &sri, &flags); + if (n_recv > 0) { + if (sockfrom != nullptr) { + *sockfrom = from; + } + pdu->N_bytes = n_recv; + } + return pdu; + } + + const char* addr_str; + int port; + struct sockaddr_in amf_sockaddr = {}; + int fd; + srsran::unique_byte_buffer_t last_sdu; +}; + +struct dummy_socket_manager : public srsran::socket_manager_itf { + dummy_socket_manager() : srsran::socket_manager_itf(srslog::fetch_basic_logger("TEST")) {} + + /// Register (fd, callback). callback is called within socket thread when fd has data. + bool add_socket_handler(int fd, recv_callback_t handler) final + { + if (s1u_fd > 0) { + return false; + } + s1u_fd = fd; + callback = std::move(handler); + return true; + } + + /// remove registered socket fd + bool remove_socket(int fd) final + { + if (s1u_fd < 0) { + return false; + } + s1u_fd = -1; + return true; + } + + int s1u_fd; + recv_callback_t callback; +}; + +void run_ng_setup(ngap& ngap_obj, amf_dummy& amf) +{ + asn1::ngap_nr::ngap_pdu_c ngap_pdu; + + // gNB -> AMF: NG Setup Request + srsran::unique_byte_buffer_t sdu = amf.read_msg(); + TESTASSERT(sdu->N_bytes > 0); + asn1::cbit_ref cbref(sdu->msg, sdu->N_bytes); + TESTASSERT(ngap_pdu.unpack(cbref) == asn1::SRSASN_SUCCESS); + TESTASSERT(ngap_pdu.type().value == asn1::ngap_nr::ngap_pdu_c::types_opts::init_msg); + TESTASSERT(ngap_pdu.init_msg().proc_code == ASN1_NGAP_NR_ID_NG_SETUP); + + // AMF -> gNB: ng Setup Response + sockaddr_in amf_addr = {}; + sctp_sndrcvinfo rcvinfo = {}; + int flags = 0; + + uint8_t ng_setup_resp[] = {0x20, 0x15, 0x00, 0x55, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x31, 0x17, 0x00, 0x61, 0x6d, + 0x61, 0x72, 0x69, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x61, 0x6d, 0x66, 0x2e, 0x35, 0x67, 0x63, + 0x2e, 0x6d, 0x6e, 0x63, 0x30, 0x30, 0x31, 0x2e, 0x6d, 0x63, 0x63, 0x30, 0x30, 0x31, 0x2e, + 0x33, 0x67, 0x70, 0x70, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x6f, 0x72, 0x67, + 0x00, 0x60, 0x00, 0x08, 0x00, 0x00, 0x00, 0xf1, 0x10, 0x80, 0x01, 0x01, 0x00, 0x56, 0x40, + 0x01, 0x32, 0x00, 0x50, 0x00, 0x08, 0x00, 0x00, 0xf1, 0x10, 0x00, 0x00, 0x00, 0x08}; + memcpy(sdu->msg, ng_setup_resp, sizeof(ng_setup_resp)); + sdu->N_bytes = sizeof(ng_setup_resp); + TESTASSERT(ngap_obj.handle_amf_rx_msg(std::move(sdu), amf_addr, rcvinfo, flags)); +} + +int main(int argc, char** argv) +{ + // Setup logging. + auto& logger = srslog::fetch_basic_logger("NGAP"); + logger.set_level(srslog::basic_levels::debug); + logger.set_hex_dump_max_size(-1); + + srsran::task_scheduler task_sched; + dummy_socket_manager rx_sockets; + ngap ngap_obj(&task_sched, logger, &rx_sockets); + + const char* amf_addr_str = "127.0.0.1"; + const uint32_t AMF_PORT = 38412; + amf_dummy amf(amf_addr_str, AMF_PORT); + + ngap_args_t args = {}; + args.cell_id = 0x01; + args.gnb_id = 0x19B; + args.mcc = 907; + args.mnc = 70; + args.ngc_bind_addr = "127.0.0.100"; + args.tac = 7; + args.gtp_bind_addr = "127.0.0.100"; + args.amf_addr = amf_addr_str; + args.gnb_name = "srsgnb01"; + rrc_interface_ngap_nr rrc; + ngap_obj.init(args, &rrc); + + // Start the log backend. + srsran::test_init(argc, argv); + run_ng_setup(ngap_obj, amf); +} \ No newline at end of file