/**
 * Copyright 2013-2023 Software Radio Systems Limited
 *
 * This file is part of srsRAN.
 *
 * srsRAN 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.
 *
 * srsRAN 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 "srsenb/hdr/stack/rrc/rrc.h"
#include "srsenb/hdr/stack/mac/sched_interface.h"
#include "srsenb/hdr/stack/rrc/rrc_cell_cfg.h"
#include "srsenb/hdr/stack/rrc/rrc_endc.h"
#include "srsenb/hdr/stack/rrc/rrc_mobility.h"
#include "srsenb/hdr/stack/rrc/rrc_paging.h"
#include "srsenb/hdr/stack/s1ap/s1ap.h"
#include "srsran/asn1/asn1_utils.h"
#include "srsran/asn1/rrc_utils.h"
#include "srsran/common/bcd_helpers.h"
#include "srsran/common/enb_events.h"
#include "srsran/common/standard_streams.h"
#include "srsran/common/string_helpers.h"
#include "srsran/interfaces/enb_mac_interfaces.h"
#include "srsran/interfaces/enb_pdcp_interfaces.h"
#include "srsran/interfaces/enb_rlc_interfaces.h"

using srsran::byte_buffer_t;

using namespace asn1::rrc;

namespace srsenb {

rrc::rrc(srsran::task_sched_handle task_sched_, enb_bearer_manager& manager_) :
  logger(srslog::fetch_basic_logger("RRC")), bearer_manager(manager_), task_sched(task_sched_), rx_pdu_queue(128)
{
}

rrc::~rrc() {}

int32_t rrc::init(const rrc_cfg_t&       cfg_,
                  phy_interface_rrc_lte* phy_,
                  mac_interface_rrc*     mac_,
                  rlc_interface_rrc*     rlc_,
                  pdcp_interface_rrc*    pdcp_,
                  s1ap_interface_rrc*    s1ap_,
                  gtpu_interface_rrc*    gtpu_)
{
  return init(cfg_, phy_, mac_, rlc_, pdcp_, s1ap_, gtpu_, nullptr);
}

int32_t rrc::init(const rrc_cfg_t&       cfg_,
                  phy_interface_rrc_lte* phy_,
                  mac_interface_rrc*     mac_,
                  rlc_interface_rrc*     rlc_,
                  pdcp_interface_rrc*    pdcp_,
                  s1ap_interface_rrc*    s1ap_,
                  gtpu_interface_rrc*    gtpu_,
                  rrc_nr_interface_rrc*  rrc_nr_)
{
  phy    = phy_;
  mac    = mac_;
  rlc    = rlc_;
  pdcp   = pdcp_;
  gtpu   = gtpu_;
  s1ap   = s1ap_;
  rrc_nr = rrc_nr_;

  cfg = cfg_;

  if (cfg.sibs[12].type() == asn1::rrc::sys_info_r8_ies_s::sib_type_and_info_item_c_::types::sib13_v920 &&
      cfg.enable_mbsfn) {
    configure_mbsfn_sibs();
  }

  cell_res_list.reset(new freq_res_common_list{cfg});

  // Loads the PRACH root sequence
  cfg.sibs[1].sib2().rr_cfg_common.prach_cfg.root_seq_idx = cfg.cell_list[0].root_seq_idx;

  if (cfg.num_nr_cells > 0) {
    cfg.sibs[1].sib2().ext = true;
    cfg.sibs[1].sib2().plmn_info_list_r15.set_present();
    cfg.sibs[1].sib2().plmn_info_list_r15.get()->resize(1);
    auto& plmn                       = cfg.sibs[1].sib2().plmn_info_list_r15.get()->back();
    plmn.upper_layer_ind_r15_present = true;
  }

  if (generate_sibs() != SRSRAN_SUCCESS) {
    logger.error("Couldn't generate SIBs.");
    return false;
  }
  config_mac();

  // Check valid inactivity timeout config
  uint32_t t310 = cfg.sibs[1].sib2().ue_timers_and_consts.t310.to_number();
  uint32_t t311 = cfg.sibs[1].sib2().ue_timers_and_consts.t311.to_number();
  uint32_t n310 = cfg.sibs[1].sib2().ue_timers_and_consts.n310.to_number();
  logger.info("T310 %d, T311 %d, N310 %d", t310, t311, n310);
  if (cfg.inactivity_timeout_ms < t310 + t311 + n310) {
    srsran::console("\nWarning: Inactivity timeout is smaller than the sum of t310, t311 and n310.\n"
                    "This may break the UE's re-establishment procedure.\n");
    logger.warning("Inactivity timeout is smaller than the sum of t310, t311 and n310. This may break the UE's "
                   "re-establishment procedure.");
  }
  logger.info("Inactivity timeout: %d ms", cfg.inactivity_timeout_ms);
  logger.info("Max consecutive MAC KOs: %d", cfg.max_mac_dl_kos);

  pending_paging.reset(new paging_manager(cfg.sibs[1].sib2().rr_cfg_common.pcch_cfg.default_paging_cycle.to_number(),
                                          cfg.sibs[1].sib2().rr_cfg_common.pcch_cfg.nb.to_number(),
                                          cfg.etws_present,
                                          cfg.cmas_present));
  running = true;

  if (logger.debug.enabled()) {
    asn1::json_writer js{};
    cfg.srb1_cfg.rlc_cfg.to_json(js);
    logger.debug("SRB1 configuration: %s", js.to_string().c_str());
    js = {};
    cfg.srb2_cfg.rlc_cfg.to_json(js);
    logger.debug("SRB2 configuration: %s", js.to_string().c_str());
  }
  return SRSRAN_SUCCESS;
}

void rrc::stop()
{
  if (running) {
    running   = false;
    rrc_pdu p = {0, LCID_EXIT, false, nullptr};
    rx_pdu_queue.push_blocking(std::move(p));
  }
  users.clear();
}

/*******************************************************************************
  Public functions
*******************************************************************************/

void rrc::get_metrics(rrc_metrics_t& m)
{
  if (running) {
    m.ues.resize(users.size());
    size_t count = 0;
    for (auto& ue : users) {
      ue.second->get_metrics(m.ues[count++]);
    }
  }
}

/*******************************************************************************
  MAC interface

  Those functions that shall be called from a phch_worker should push the command
  to the queue and process later
*******************************************************************************/

uint8_t* rrc::read_pdu_bcch_dlsch(const uint8_t cc_idx, const uint32_t sib_index)
{
  if (sib_index < ASN1_RRC_MAX_SIB && cc_idx < cell_common_list->nof_cells()) {
    return cell_common_list->get_cc_idx(cc_idx)->sib_buffer.at(sib_index).get()->msg;
  }
  return nullptr;
}

uint8_t* rrc::read_pdu_bcch_dlsch(const uint8_t cc_idx, const uint32_t sib_index, const uint32_t sib_segment_index)
{
  if (sib_index < ASN1_RRC_MAX_SIB && cc_idx < cell_common_list->nof_cells()) {
    // SIB message.
    packed_sib_buffer& sib = cell_common_list->get_cc_idx(cc_idx)->sib_buffer.at(sib_index);
    return sib.get_segment(sib_segment_index)->msg;
  }
  return nullptr;
}

