diff --git a/lib/include/srslte/interfaces/rrc_interface_types.h b/lib/include/srslte/interfaces/rrc_interface_types.h index 143873140..ce25f0b1c 100644 --- a/lib/include/srslte/interfaces/rrc_interface_types.h +++ b/lib/include/srslte/interfaces/rrc_interface_types.h @@ -87,6 +87,34 @@ struct plmn_id_t { uint8_t* plmn_ptr = (uint8_t*)&s1ap_plmn; memcpy(&plmn_bytes[0], plmn_ptr + 1, 3); } + void to_rrctl_bytes(uint8_t *mcc_buf, uint8_t *mnc_buf) const + { + mcc_buf[0] = ((mcc[1] & 0x0f) << 4) | (mcc[0] & 0x0f); + mcc_buf[1] = (0x0f << 4) | (mcc[2] & 0x0f); + + mnc_buf[0] = ((mnc[1] & 0x0f) << 4) | (mnc[0] & 0x0f); + if (nof_mnc_digits > 2) + mnc_buf[1] = (0x0f << 4) | (mnc[2] & 0x0f); + else + mnc_buf[1] = 0xff; + } + void from_rrctl_bytes(const uint8_t *mcc_buf, const uint8_t *mnc_buf) + { + mcc[0] = mcc_buf[0] & 0x0f; + mcc[1] = mcc_buf[0] >> 4; + mcc[2] = mcc_buf[1] & 0x0f; + + mnc[0] = mnc_buf[0] & 0x0f; + mnc[1] = mnc_buf[0] >> 4; + + if (mnc_buf[1] != 0xff) { + nof_mnc_digits = 3; + mnc[2] = mnc_buf[1] & 0x0f; + } else { + nof_mnc_digits = 2; + mnc[2] = 0x00; + } + } int from_string(const std::string& plmn_str) { if (plmn_str.size() < 5 or plmn_str.size() > 6) { diff --git a/srsue/hdr/stack/upper/nas_ext.h b/srsue/hdr/stack/upper/nas_ext.h index 471ff709a..5802f130e 100644 --- a/srsue/hdr/stack/upper/nas_ext.h +++ b/srsue/hdr/stack/upper/nas_ext.h @@ -37,6 +37,7 @@ #include "srsue/hdr/stack/upper/nas_ext.h" #include "srsue/hdr/stack/upper/nas_extif.h" #include "srsue/hdr/stack/upper/nas_metrics.h" +#include "srsue/hdr/stack/upper/rrctl.h" using srslte::byte_buffer_t; @@ -79,6 +80,18 @@ private: // Interface to an external NAS entity std::unique_ptr iface; + + // RRCTL message handlers + void handle_rrctl_reset(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len); + void handle_rrctl_plmn_search(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len); + void handle_rrctl_plmn_select(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len); + void handle_rrctl_conn_establish(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len); + void handle_rrctl_conn_release(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len); + void handle_rrctl_data(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len); + void handle_rrctl_param(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len); + + void rrctl_send_confirm(rrctl::proto::msg_type type); + void rrctl_send_error(rrctl::proto::msg_type type); }; } // namespace srsue diff --git a/srsue/hdr/stack/upper/rrctl.h b/srsue/hdr/stack/upper/rrctl.h new file mode 100644 index 000000000..e17db4534 --- /dev/null +++ b/srsue/hdr/stack/upper/rrctl.h @@ -0,0 +1,162 @@ +/* + * Copyright 2020 Software Radio Systems Limited + * Author: Vadim Yanitskiy + * Sponsored by Positive Technologies + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include +#include + +#include "srslte/common/common.h" +#include "srslte/interfaces/ue_interfaces.h" + +namespace rrctl { + +namespace proto { + + enum msg_type { + RRCTL_RESET = 0x00, + RRCTL_DATA, + RRCTL_PLMN_SEARCH, + RRCTL_PLMN_SELECT, + RRCTL_CONN_ESTABLISH, + RRCTL_CONN_RELEASE, + RRCTL_PAGING, + RRCTL_PARAM, + }; + + enum msg_disc { + RRCTL_REQ = 0x00, + RRCTL_IND = 0x01, + RRCTL_CNF = 0x02, + RRCTL_ERR = 0x03, + }; + + struct msg_hdr { +#if defined(__LITTLE_ENDIAN_BITFIELD) + uint8_t disc:2, type:6; +#elif defined(__BIG_ENDIAN_BITFIELD) + uint8_t type:6, disc:2; +#else + #error "Please fix " +#endif + uint8_t rfu; // Reserved + uint16_t len; + uint8_t data[0]; + } __attribute__((packed)); + + struct msg_plmn_search_res { + uint8_t nof_plmns; + struct plmn { + uint8_t mcc[2]; + uint8_t mnc[2]; + uint16_t tac; + } plmns[16]; + } __attribute__((packed)); + + struct msg_plmn_select_req { + uint8_t mcc[2]; + uint8_t mnc[2]; + } __attribute__((packed)); + + struct msg_conn_establish_req { + uint8_t cause; + uint8_t pdu[0]; + } __attribute__((packed)); + + struct msg_data { + uint32_t lcid; + uint8_t pdu[0]; + } __attribute__((packed)); + + struct __mmec_m_tmsi { + uint8_t mmec; + uint32_t m_tmsi; + } __attribute__((packed)); + + struct msg_paging_ind { + struct __mmec_m_tmsi ueid; + } __attribute__((packed)); + + enum msg_param_type { + RRCTL_PARAM_UEID = 0x00, + }; + + struct msg_param_req { + uint8_t type; + uint8_t len; + union { + struct __mmec_m_tmsi ueid; + } u; + } __attribute__((packed)); + + struct msg { + struct msg_hdr hdr; + union { + struct msg_data data; + struct msg_param_req param_req; + struct msg_paging_ind paging_ind; + struct msg_plmn_search_res plmn_search_res; + struct msg_plmn_select_req plmn_select_req; + struct msg_conn_establish_req conn_establish_req; + } u; + } __attribute__((packed)); + + std::string msg_hdr_desc(proto::msg_type type, proto::msg_disc disc, uint16_t len = 0); + +} // namespace proto + +namespace codec { + +class error : public std::runtime_error { +public: + explicit error(const std::string& msg) : std::runtime_error(msg) {}; +}; + +void enc_hdr(srslte::byte_buffer_t& buf, + proto::msg_type type, + proto::msg_disc disc, + uint16_t len = 0); +const uint8_t* dec_hdr(const srslte::byte_buffer_t& buf, + proto::msg_type& type, + proto::msg_disc& disc, + uint16_t& len); + +void enc_plmn_search_res(srslte::byte_buffer_t& buf, + const srsue::rrc_interface_nas::found_plmn_t* plmns, + size_t nof_plmns); + +void dec_plmn_select_req(std::pair& mcc_mnc, + const uint8_t* payload, size_t len); + +void dec_conn_establish_req(srslte::establishment_cause_t& cause, + const uint8_t*& pdu, size_t& pdu_len, + const uint8_t* payload, size_t len); + +void enc_data_ind(srslte::byte_buffer_t& buf, + const uint8_t *pdu, size_t pdu_len, + uint32_t lcid); + +void enc_paging_ind(srslte::byte_buffer_t& buf, + srslte::s_tmsi_t* ue_identity); + +} // namespace codec + +} // namespace rrctl diff --git a/srsue/src/stack/upper/CMakeLists.txt b/srsue/src/stack/upper/CMakeLists.txt index 8b9ca981e..d7fd4bc1f 100644 --- a/srsue/src/stack/upper/CMakeLists.txt +++ b/srsue/src/stack/upper/CMakeLists.txt @@ -18,7 +18,7 @@ # and at http://www.gnu.org/licenses/. # -set(SOURCES gw.cc nas.cc nas_ext.cc nas_extif.cc usim_base.cc usim.cc tft_packet_filter.cc) +set(SOURCES gw.cc nas.cc nas_ext.cc nas_extif.cc rrctl.cc usim_base.cc usim.cc tft_packet_filter.cc) if(HAVE_PCSC) list(APPEND SOURCES "pcsc_usim.cc") diff --git a/srsue/src/stack/upper/nas_ext.cc b/srsue/src/stack/upper/nas_ext.cc index de5147835..e86bcdab1 100644 --- a/srsue/src/stack/upper/nas_ext.cc +++ b/srsue/src/stack/upper/nas_ext.cc @@ -45,14 +45,172 @@ void nas_ext::init(usim_interface_nas* usim_, rrc_interface_nas* rrc_, gw_interf rrc = rrc_; gw = gw_; - auto rx_cb = [this](const srslte::byte_buffer_t& pdu) { - // TODO: parse received payload + // RRCTL PDU handler + auto rrctl_rx_cb = [this](const srslte::byte_buffer_t& pdu) { + rrctl::proto::msg_type type; + rrctl::proto::msg_disc disc; + const uint8_t* payload; + std::string desc; + uint16_t length; + + // Parse the message header + try { + payload = rrctl::codec::dec_hdr(pdu, type, disc, length); + desc = rrctl::proto::msg_hdr_desc(type, disc, length); + nas_log->info("Got RRCTL message: %s\n", desc.c_str()); + } catch (const rrctl::codec::error& e) { + nas_log->warning("Got malformed RRCTL message: %s\n", e.what()); + return; + } + + // Call the corresponding handler + switch (type) { + case rrctl::proto::RRCTL_RESET: + handle_rrctl_reset(disc, payload, length); + break; + case rrctl::proto::RRCTL_PLMN_SEARCH: + handle_rrctl_plmn_search(disc, payload, length); + break; + case rrctl::proto::RRCTL_PLMN_SELECT: + handle_rrctl_plmn_select(disc, payload, length); + break; + case rrctl::proto::RRCTL_CONN_ESTABLISH: + handle_rrctl_conn_establish(disc, payload, length); + break; + case rrctl::proto::RRCTL_DATA: + handle_rrctl_data(disc, payload, length); + break; + case rrctl::proto::RRCTL_PARAM: + handle_rrctl_param(disc, payload, length); + break; + case rrctl::proto::RRCTL_CONN_RELEASE: + default: + nas_log->warning("%s is not handled\n", desc.c_str()); + } }; - std::unique_ptr iface_(new nas_extif_unix(rx_cb, cfg.sock_path)); + std::unique_ptr iface_(new nas_extif_unix(rrctl_rx_cb, cfg.sock_path)); iface = std::move(iface_); } +void nas_ext::rrctl_send_confirm(rrctl::proto::msg_type type) +{ + srslte::byte_buffer_t pdu; + + rrctl::codec::enc_hdr(pdu, type, rrctl::proto::RRCTL_CNF); + iface->write(pdu); +} + +void nas_ext::rrctl_send_error(rrctl::proto::msg_type type) +{ + srslte::byte_buffer_t pdu; + + rrctl::codec::enc_hdr(pdu, type, rrctl::proto::RRCTL_ERR); + iface->write(pdu); +} + +void nas_ext::handle_rrctl_reset(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len) +{ + // TODO: do we need to reset anything? + rrctl_send_confirm(rrctl::proto::RRCTL_RESET); +} + +void nas_ext::handle_rrctl_plmn_search(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len) +{ + // Response to be sent when RRC responds + rrc->plmn_search(); +} + +void nas_ext::handle_rrctl_plmn_select(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len) +{ + std::pair mcc_mnc; + srslte::plmn_id_t plmn_id; + + // Parse PLMN ID + try { + rrctl::codec::dec_plmn_select_req(mcc_mnc, msg, len); + } catch (const rrctl::codec::error& e) { + nas_log->warning("Failed to parse RRCTL message: %s\n", e.what()); + rrctl_send_error(rrctl::proto::RRCTL_PLMN_SELECT); + return; + } + + if (plmn_id.from_number(mcc_mnc.first, mcc_mnc.second) != SRSLTE_SUCCESS) { + nas_log->warning("Failed to parse PLMN ID from PLMN Select Request\n"); + rrctl_send_error(rrctl::proto::RRCTL_PLMN_SELECT); + } + + rrc->plmn_select(plmn_id); + rrctl_send_confirm(rrctl::proto::RRCTL_PLMN_SELECT); +} + +void nas_ext::handle_rrctl_conn_establish(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len) +{ + srslte::establishment_cause_t cause; + const uint8_t* pdu; + size_t pdu_len; + + try { + rrctl::codec::dec_conn_establish_req(cause, pdu, pdu_len, msg, len); + } catch (const rrctl::codec::error& e) { + nas_log->warning("Failed to parse RRCTL message: %s\n", e.what()); + rrctl_send_error(rrctl::proto::RRCTL_CONN_ESTABLISH); + return; + } + + // Allocate a new NAS PDU on heap + unique_byte_buffer_t nas_pdu = srslte::allocate_unique_buffer(*pool, true); + nas_pdu->append_bytes(pdu, pdu_len); + + rrc->connection_request(cause, std::move(nas_pdu)); +} + +void nas_ext::handle_rrctl_data(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len) +{ + if (not rrc->is_connected()) { + nas_log->warning("Received DATA.req, but there is no active connection\n"); + rrctl_send_error(rrctl::proto::RRCTL_DATA); + return; + } + + // Allocate a new NAS PDU on heap + unique_byte_buffer_t nas_pdu = srslte::allocate_unique_buffer(*pool, true); + nas_pdu->append_bytes(msg, len); + + rrc->write_sdu(std::move(nas_pdu)); + rrctl_send_confirm(rrctl::proto::RRCTL_DATA); +} + +void nas_ext::handle_rrctl_param(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len) +{ + const struct rrctl::proto::msg_param_req* param; + srslte::s_tmsi_t ue_identity; + + param = reinterpret_cast (msg); + nas_log->warning("Rx PARAM.req (type=%02x, len=%u)\n", param->type, param->len); + + if (param->len != (len - 2)) { /* XXX: type + length */ + nas_log->info("Received malformed PARAM.req (len=%u)\n", param->len); + rrctl_send_error(rrctl::proto::RRCTL_PARAM); + return; + } + + switch (param->type) { + case rrctl::proto::RRCTL_PARAM_UEID: + ue_identity.m_tmsi = ntohl(param->u.ueid.m_tmsi); + ue_identity.mmec = param->u.ueid.mmec; + + nas_log->info("Setting UEID: mmec=%x m_tmsi=%x\n", + ue_identity.mmec, ue_identity.m_tmsi); + rrc->set_ue_identity(ue_identity); + rrctl_send_confirm(rrctl::proto::RRCTL_PARAM); + break; + default: + nas_log->warning("Unhandled PARAM.req type (0x%02x)\n", param->type); + rrctl_send_error(rrctl::proto::RRCTL_PARAM); + } +} + void nas_ext::get_metrics(nas_metrics_t* m) { nas_metrics_t metrics = {}; @@ -100,15 +258,25 @@ void nas_ext::left_rrc_connected() {} bool nas_ext::paging(srslte::s_tmsi_t* ue_identity) { + srslte::byte_buffer_t msg; + nas_log->info("Received paging from RRC\n"); - // TODO: send PAGING.ind to the external entity + + rrctl::codec::enc_paging_ind(msg, ue_identity); + iface->write(msg); + + // TODO: what are we supposed to return? return false; } void nas_ext::write_pdu(uint32_t lcid, srslte::unique_byte_buffer_t pdu) { + srslte::byte_buffer_t msg; + nas_log->info_hex(pdu->msg, pdu->N_bytes, "Received DL %s PDU from RRC\n", rrc->get_rb_name(lcid).c_str()); - // TODO: send DATA.ind to the external entity + + rrctl::codec::enc_data_ind(msg, pdu->msg, pdu->N_bytes, lcid); + iface->write(msg); } uint32_t nas_ext::get_k_enb_count() @@ -145,15 +313,30 @@ void nas_ext::plmn_search_completed( const rrc_interface_nas::found_plmn_t found_plmns[rrc_interface_nas::MAX_FOUND_PLMNS], int nof_plmns) { + srslte::byte_buffer_t pdu; + nas_log->info("RRC has completed PLMN search, %d carriers found\n", nof_plmns); - // TODO: send PLMN_SEARCH.res to the external entity + + // Send PLMN_SEARCH.res to an external entity + if (nof_plmns >= 0) { + rrctl::codec::enc_plmn_search_res(pdu, found_plmns, nof_plmns); + iface->write(pdu); + } else { + nas_log->warning("PLMN search completed with an error\n"); + rrctl_send_error(rrctl::proto::RRCTL_PLMN_SEARCH); + } } bool nas_ext::connection_request_completed(bool outcome) { nas_log->info("RRC has %s connection establisment\n", outcome ? "completed" : "failed"); - // TODO: send CONN_ESTABLISH.res to the external entity - return false; + + if (outcome) + rrctl_send_confirm(rrctl::proto::RRCTL_CONN_ESTABLISH); + else + rrctl_send_error(rrctl::proto::RRCTL_CONN_ESTABLISH); + + return false; // FIXME: what should we return here? } } // namespace srsue diff --git a/srsue/src/stack/upper/rrctl.cc b/srsue/src/stack/upper/rrctl.cc new file mode 100644 index 000000000..0d1f78f87 --- /dev/null +++ b/srsue/src/stack/upper/rrctl.cc @@ -0,0 +1,218 @@ +/* + * Copyright 2020 Software Radio Systems Limited + * Author: Vadim Yanitskiy + * Sponsored by Positive Technologies + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include +#include + +#include "srsue/hdr/stack/upper/rrctl.h" + +namespace rrctl { + +namespace proto { + +std::string msg_hdr_desc(proto::msg_type type, proto::msg_disc disc, uint16_t len) +{ + std::string desc; + + switch (type) { + case RRCTL_RESET: + desc += "Reset"; + break; + case RRCTL_PLMN_SEARCH: + desc += "PLMN Search"; + break; + case RRCTL_PLMN_SELECT: + desc += "PLMN Select"; + break; + case RRCTL_CONN_ESTABLISH: + desc += "Connection Establish"; + break; + case RRCTL_CONN_RELEASE: + desc += "Connection Release"; + break; + case RRCTL_DATA: + desc += "Data (PDU)"; + break; + case RRCTL_PAGING: + desc += "Paging"; + break; + case RRCTL_PARAM: + desc += "Parameter"; + break; + default: + desc += ""; + } + + desc += " "; + + switch (disc) { + case RRCTL_REQ: + desc += "Request"; + break; + case RRCTL_IND: + desc += "Indication"; + break; + case RRCTL_CNF: + desc += "Confirmation"; + break; + case RRCTL_ERR: + desc += "Error"; + break; + } + + if (len > 0) { + desc += " (length "; + desc += std::to_string(len); + desc += ")"; + } + + return desc; +} + +} + +namespace codec { + +void enc_hdr(srslte::byte_buffer_t& buf, + proto::msg_type type, + proto::msg_disc disc, + uint16_t len) +{ + struct proto::msg_hdr hdr = { +#if defined(__LITTLE_ENDIAN_BITFIELD) + .disc = (uint8_t) disc, + .type = (uint8_t) type, +#elif defined(__BIG_ENDIAN_BITFIELD) + .type = (uint8_t) type, + .disc = (uint8_t) disc, +#else + #error "Please fix " +#endif + .len = ntohs(len), + }; + + buf.append_bytes((const uint8_t*) &hdr, sizeof(hdr)); +} + +const uint8_t* dec_hdr(const srslte::byte_buffer_t& buf, + proto::msg_type& type, + proto::msg_disc& disc, + uint16_t& len) +{ + const struct proto::msg_hdr* hdr; + + // Make sure at least header is present + if (buf.N_bytes < sizeof(*hdr)) + throw codec::error("header is too short"); + + hdr = reinterpret_cast (buf.msg); + type = static_cast (hdr->type); + disc = static_cast (hdr->disc); + len = htons(hdr->len); + + // Make sure the whole message fits + if (buf.N_bytes < sizeof(*hdr) + len) + throw codec::error("body is too short"); + + // Return pointer to the payload (if present) + return len ? hdr->data : NULL; +} + +void enc_plmn_search_res(srslte::byte_buffer_t& buf, + const srsue::rrc_interface_nas::found_plmn_t* plmns, + size_t nof_plmns) +{ + struct proto::msg_plmn_search_res msg; + uint16_t msg_len; + + if (nof_plmns > 16) + throw codec::error("too many PLMNS to encode"); + msg.nof_plmns = static_cast (nof_plmns); + + for (size_t i = 0; i < nof_plmns; i++) { + plmns[i].plmn_id.to_rrctl_bytes(msg.plmns[i].mcc, msg.plmns[i].mnc); + msg.plmns[i].tac = htons(plmns[i].tac); + } + + msg_len = sizeof(proto::msg_plmn_search_res::plmn) * nof_plmns + 1; + enc_hdr(buf, proto::RRCTL_PLMN_SEARCH, proto::RRCTL_CNF, msg_len); + buf.append_bytes((uint8_t *) &msg, msg_len); +} + +void dec_plmn_select_req(std::pair& mcc_mnc, + const uint8_t* payload, size_t len) +{ + const struct proto::msg_plmn_select_req* msg; + struct srslte::plmn_id_t plmn_id; + + if (len < sizeof(*msg)) + throw codec::error("body is too short"); + + msg = reinterpret_cast (payload); + plmn_id.from_rrctl_bytes(msg->mcc, msg->mnc); + mcc_mnc = plmn_id.to_number(); +} + +void dec_conn_establish_req(srslte::establishment_cause_t& cause, + const uint8_t*& pdu, size_t& pdu_len, + const uint8_t* payload, size_t len) +{ + const struct proto::msg_conn_establish_req* msg; + + if (len < sizeof(*msg)) + throw codec::error("body is too short"); + + msg = reinterpret_cast (payload); + cause = static_cast (msg->cause); + pdu_len = len - 1; + pdu = msg->pdu; +} + +void enc_data_ind(srslte::byte_buffer_t& buf, + const uint8_t *pdu, size_t pdu_len, + uint32_t lcid) +{ + struct proto::msg_data msg; + + msg.lcid = htonl(lcid); + + enc_hdr(buf, proto::RRCTL_DATA, proto::RRCTL_IND, sizeof(msg) + pdu_len); + buf.append_bytes((const uint8_t*) &msg, sizeof(msg)); + buf.append_bytes(pdu, pdu_len); +} + +void enc_paging_ind(srslte::byte_buffer_t& buf, + srslte::s_tmsi_t* ue_identity) +{ + struct proto::msg_paging_ind msg; + + msg.ueid.m_tmsi = htonl(ue_identity->m_tmsi); + msg.ueid.mmec = ue_identity->mmec; + + enc_hdr(buf, proto::RRCTL_PAGING, proto::RRCTL_IND, sizeof(msg)); + buf.append_bytes((const uint8_t*) &msg, sizeof(msg)); +} + +} // namespace codec + +} // namespace rrctl