void rrc::set_radiolink_dl_state(uint16_t rnti, bool crc_res)
{
  // embed parameters in arg value
  rrc_pdu p = {rnti, LCID_RADLINK_DL, crc_res, nullptr};

  if (not rx_pdu_queue.try_push(std::move(p))) {
    logger.error("Failed to push radio link DL state");
  }
}

void rrc::set_radiolink_ul_state(uint16_t rnti, bool crc_res)
{
  // embed parameters in arg value
  rrc_pdu p = {rnti, LCID_RADLINK_UL, crc_res, nullptr};

  if (not rx_pdu_queue.try_push(std::move(p))) {
    logger.error("Failed to push radio link UL state");
  }
}

void rrc::set_activity_user(uint16_t rnti)
{
  rrc_pdu p = {rnti, LCID_ACT_USER, false, nullptr};

  if (not rx_pdu_queue.try_push(std::move(p))) {
    logger.error("Failed to push UE activity command to RRC queue");
  }
}

void rrc::rem_user_thread(uint16_t rnti)
{
  rrc_pdu p = {rnti, LCID_REM_USER, false, nullptr};
  if (not rx_pdu_queue.try_push(std::move(p))) {
    logger.error("Failed to push UE remove command to RRC queue");
  }
}

uint32_t rrc::get_nof_users()
{
  return users.size();
}

void rrc::max_retx_attempted(uint16_t rnti)
{
  rrc_pdu p = {rnti, LCID_RLC_RTX, false, nullptr};
  if (not rx_pdu_queue.try_push(std::move(p))) {
    logger.error("Failed to push max Retx event to RRC queue");
  }
}

void rrc::protocol_failure(uint16_t rnti)
{
  rrc_pdu p = {rnti, LCID_PROT_FAIL, false, nullptr};
  if (not rx_pdu_queue.try_push(std::move(p))) {
    logger.error("Failed to push protocol failure to RRC queue");
  }
}

// This function is called from PRACH worker (can wait)
int rrc::add_user(uint16_t rnti, const sched_interface::ue_cfg_t& sched_ue_cfg)
{
  auto user_it = users.find(rnti);
  if (user_it == users.end()) {
    if (rnti != SRSRAN_MRNTI) {
      // only non-eMBMS RNTIs are present in user map
      unique_rnti_ptr<ue> u = make_rnti_obj<ue>(rnti, this, rnti, sched_ue_cfg);
      if (u->init() != SRSRAN_SUCCESS) {
        logger.error("Adding user rnti=0x%x - Failed to allocate user resources", rnti);
        return SRSRAN_ERROR;
      }
      users.insert(std::make_pair(rnti, std::move(u)));
    }
    rlc->add_user(rnti);
    pdcp->add_user(rnti);
    logger.info("Added new user rnti=0x%x", rnti);
  } else {
    logger.error("Adding user rnti=0x%x (already exists)", rnti);
  }

  if (rnti == SRSRAN_MRNTI) {
    for (auto& mbms_item : mcch.msg.c1().mbsfn_area_cfg_r9().pmch_info_list_r9[0].mbms_session_info_list_r9) {
      uint32_t lcid = mbms_item.lc_ch_id_r9;
      uint32_t addr_in;
      // adding UE object to MAC for MRNTI without scheduling configuration (broadcast not part of regular scheduling)
      rlc->add_bearer_mrb(SRSRAN_MRNTI, lcid);
      bearer_manager.add_eps_bearer(SRSRAN_MRNTI, 1, srsran::srsran_rat_t::lte, lcid);
      pdcp->add_bearer(SRSRAN_MRNTI, lcid, srsran::make_drb_pdcp_config_t(1, false));
      gtpu->add_bearer(SRSRAN_MRNTI, lcid, 1, 1, addr_in);
    }
  }
  return SRSRAN_SUCCESS;
}

/* Function called by MAC after the reception of a C-RNTI CE indicating that the UE still has a
 * valid RNTI.
 */
void rrc::upd_user(uint16_t new_rnti, uint16_t old_rnti)
{
  // Remove new_rnti
  auto new_ue_it = users.find(new_rnti);
  if (new_ue_it != users.end()) {
    new_ue_it->second->deactivate_bearers();
    rem_user_thread(new_rnti);
  }

  // Send Reconfiguration to old_rnti if is RRC_CONNECT or RRC Release if already released here
  auto old_it = users.find(old_rnti);
  if (old_it == users.end()) {
    logger.info("rnti=0x%x received MAC CRNTI CE: 0x%x, but old context is unavailable", new_rnti, old_rnti);
    return;
  }
  ue* ue_ptr = old_it->second.get();

  if (ue_ptr->mobility_handler->is_ho_running()) {
    ue_ptr->mobility_handler->trigger(ue::rrc_mobility::user_crnti_upd_ev{old_rnti, new_rnti});
  } else {
    logger.info("Resuming rnti=0x%x RRC connection due to received C-RNTI CE from rnti=0x%x.", old_rnti, new_rnti);
    if (ue_ptr->is_connected()) {
      // Send a new RRC Reconfiguration to overlay previous
      old_it->second->send_connection_reconf();
    }
  }

  // Log event.
  event_logger::get().log_connection_resume(
      ue_ptr->get_cell_list().get_ue_cc_idx(UE_PCELL_CC_IDX)->cell_common->enb_cc_idx, old_rnti, new_rnti);
}

// Note: this method is not part of UE methods, because the UE context may not exist anymore when reject is sent
void rrc::send_rrc_connection_reject(uint16_t rnti)
{
  dl_ccch_msg_s dl_ccch_msg;
  dl_ccch_msg.msg.set_c1().set_rrc_conn_reject().crit_exts.set_c1().set_rrc_conn_reject_r8().wait_time = 10;

  // Allocate a new PDU buffer, pack the message and send to PDCP
  srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer();
  if (pdu == nullptr) {
    logger.error("Allocating pdu");
    return;
  }
  asn1::bit_ref bref(pdu->msg, pdu->get_tailroom());
  if (dl_ccch_msg.pack(bref) != asn1::SRSASN_SUCCESS) {
    logger.error(pdu->msg, bref.distance_bytes(), "Failed to pack DL-CCCH-Msg:");
    return;
  }
  pdu->N_bytes = bref.distance_bytes();

  log_rrc_message(Tx, rnti, srb_to_lcid(lte_srb::srb0), *pdu, dl_ccch_msg, dl_ccch_msg.msg.c1().type().to_string());

  rlc->write_sdu(rnti, srb_to_lcid(lte_srb::srb0), std::move(pdu));
}

/*******************************************************************************
  PDCP interface
*******************************************************************************/
void rrc::write_pdu(uint16_t rnti, uint32_t lcid, srsran::unique_byte_buffer_t pdu)
{
  rrc_pdu p = {rnti, lcid, false, std::move(pdu)};
  if (not rx_pdu_queue.try_push(std::move(p))) {
    logger.error("Failed to push Release command to RRC queue");
  }
}

void rrc::notify_pdcp_integrity_error(uint16_t rnti, uint32_t lcid)
{
  logger.warning("Received integrity protection failure indication, rnti=0x%x, lcid=%u", rnti, lcid);
  s1ap->user_release(rnti, asn1::s1ap::cause_radio_network_opts::unspecified);
}

/*******************************************************************************
  S1AP interface
*******************************************************************************/
void rrc::write_dl_info(uint16_t rnti, srsran::unique_byte_buffer_t sdu)
{
  dl_dcch_msg_s dl_dcch_msg;
  dl_dcch_msg.msg.set_c1();
  dl_dcch_msg_type_c::c1_c_* msg_c1 = &dl_dcch_msg.msg.c1();

  auto user_it = users.find(rnti);
  if (user_it != users.end()) {
    dl_info_transfer_r8_ies_s* dl_info_r8 =
        &msg_c1->set_dl_info_transfer().crit_exts.set_c1().set_dl_info_transfer_r8();
    //    msg_c1->dl_info_transfer().rrc_transaction_id = ;
    dl_info_r8->non_crit_ext_present = false;
    dl_info_r8->ded_info_type.set_ded_info_nas();
    dl_info_r8->ded_info_type.ded_info_nas().resize(sdu->N_bytes);
    memcpy(msg_c1->dl_info_transfer().crit_exts.c1().dl_info_transfer_r8().ded_info_type.ded_info_nas().data(),
           sdu->msg,
           sdu->N_bytes);

    sdu->clear();

    user_it->second->send_dl_dcch(&dl_dcch_msg, std::move(sdu));
  } else {
    logger.error("Rx SDU for unknown rnti=0x%x", rnti);
  }
}

void rrc::release_ue(uint16_t rnti)
{
  rrc_pdu p = {rnti, LCID_REL_USER, false, nullptr};
  if (not rx_pdu_queue.try_push(std::move(p))) {
    logger.error("Failed to push Release command to RRC queue");
  }
}

bool rrc::setup_ue_ctxt(uint16_t rnti, const asn1::s1ap::init_context_setup_request_s& msg)
{
  logger.info("Adding initial context for 0x%x", rnti);
  auto user_it = users.find(rnti);
  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    return false;
  }

  user_it->second->handle_ue_init_ctxt_setup_req(msg);
  return true;
}

bool rrc::modify_ue_ctxt(uint16_t rnti, const asn1::s1ap::ue_context_mod_request_s& msg)
{
  logger.info("Modifying context for 0x%x", rnti);
  auto user_it = users.find(rnti);

  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    return false;
  }

  return user_it->second->handle_ue_ctxt_mod_req(msg);
}

bool rrc::release_erabs(uint32_t rnti)
{
  logger.info("Releasing E-RABs for 0x%x", rnti);
  auto user_it = users.find(rnti);

  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    return false;
  }

  bool ret = user_it->second->release_erabs();
  return ret;
}

int rrc::release_erab(uint16_t rnti, uint16_t erab_id)
{
  logger.info("Releasing E-RAB id=%d for 0x%x", erab_id, rnti);
  auto user_it = users.find(rnti);

  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    return SRSRAN_ERROR;
  }

  return user_it->second->release_erab(erab_id);
}

int rrc::notify_ue_erab_updates(uint16_t rnti, srsran::const_byte_span nas_pdu)
{
  auto user_it = users.find(rnti);
  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    return SRSRAN_ERROR;
  }
  user_it->second->send_connection_reconf(nullptr, false, nas_pdu);
  return SRSRAN_SUCCESS;
}

bool rrc::has_erab(uint16_t rnti, uint32_t erab_id) const
{
  auto user_it = users.find(rnti);
  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    return false;
  }
  return user_it->second->has_erab(erab_id);
}

int rrc::get_erab_addr_in(uint16_t rnti, uint16_t erab_id, transp_addr_t& addr_in, uint32_t& teid_in) const
{
  auto user_it = users.find(rnti);
  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    return SRSRAN_ERROR;
  }
  return user_it->second->get_erab_addr_in(erab_id, addr_in, teid_in);
}

void rrc::set_aggregate_max_bitrate(uint16_t rnti, const asn1::s1ap::ue_aggregate_maximum_bitrate_s& bitrate)
{
  auto user_it = users.find(rnti);
  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    return;
  }
  user_it->second->set_bitrates(bitrate);
}

int rrc::setup_erab(uint16_t                                           rnti,
                    uint16_t                                           erab_id,
                    const asn1::s1ap::erab_level_qos_params_s&         qos_params,
                    srsran::const_span<uint8_t>                        nas_pdu,
                    const asn1::bounded_bitstring<1, 160, true, true>& addr,
                    uint32_t                                           gtpu_teid_out,
                    asn1::s1ap::cause_c&                               cause)
{
  logger.info("Setting up erab id=%d for 0x%x", erab_id, rnti);
  auto user_it = users.find(rnti);
  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::unknown_erab_id;
    return SRSRAN_ERROR;
  }
  return user_it->second->setup_erab(erab_id, qos_params, nas_pdu, addr, gtpu_teid_out, cause);
}

int rrc::modify_erab(uint16_t                                   rnti,
                     uint16_t                                   erab_id,
                     const asn1::s1ap::erab_level_qos_params_s& qos_params,
                     srsran::const_span<uint8_t>                nas_pdu,
                     asn1::s1ap::cause_c&                       cause)
{
  logger.info("Modifying E-RAB for 0x%x. E-RAB Id %d", rnti, erab_id);
  auto user_it = users.find(rnti);
  if (user_it == users.end()) {
    logger.warning("Unrecognised rnti: 0x%x", rnti);
    cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::unknown_erab_id;
    return SRSRAN_ERROR;
  }

  return user_it->second->modify_erab(erab_id, qos_params, nas_pdu, cause);
}

/*******************************************************************************
  Paging functions
  These functions use a different mutex because access different shared variables
  than user map
*******************************************************************************/

void rrc::add_paging_id(uint32_t ueid, const asn1::s1ap::ue_paging_id_c& ue_paging_id)
{
  if (ue_paging_id.type().value == asn1::s1ap::ue_paging_id_c::types_opts::imsi) {
    pending_paging->add_imsi_paging(ueid, ue_paging_id.imsi());
  } else {
    pending_paging->add_tmsi_paging(ueid, ue_paging_id.s_tmsi().mmec[0], ue_paging_id.s_tmsi().m_tmsi);
  }
}

bool rrc::is_paging_opportunity(uint32_t tti, uint32_t* payload_len)
{
  *payload_len = pending_paging->pending_pcch_bytes(tti_point(tti));
  return *payload_len > 0;
}

void rrc::read_pdu_pcch(uint32_t tti_tx_dl, uint8_t* payload, uint32_t buffer_size)
{
  auto read_func = [this, payload, buffer_size](srsran::const_byte_span pdu, const pcch_msg_s& msg, bool first_tx) {
    // copy PCCH pdu to buffer
    if (pdu.size() > buffer_size) {
      logger.warning("byte buffer with size=%zd is too small to fit pcch msg with size=%zd", buffer_size, pdu.size());
      return false;
    }
    std::copy(pdu.begin(), pdu.end(), payload);

    if (first_tx) {
      logger.info("Assembling PCCH payload with %d UE identities, payload_len=%d bytes",
                  msg.msg.c1().paging().paging_record_list.size(),
                  pdu.size());
      log_broadcast_rrc_message(SRSRAN_PRNTI, pdu, msg, msg.msg.c1().type().to_string());
    }
    return true;
  };

  pending_paging->read_pdu_pcch(tti_point(tti_tx_dl), read_func);
}

/*******************************************************************************
  Handover functions
*******************************************************************************/

void rrc::ho_preparation_complete(uint16_t                     rnti,
                                  ho_prep_result               result,
                                  const asn1::s1ap::ho_cmd_s&  msg,
                                  srsran::unique_byte_buffer_t rrc_container)
{
  users.at(rnti)->mobility_handler->handle_ho_preparation_complete(result, msg, std::move(rrc_container));
}

void rrc::set_erab_status(uint16_t rnti, const asn1::s1ap::bearers_subject_to_status_transfer_list_l& erabs)
{
  auto ue_it = users.find(rnti);
  if (ue_it == users.end()) {
    logger.warning("rnti=0x%x does not exist", rnti);
    return;
  }
  ue_it->second->mobility_handler->trigger(erabs);
}

/*******************************************************************************
  EN-DC/NSA helper functions
*******************************************************************************/

void rrc::sgnb_addition_ack(uint16_t eutra_rnti, sgnb_addition_ack_params_t params)
{
  logger.info("Received SgNB addition acknowledgement for rnti=0x%x", eutra_rnti);
  auto ue_it = users.find(eutra_rnti);
  if (ue_it == users.end()) {
    logger.warning("rnti=0x%x does not exist", eutra_rnti);
    return;
  }
  ue_it->second->endc_handler->trigger(ue::rrc_endc::sgnb_add_req_ack_ev{params});

  // trigger RRC Reconfiguration to send NR config to UE
  ue_it->second->send_connection_reconf();
}

void rrc::sgnb_addition_reject(uint16_t eutra_rnti)
{
  logger.error("Received SgNB addition reject for rnti=%d", eutra_rnti);
  auto ue_it = users.find(eutra_rnti);
  if (ue_it == users.end()) {
    logger.warning("rnti=0x%x does not exist", eutra_rnti);
    return;
  }
  ue_it->second->endc_handler->trigger(ue::rrc_endc::sgnb_add_req_reject_ev{});
}

void rrc::sgnb_addition_complete(uint16_t eutra_rnti, uint16_t nr_rnti)
{
  logger.info("User rnti=0x%x successfully enabled EN-DC", eutra_rnti);
  auto ue_it = users.find(eutra_rnti);
  if (ue_it == users.end()) {
    logger.warning("rnti=0x%x does not exist", eutra_rnti);
    return;
  }
  ue_it->second->endc_handler->trigger(ue::rrc_endc::sgnb_add_complete_ev{nr_rnti});
}

void rrc::sgnb_inactivity_timeout(uint16_t eutra_rnti)
{
  logger.info("Received NR inactivity timeout for rnti=0x%x - releasing UE", eutra_rnti);
  auto ue_it = users.find(eutra_rnti);
  if (ue_it == users.end()) {
    logger.warning("rnti=0x%x does not exist", eutra_rnti);
    return;
  }
  s1ap->user_release(eutra_rnti, asn1::s1ap::cause_radio_network_opts::user_inactivity);
}

void rrc::sgnb_release_ack(uint16_t eutra_rnti)
{
  auto ue_it = users.find(eutra_rnti);
  if (ue_it != users.end()) {
    logger.info("Received SgNB release acknowledgement for rnti=0x%x", eutra_rnti);
    ue_it->second->endc_handler->trigger(ue::rrc_endc::sgnb_rel_req_ack_ev{});
  } else {
    // The EUTRA does not need to wait for Release Ack in case it wants to destroy the EUTRA UE
    logger.info("Received SgNB release acknowledgement for already released rnti=0x%x", eutra_rnti);
  }
}

/*******************************************************************************
  Private functions
  All private functions are not mutexed and must be called from a mutexed environment
  from either a public function or the internal thread
*******************************************************************************/

void rrc::parse_ul_ccch(ue& ue, srsran::unique_byte_buffer_t pdu)
{
  srsran_assert(pdu != nullptr, "handle_ul_ccch called for empty message");

  ul_ccch_msg_s  ul_ccch_msg;
  asn1::cbit_ref bref(pdu->msg, pdu->N_bytes);
  if (ul_ccch_msg.unpack(bref) != asn1::SRSASN_SUCCESS or
      ul_ccch_msg.msg.type().value != ul_ccch_msg_type_c::types_opts::c1) {
    log_rx_pdu_fail(ue.rnti, srb_to_lcid(lte_srb::srb0), *pdu, "Failed to unpack UL-CCCH message");
    return;
  }

  // Log Rx message
  log_rrc_message(
      Rx, ue.rnti, srsran::srb_to_lcid(lte_srb::srb0), *pdu, ul_ccch_msg, ul_ccch_msg.msg.c1().type().to_string());

  switch (ul_ccch_msg.msg.c1().type().value) {
    case ul_ccch_msg_type_c::c1_c_::types::rrc_conn_request:
      ue.save_ul_message(std::move(pdu));
      ue.handle_rrc_con_req(&ul_ccch_msg.msg.c1().rrc_conn_request());
      break;
    case ul_ccch_msg_type_c::c1_c_::types::rrc_conn_reest_request:
      ue.save_ul_message(std::move(pdu));
      ue.handle_rrc_con_reest_req(&ul_ccch_msg.msg.c1().rrc_conn_reest_request());
      break;
    default:
      logger.error("Processing UL-CCCH for rnti=0x%x - Unsupported message type %s",
                   ul_ccch_msg.msg.c1().type().to_string());
      break;
  }
}

///< User mutex must be hold by caller
void rrc::parse_ul_dcch(ue& ue, uint32_t lcid, srsran::unique_byte_buffer_t pdu)
{
  srsran_assert(pdu != nullptr, "handle_ul_dcch called for empty message");

  ue.parse_ul_dcch(lcid, std::move(pdu));
}

///< User mutex must be hold by caller
void rrc::process_release_complete(uint16_t rnti)
{
  logger.info("Received Release Complete rnti=0x%x", rnti);
  auto user_it = users.find(rnti);
  if (user_it == users.end()) {
    logger.error("Received ReleaseComplete for unknown rnti=0x%x", rnti);
    return;
  }
  ue* u = user_it->second.get();

  if (u->is_idle() or u->mobility_handler->is_ho_running()) {
    rem_user_thread(rnti);
  } else if (not u->is_idle()) {
    rlc->clear_buffer(rnti);
    user_it->second->send_connection_release();
    // delay user deletion for ~50 TTI (until RRC release is sent)
    task_sched.defer_callback(50, [this, rnti]() { rem_user_thread(rnti); });
  }
}

void rrc::rem_user(uint16_t rnti)
{
  auto user_it = users.find(rnti);
  if (user_it != users.end()) {
    // First remove MAC and GTPU to stop processing DL/UL traffic for this user
    mac->ue_rem(rnti); // MAC handles PHY
    gtpu->rem_user(rnti);

    // Now remove RLC and PDCP
    bearer_manager.rem_user(rnti);
    rlc->rem_user(rnti);
    pdcp->rem_user(rnti);

    users.erase(rnti);

    srsran::console("Disconnecting rnti=0x%x.\n", rnti);
    logger.info("Removed user rnti=0x%x", rnti);
  } else {
    logger.error("Removing user rnti=0x%x (does not exist)", rnti);
  }
}

void rrc::config_mac()
{
  using sched_cell_t = sched_interface::cell_cfg_t;

  // Fill MAC scheduler configuration for SIBs
  std::vector<sched_cell_t> sched_cfg;
  sched_cfg.resize(cfg.cell_list.size());

  for (uint32_t ccidx = 0; ccidx < cfg.cell_list.size(); ++ccidx) {
    sched_interface::cell_cfg_t& item = sched_cfg[ccidx];

    // set sib/prach cfg
    for (uint32_t i = 0; i < nof_si_messages; i++) {
      auto& buffer = cell_common_list->get_cc_idx(ccidx)->sib_buffer.at(i);
      // If the SIB is segmented, add the SIB length of each segment.
      if (buffer.is_segmented()) {
        for (unsigned i_segment = 0, nof_segments = buffer.get_nof_segments(); i_segment != nof_segments; ++i_segment) {
          item.sibs[i].add_segment(
              cell_common_list->get_cc_idx(ccidx)->sib_buffer.at(i).get_segment(i_segment)->N_bytes);
        }
      } else {
        item.sibs[i].add_segment(cell_common_list->get_cc_idx(ccidx)->sib_buffer.at(i).get()->N_bytes);
      }
      if (i == 0) {
        item.sibs[i].set_period_rf(8); // SIB1 is always 8 rf
      } else {
        item.sibs[i].set_period_rf(cfg.sib1.sched_info_list[i - 1].si_periodicity.to_number());
      }
    }
    item.prach_config        = cfg.sibs[1].sib2().rr_cfg_common.prach_cfg.prach_cfg_info.prach_cfg_idx;
    item.prach_nof_preambles = cfg.sibs[1].sib2().rr_cfg_common.rach_cfg_common.preamb_info.nof_ra_preambs.to_number();
    item.si_window_ms        = cfg.sib1.si_win_len.to_number();
    item.prach_rar_window =
        cfg.sibs[1].sib2().rr_cfg_common.rach_cfg_common.ra_supervision_info.ra_resp_win_size.to_number();
    item.prach_freq_offset    = cfg.sibs[1].sib2().rr_cfg_common.prach_cfg.prach_cfg_info.prach_freq_offset;
    item.maxharq_msg3tx       = cfg.sibs[1].sib2().rr_cfg_common.rach_cfg_common.max_harq_msg3_tx;
    item.enable_64qam         = cfg.sibs[1].sib2().rr_cfg_common.pusch_cfg_common.pusch_cfg_basic.enable64_qam;
    item.target_pucch_ul_sinr = cfg.cell_list[ccidx].target_pucch_sinr_db;
    item.target_pusch_ul_sinr = cfg.cell_list[ccidx].target_pusch_sinr_db;
    item.enable_phr_handling  = cfg.cell_list[ccidx].enable_phr_handling;
    item.min_phr_thres        = cfg.cell_list[ccidx].min_phr_thres;
    item.delta_pucch_shift    = cfg.sibs[1].sib2().rr_cfg_common.pucch_cfg_common.delta_pucch_shift.to_number();
    item.ncs_an               = cfg.sibs[1].sib2().rr_cfg_common.pucch_cfg_common.ncs_an;
    item.n1pucch_an           = cfg.sibs[1].sib2().rr_cfg_common.pucch_cfg_common.n1_pucch_an;
    item.nrb_cqi              = cfg.sibs[1].sib2().rr_cfg_common.pucch_cfg_common.nrb_cqi;

    item.nrb_pucch = SRSRAN_MAX(cfg.sr_cfg.nof_prb, item.nrb_cqi);
    logger.info("Allocating %d PRBs for PUCCH", item.nrb_pucch);

    // Copy base cell configuration
    item.cell    = cfg.cell;
    item.cell.id = cfg.cell_list[ccidx].pci;

    // copy secondary cell list info
    sched_cfg[ccidx].scell_list.reserve(cfg.cell_list[ccidx].scell_list.size());
    for (uint32_t scidx = 0; scidx < cfg.cell_list[ccidx].scell_list.size(); ++scidx) {
      const auto& scellitem = cfg.cell_list[ccidx].scell_list[scidx];
      // search enb_cc_idx specific to cell_id
      auto it = std::find_if(cfg.cell_list.begin(), cfg.cell_list.end(), [&scellitem](const cell_cfg_t& e) {
        return e.cell_id == scellitem.cell_id;
      });
      if (it == cfg.cell_list.end()) {
        logger.warning("Secondary cell 0x%x not configured", scellitem.cell_id);
        continue;
      }
      sched_interface::cell_cfg_t::scell_cfg_t scellcfg;
      scellcfg.enb_cc_idx               = it - cfg.cell_list.begin();
      scellcfg.ul_allowed               = scellitem.ul_allowed;
      scellcfg.cross_carrier_scheduling = scellitem.cross_carrier_sched;
      sched_cfg[ccidx].scell_list.push_back(scellcfg);
    }
  }

  // Configure MAC scheduler
  mac->cell_cfg(sched_cfg);
}

/* This methods packs the SIBs for each component carrier and stores them
 * inside the sib_buffer, a vector of SIBs for each CC.
 *
 * Before packing the message, it patches the cell specific params of
 * the SIB, including the cellId and the PRACH config index.
 *
 * The number of generates SIB messages is stored in the class member nof_si_messages
 *
 * @return SRSRAN_SUCCESS on success, SRSRAN_ERROR on failure
 */
uint32_t rrc::generate_sibs()
{
  // nof_messages includes SIB2 by default, plus all configured SIBs
  uint32_t           nof_messages = 1 + cfg.sib1.sched_info_list.size();
  sched_info_list_l& sched_info   = cfg.sib1.sched_info_list;

  // Store configs,SIBs in common cell ctxt list
  cell_common_list.reset(new enb_cell_common_list{cfg});

  // generate and pack into SIB buffers
  for (uint32_t cc_idx = 0; cc_idx < cfg.cell_list.size(); cc_idx++) {
    enb_cell_common* cell_ctxt = cell_common_list->get_cc_idx(cc_idx);
    // msg is array of SI messages, each SI message msg[i] may contain multiple SIBs
    // all SIBs in a SI message msg[i] share the same periodicity
    asn1::dyn_array<bcch_dl_sch_msg_s> msg(nof_messages + 1);

    // Array of messages for SIB-11 segments. Each message may contain a single SIB11 segment.
    asn1::dyn_array<bcch_dl_sch_msg_s> sib11_segments;
    // Array of messages for SIB-12 segments. Each message may contain a single SIB12 segment.
    asn1::dyn_array<bcch_dl_sch_msg_s> sib12_segments;
    // Index of the SI message containing the SIB-11.
    srsran::optional<unsigned> sib11_si_index;
    // Index of the SI message containing the SIB-12.
    srsran::optional<unsigned> sib12_si_index;

    // Copy SIB1 to first SI message
    msg[0].msg.set_c1().set_sib_type1() = cell_ctxt->sib1;

    // Copy rest of SIBs
    for (uint32_t sched_info_elem = 0; sched_info_elem < nof_messages - 1; sched_info_elem++) {
      uint32_t msg_index = sched_info_elem + 1; // first msg is SIB1, therefore start with second

      msg[msg_index].msg.set_c1().set_sys_info().crit_exts.set_sys_info_r8();
      sys_info_r8_ies_s::sib_type_and_info_l_& sib_list =
          msg[msg_index].msg.c1().sys_info().crit_exts.sys_info_r8().sib_type_and_info;

      // SIB2 always in second SI message
      if (msg_index == 1) {
        sib_info_item_c sibitem;
        sibitem.set_sib2() = cell_ctxt->sib2;
        sib_list.push_back(sibitem);
      }

      // Add other SIBs to this message, if any
      for (auto& mapping_enum : sched_info[sched_info_elem].sib_map_info) {
        if (mapping_enum.value == sib_type_opts::options::sib_type11) {
          for (unsigned i_segment = 0; i_segment != cfg.sib11_segments.size(); ++i_segment) {
            // SI message description holding one SIB-11 segment.
            bcch_dl_sch_msg_s segment;
            segment.msg.set_c1().set_sys_info().crit_exts.set_sys_info_r8();
            segment.msg.c1().sys_info().crit_exts.sys_info_r8().sib_type_and_info.push_back(
                cfg.sib11_segments[i_segment]);

            // Add the SI message belonging to each segment to the list.
            sib11_segments.push_back(segment);
          }
          sib11_si_index.emplace(msg_index);
        } else if (mapping_enum.value == sib_type_opts::options::sib_type12_v920) {
          for (unsigned i_segment = 0; i_segment != cfg.sib12_segments.size(); ++i_segment) {
            // SI message description holding one SIB-12 segment.
            bcch_dl_sch_msg_s segment;
            segment.msg.set_c1().set_sys_info().crit_exts.set_sys_info_r8();
            segment.msg.c1().sys_info().crit_exts.sys_info_r8().sib_type_and_info.push_back(
                cfg.sib12_segments[i_segment]);

            // Add the SI message belonging to each segment to the list.
            sib12_segments.push_back(segment);
          }
          sib12_si_index.emplace(msg_index);
        } else {
          sib_list.push_back(cfg.sibs[(int)mapping_enum + 2]);
        }
      }
    }

    // Pack payload for all SIB messages.
    for (uint32_t msg_index = 0; msg_index != nof_messages; ++msg_index) {
      // If the message contains SIB-11 or SIB-12, pack each SIB segment.
      if ((sib11_si_index.has_value() && (msg_index == sib11_si_index.value())) ||
          (sib12_si_index.has_value() && (msg_index == sib12_si_index.value()))) {
        // Determine if the message is SIB-11 or SIB-12.
        sib_type_opts::options sib_type = (sib11_si_index.has_value() && (msg_index == sib11_si_index.value()))
                                              ? sib_type_opts::sib_type11
                                              : sib_type_opts::sib_type12_v920;
        // Number of SIB segments.
        unsigned nof_segments = (sib_type == sib_type_opts::sib_type11) ? sib11_segments.size() : sib12_segments.size();

        // Parsed SIB messages belonging to each segment.
        asn1::dyn_array<bcch_dl_sch_msg_s>& sib_segments =
            (sib_type == sib_type_opts::sib_type11) ? sib11_segments : sib12_segments;

        for (unsigned i_segment = 0; i_segment != nof_segments; ++i_segment) {
          srsran::unique_byte_buffer_t sib_buffer = srsran::make_byte_buffer();
          if (sib_buffer == nullptr) {
            logger.error("Couldn't allocate PDU in %s().", __FUNCTION__);
            return SRSRAN_ERROR;
          }
          asn1::bit_ref bref(sib_buffer->msg, sib_buffer->get_tailroom());

          // Pack the segments.
          if (sib_segments[i_segment].pack(bref) != asn1::SRSASN_SUCCESS) {
            logger.error("Failed to pack SIB message %d", msg_index);
            return SRSRAN_ERROR;
          }
          sib_buffer->N_bytes = bref.distance_bytes();

          // Write each segment into the packed SIB buffer.
          if (i_segment == 0) {
            cell_ctxt->sib_buffer.emplace_back(std::move(sib_buffer));
          } else {
            cell_ctxt->sib_buffer.back().set_segment(std::move(sib_buffer));
          }
        }
      } else {
        // Pack unsegmented SIB messages.
        srsran::unique_byte_buffer_t sib_buffer = srsran::make_byte_buffer();
        if (sib_buffer == nullptr) {
          logger.error("Couldn't allocate PDU in %s().", __FUNCTION__);
          return SRSRAN_ERROR;
        }
        asn1::bit_ref bref(sib_buffer->msg, sib_buffer->get_tailroom());

        if (msg[msg_index].pack(bref) != asn1::SRSASN_SUCCESS) {
          logger.error("Failed to pack SIB message %d", msg_index);
          return SRSRAN_ERROR;
        }
        sib_buffer->N_bytes = bref.distance_bytes();
        cell_ctxt->sib_buffer.emplace_back(std::move(sib_buffer));
      }

      // Log SIBs in JSON format
      fmt::memory_buffer membuf;
      const char*        msg_str = msg[msg_index].msg.c1().type().to_string();
      if (msg[msg_index].msg.c1().type().value != asn1::rrc::bcch_dl_sch_msg_type_c::c1_c_::types_opts::sib_type1) {
        msg_str = msg[msg_index].msg.c1().sys_info().crit_exts.type().to_string();
      }
      packed_sib_buffer& sib = cell_ctxt->sib_buffer.back();
      if (sib.is_segmented()) {
        for (unsigned i_segment = 0, nof_segments = sib.get_nof_segments(); i_segment != nof_segments; ++i_segment) {
          membuf.clear();
          fmt::format_to(membuf, "{}, cc={}, idx={} sib_segment={}", msg_str, cc_idx, msg_index, i_segment);
          log_broadcast_rrc_message(
              SRSRAN_SIRNTI, *sib.get_segment(i_segment), msg[msg_index], srsran::to_c_str(membuf));
        }
      } else {
        fmt::format_to(membuf, "{}, cc={}, idx={}", msg_str, cc_idx, msg_index);
        log_broadcast_rrc_message(SRSRAN_SIRNTI, *sib.get(), msg[msg_index], srsran::to_c_str(membuf));
      }
    }

    if (cfg.sibs[6].type() == asn1::rrc::sys_info_r8_ies_s::sib_type_and_info_item_c_::types::sib7) {
      sib7 = cfg.sibs[6].sib7();
    }
  }

  nof_si_messages = nof_messages;

  return SRSRAN_SUCCESS;
}

void rrc::configure_mbsfn_sibs()
{
  // populate struct with sib2 values needed in PHY/MAC
  srsran::sib2_mbms_t sibs2;
  sibs2.mbsfn_sf_cfg_list_present = cfg.sibs[1].sib2().mbsfn_sf_cfg_list_present;
  sibs2.nof_mbsfn_sf_cfg          = cfg.sibs[1].sib2().mbsfn_sf_cfg_list.size();
  for (int i = 0; i < sibs2.nof_mbsfn_sf_cfg; i++) {
    sibs2.mbsfn_sf_cfg_list[i].nof_alloc_subfrs = srsran::mbsfn_sf_cfg_t::sf_alloc_type_t::one_frame;
    sibs2.mbsfn_sf_cfg_list[i].radioframe_alloc_offset =
        cfg.sibs[1].sib2().mbsfn_sf_cfg_list[i].radioframe_alloc_offset;
    sibs2.mbsfn_sf_cfg_list[i].radioframe_alloc_period =
        (srsran::mbsfn_sf_cfg_t::alloc_period_t)cfg.sibs[1].sib2().mbsfn_sf_cfg_list[i].radioframe_alloc_period.value;
    sibs2.mbsfn_sf_cfg_list[i].sf_alloc =
        (uint32_t)cfg.sibs[1].sib2().mbsfn_sf_cfg_list[i].sf_alloc.one_frame().to_number();
  }
  // populate struct with sib13 values needed for PHY/MAC
  srsran::sib13_t sibs13;
  sibs13.notif_cfg.notif_offset = cfg.sibs[12].sib13_v920().notif_cfg_r9.notif_offset_r9;
  sibs13.notif_cfg.notif_repeat_coeff =
      (srsran::mbms_notif_cfg_t::coeff_t)cfg.sibs[12].sib13_v920().notif_cfg_r9.notif_repeat_coeff_r9.value;
  sibs13.notif_cfg.notif_sf_idx = cfg.sibs[12].sib13_v920().notif_cfg_r9.notif_sf_idx_r9;
  sibs13.nof_mbsfn_area_info    = cfg.sibs[12].sib13_v920().mbsfn_area_info_list_r9.size();
  for (uint32_t i = 0; i < sibs13.nof_mbsfn_area_info; i++) {
    sibs13.mbsfn_area_info_list[i].mbsfn_area_id =
        cfg.sibs[12].sib13_v920().mbsfn_area_info_list_r9[i].mbsfn_area_id_r9;
    sibs13.mbsfn_area_info_list[i].notif_ind        = cfg.sibs[12].sib13_v920().mbsfn_area_info_list_r9[i].notif_ind_r9;
    sibs13.mbsfn_area_info_list[i].mcch_cfg.sig_mcs = (srsran::mbsfn_area_info_t::mcch_cfg_t::sig_mcs_t)cfg.sibs[12]
                                                          .sib13_v920()
                                                          .mbsfn_area_info_list_r9[i]
                                                          .mcch_cfg_r9.sig_mcs_r9.value;
    sibs13.mbsfn_area_info_list[i].mcch_cfg.sf_alloc_info =
        cfg.sibs[12].sib13_v920().mbsfn_area_info_list_r9[i].mcch_cfg_r9.sf_alloc_info_r9.to_number();
    sibs13.mbsfn_area_info_list[i].mcch_cfg.mcch_repeat_period =
        (srsran::mbsfn_area_info_t::mcch_cfg_t::repeat_period_t)cfg.sibs[12]
            .sib13_v920()
            .mbsfn_area_info_list_r9[i]
            .mcch_cfg_r9.mcch_repeat_period_r9.value;
    sibs13.mbsfn_area_info_list[i].mcch_cfg.mcch_offset =
        cfg.sibs[12].sib13_v920().mbsfn_area_info_list_r9[i].mcch_cfg_r9.mcch_offset_r9;
    sibs13.mbsfn_area_info_list[i].mcch_cfg.mcch_mod_period =
        (srsran::mbsfn_area_info_t::mcch_cfg_t::mod_period_t)cfg.sibs[12]
            .sib13_v920()
            .mbsfn_area_info_list_r9[i]
            .mcch_cfg_r9.mcch_mod_period_r9.value;
    sibs13.mbsfn_area_info_list[i].non_mbsfn_region_len = (srsran::mbsfn_area_info_t::region_len_t)cfg.sibs[12]
                                                              .sib13_v920()
                                                              .mbsfn_area_info_list_r9[i]
                                                              .non_mbsfn_region_len.value;
    sibs13.mbsfn_area_info_list[i].notif_ind = cfg.sibs[12].sib13_v920().mbsfn_area_info_list_r9[i].notif_ind_r9;
  }

  // pack MCCH for transmission and pass relevant MCCH values to PHY/MAC
  pack_mcch();
  srsran::mcch_msg_t mcch_t;
  mcch_t.common_sf_alloc_period         = srsran::mcch_msg_t::common_sf_alloc_period_t::rf64;
  mcch_t.nof_common_sf_alloc            = 1;
  srsran::mbsfn_sf_cfg_t sf_alloc_item  = mcch_t.common_sf_alloc[0];
  sf_alloc_item.radioframe_alloc_offset = 0;
  sf_alloc_item.radioframe_alloc_period = srsran::mbsfn_sf_cfg_t::alloc_period_t::n1;
  sf_alloc_item.sf_alloc                = 63;
  mcch_t.nof_pmch_info                  = 1;
  srsran::pmch_info_t* pmch_item        = &mcch_t.pmch_info_list[0];

  pmch_item->nof_mbms_session_info              = 1;
  pmch_item->mbms_session_info_list[0].lc_ch_id = 1;
  if (pmch_item->nof_mbms_session_info > 1) {
    pmch_item->mbms_session_info_list[1].lc_ch_id = 2;
  }
  uint16_t mbms_mcs = cfg.mbms_mcs;
  if (mbms_mcs > 28) {
    mbms_mcs = 28; // TS 36.213, Table 8.6.1-1
    logger.warning("PMCH data MCS too high, setting it to 28");
  }
  logger.debug("PMCH data MCS=%d", mbms_mcs);
  pmch_item->data_mcs         = mbms_mcs;
  pmch_item->mch_sched_period = srsran::pmch_info_t::mch_sched_period_t::rf64;
  pmch_item->sf_alloc_end     = 64 * 6;

  // Configure PHY when PHY is done being initialized
  task_sched.defer_task([this, sibs2, sibs13, mcch_t]() mutable {
    phy->configure_mbsfn(&sibs2, &sibs13, mcch_t);
    mac->write_mcch(&sibs2, &sibs13, &mcch_t, mcch_payload_buffer, current_mcch_length);
    add_user(SRSRAN_MRNTI, {});
  });
}

int rrc::pack_mcch()
{
  mcch.msg.set_c1();
  mbsfn_area_cfg_r9_s& area_cfg_r9      = mcch.msg.c1().mbsfn_area_cfg_r9();
  area_cfg_r9.common_sf_alloc_period_r9 = mbsfn_area_cfg_r9_s::common_sf_alloc_period_r9_e_::rf64;
  area_cfg_r9.common_sf_alloc_r9.resize(1);
  mbsfn_sf_cfg_s* sf_alloc_item          = &area_cfg_r9.common_sf_alloc_r9[0];
  sf_alloc_item->radioframe_alloc_offset = 0;
  sf_alloc_item->radioframe_alloc_period = mbsfn_sf_cfg_s::radioframe_alloc_period_e_::n1;
  sf_alloc_item->sf_alloc.set_one_frame().from_number(32 + 31);

  area_cfg_r9.pmch_info_list_r9.resize(1);
  pmch_info_r9_s* pmch_item = &area_cfg_r9.pmch_info_list_r9[0];
  pmch_item->mbms_session_info_list_r9.resize(1);

  pmch_item->mbms_session_info_list_r9[0].lc_ch_id_r9           = 1;
  pmch_item->mbms_session_info_list_r9[0].session_id_r9_present = true;
  pmch_item->mbms_session_info_list_r9[0].session_id_r9[0]      = 0;
  pmch_item->mbms_session_info_list_r9[0].tmgi_r9.plmn_id_r9.set_explicit_value_r9();
  srsran::plmn_id_t plmn_obj;
  plmn_obj.from_string("00003");
  srsran::to_asn1(&pmch_item->mbms_session_info_list_r9[0].tmgi_r9.plmn_id_r9.explicit_value_r9(), plmn_obj);
  uint8_t byte[] = {0x0, 0x0, 0x0};
  memcpy(&pmch_item->mbms_session_info_list_r9[0].tmgi_r9.service_id_r9[0], &byte[0], 3);

  if (pmch_item->mbms_session_info_list_r9.size() > 1) {
    pmch_item->mbms_session_info_list_r9[1].lc_ch_id_r9           = 2;
    pmch_item->mbms_session_info_list_r9[1].session_id_r9_present = true;
    pmch_item->mbms_session_info_list_r9[1].session_id_r9[0]      = 1;
    pmch_item->mbms_session_info_list_r9[1].tmgi_r9.plmn_id_r9.set_explicit_value_r9() =
        pmch_item->mbms_session_info_list_r9[0].tmgi_r9.plmn_id_r9.explicit_value_r9();
    byte[2] = 1;
    memcpy(&pmch_item->mbms_session_info_list_r9[1].tmgi_r9.service_id_r9[0],
           &byte[0],
           3); // TODO: Check if service is set to 1
  }

  uint16_t mbms_mcs = cfg.mbms_mcs;
  if (mbms_mcs > 28) {
    mbms_mcs = 28; // TS 36.213, Table 8.6.1-1
    logger.warning("PMCH data MCS too high, setting it to 28");
  }

  logger.debug("PMCH data MCS=%d", mbms_mcs);
  pmch_item->pmch_cfg_r9.data_mcs_r9         = mbms_mcs;
  pmch_item->pmch_cfg_r9.mch_sched_period_r9 = pmch_cfg_r9_s::mch_sched_period_r9_e_::rf64;
  pmch_item->pmch_cfg_r9.sf_alloc_end_r9     = 64 * 6;

  const int     rlc_header_len = 1;
  asn1::bit_ref bref(&mcch_payload_buffer[rlc_header_len], sizeof(mcch_payload_buffer) - rlc_header_len);
  if (mcch.pack(bref) != asn1::SRSASN_SUCCESS) {
    logger.error("Failed to pack MCCH message");
  }

  current_mcch_length = bref.distance_bytes(&mcch_payload_buffer[1]);
  current_mcch_length = current_mcch_length + rlc_header_len;
  return current_mcch_length;
}

/*******************************************************************************
  RRC run tti method
*******************************************************************************/

void rrc::tti_clock()
{
  // pop cmds from queue
  rrc_pdu p;
  while (rx_pdu_queue.try_pop(p)) {
    // check if user exists
    auto user_it = users.find(p.rnti);
    if (user_it == users.end()) {
      if (p.pdu != nullptr) {
        log_rx_pdu_fail(p.rnti, p.lcid, *p.pdu, "unknown rnti");
      } else {
        logger.warning("Ignoring rnti=0x%x command %d arg %d. Cause: unknown rnti", p.rnti, p.lcid, p.arg);
      }
      continue;
    }
    ue& ue = *user_it->second;

    // handle queue cmd
    switch (p.lcid) {
      case srb_to_lcid(lte_srb::srb0):
        parse_ul_ccch(ue, std::move(p.pdu));
        break;
      case srb_to_lcid(lte_srb::srb1):
      case srb_to_lcid(lte_srb::srb2):
        parse_ul_dcch(ue, p.lcid, std::move(p.pdu));
        break;
      case LCID_REM_USER:
        rem_user(p.rnti);
        break;
      case LCID_REL_USER:
        process_release_complete(p.rnti);
        break;
      case LCID_ACT_USER:
        user_it->second->set_activity();
        break;
      case LCID_RADLINK_DL:
        user_it->second->set_radiolink_dl_state(p.arg);
        break;
      case LCID_RADLINK_UL:
        user_it->second->set_radiolink_ul_state(p.arg);
        break;
      case LCID_RLC_RTX:
        user_it->second->max_rlc_retx_reached();
        break;
      case LCID_PROT_FAIL:
        user_it->second->protocol_failure();
        break;
      case LCID_EXIT:
        logger.info("Exiting thread");
        break;
      default:
        logger.error("Rx PDU with invalid bearer id: %d", p.lcid);
        break;
    }
  }
}

void rrc::log_rx_pdu_fail(uint16_t rnti, uint32_t lcid, srsran::const_byte_span pdu, const char* cause_str)
{
  logger.error(
      pdu.data(), pdu.size(), "Rx %s PDU, rnti=0x%x - Discarding. Cause: %s", get_rb_name(lcid), rnti, cause_str);
}

void rrc::log_rxtx_pdu_impl(direction_t             dir,
                            uint16_t                rnti,
                            uint32_t                lcid,
                            srsran::const_byte_span pdu,
                            const char*             msg_type)
{
  static const char* dir_str[] = {"Rx", "Tx", "Tx S1AP", "Rx S1AP"};
  fmt::memory_buffer membuf;
  fmt::format_to(membuf, "{} ", dir_str[dir]);
  if (rnti != SRSRAN_PRNTI and rnti != SRSRAN_SIRNTI) {
    if (dir == Tx or dir == Rx) {
      fmt::format_to(membuf, "{} ", srsran::get_srb_name(srsran::lte_lcid_to_srb(lcid)));
    }
    fmt::format_to(membuf, "PDU, rnti=0x{:x} ", rnti);
  } else {
    fmt::format_to(membuf, "Broadcast PDU ");
  }
  fmt::format_to(membuf, "- {} ({} B)", msg_type, pdu.size());

  logger.info(pdu.data(), pdu.size(), "%s", srsran::to_c_str(membuf));
}

} // namespace srsenb
