/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/*! \file flexran_agent_ran_api.c
 * \brief FlexRAN RAN API abstraction
 * \author N. Nikaein, X. Foukas, S. SHARIAT BAGHERI and R. Schmidt
 * \date 2017
 * \version 0.1
 */

#include <dlfcn.h>
#include "flexran_agent_ran_api.h"
#include "s1ap_eNB_ue_context.h"
#include "s1ap_eNB_management_procedures.h"
#include "openair2/LAYER2/MAC/slicing/slicing.h"

#include "common/ran_context.h"
extern RAN_CONTEXT_t RC;

static inline int phy_is_present(mid_t mod_id, uint8_t cc_id) {
  return RC.eNB && RC.eNB[mod_id] && RC.eNB[mod_id][cc_id];
}

static inline int mac_is_present(mid_t mod_id) {
  return RC.mac && RC.mac[mod_id];
}

static inline int rrc_is_present(mid_t mod_id) {
  return RC.rrc && RC.rrc[mod_id];
}

uint32_t flexran_get_current_time_ms(mid_t mod_id, int subframe_flag) {
  if (!mac_is_present(mod_id)) return 0;

  if (subframe_flag == 1)
    return RC.mac[mod_id]->frame*10 + RC.mac[mod_id]->subframe;
  else
    return RC.mac[mod_id]->frame*10;
}

frame_t flexran_get_current_frame(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return 0;

  //  #warning "SFN will not be in [0-1023] when oaisim is used"
  return RC.mac[mod_id]->frame;
}

frame_t flexran_get_current_system_frame_num(mid_t mod_id) {
  return flexran_get_current_frame(mod_id) % 1024;
}

sub_frame_t flexran_get_current_subframe(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->subframe;
}

uint32_t flexran_get_sfn_sf(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return 0;

  return flexran_get_current_frame(mod_id) * 10 + flexran_get_current_subframe(mod_id);
}

uint16_t flexran_get_future_sfn_sf(mid_t mod_id, int ahead_of_time) {
  if (!mac_is_present(mod_id)) return 0;

  frame_t frame = flexran_get_current_system_frame_num(mod_id);
  sub_frame_t subframe = flexran_get_current_subframe(mod_id);
  uint16_t sfn_sf, frame_mask, sf_mask;
  int additional_frames;
  subframe = (subframe + ahead_of_time) % 10;

  if (subframe < flexran_get_current_subframe(mod_id))
    frame = (frame + 1) % 1024;

  additional_frames = ahead_of_time / 10;
  frame = (frame + additional_frames) % 1024;
  frame_mask = (1 << 12) - 1;
  sf_mask = (1 << 4) - 1;
  sfn_sf = (subframe & sf_mask) | ((frame & frame_mask) << 4);
  return sfn_sf;
}

int flexran_get_mac_num_ues(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.num_UEs;
}

int flexran_get_num_ue_lcs(mid_t mod_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return 0;

  // Not sure whether this is needed: if (!rrc_is_present(mod_id)) return 0;
  const rnti_t rnti = flexran_get_mac_ue_crnti(mod_id, ue_id);
  const int s = mac_eNB_get_rrc_status(mod_id, rnti);

  if (s < RRC_CONNECTED)
    return 0;
  else if (s == RRC_CONNECTED)
    return 1;
  else
    return 3;
}

int flexran_get_mac_ue_id_rnti(mid_t mod_id, rnti_t rnti) {
  int n;

  if (!mac_is_present(mod_id)) return 0;

  /* get the (active) UE with RNTI i */
  for (n = 0; n < MAX_MOBILES_PER_ENB; ++n) {
    if (RC.mac[mod_id]->UE_info.active[n] == TRUE
        && rnti == UE_RNTI(mod_id, n)) {
      return n;
    }
  }

  return 0;
}

int flexran_get_mac_ue_id(mid_t mod_id, int i) {
  int n;

  if (!mac_is_present(mod_id)) return 0;

  /* get the (i+1)'th active UE */
  for (n = 0; n < MAX_MOBILES_PER_ENB; ++n) {
    if (RC.mac[mod_id]->UE_info.active[n] == TRUE) {
      if (i == 0)
        return n;

      --i;
    }
  }

  return 0;
}

rnti_t flexran_get_mac_ue_crnti(mid_t mod_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return 0;

  return UE_RNTI(mod_id, ue_id);
}

int flexran_get_ue_bsr_ul_buffer_info(mid_t mod_id, mid_t ue_id, lcid_t lcid) {
  if (!mac_is_present(mod_id)) return -1;

  return RC.mac[mod_id]->UE_info.UE_template[UE_PCCID(mod_id, ue_id)][ue_id].ul_buffer_info[lcid];
}

int8_t flexran_get_ue_phr(mid_t mod_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.UE_template[UE_PCCID(mod_id, ue_id)][ue_id].phr_info;
}

uint8_t flexran_get_ue_wcqi(mid_t mod_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.UE_sched_ctrl[ue_id].dl_cqi[0];
}

rlc_buffer_occupancy_t flexran_get_tx_queue_size(mid_t mod_id, mid_t ue_id, logical_chan_id_t channel_id) {
  if (!mac_is_present(mod_id)) return 0;

  rnti_t rnti = flexran_get_mac_ue_crnti(mod_id, ue_id);
  frame_t frame = flexran_get_current_frame(mod_id);
  sub_frame_t subframe = flexran_get_current_subframe(mod_id);
  mac_rlc_status_resp_t rlc_status = mac_rlc_status_ind(mod_id,rnti, mod_id, frame, subframe, ENB_FLAG_YES,MBMS_FLAG_NO, channel_id, 0, 0
                                                       );
  return rlc_status.bytes_in_buffer;
}

rlc_buffer_occupancy_t flexran_get_num_pdus_buffer(mid_t mod_id, mid_t ue_id, logical_chan_id_t channel_id) {
  if (!mac_is_present(mod_id)) return 0;

  rnti_t rnti = flexran_get_mac_ue_crnti(mod_id,ue_id);
  frame_t frame = flexran_get_current_frame(mod_id);
  sub_frame_t subframe = flexran_get_current_subframe(mod_id);
  mac_rlc_status_resp_t rlc_status = mac_rlc_status_ind(mod_id,rnti, mod_id, frame, subframe, ENB_FLAG_YES,MBMS_FLAG_NO, channel_id, 0, 0
                                                       );
  return rlc_status.pdus_in_buffer;
}

frame_t flexran_get_hol_delay(mid_t mod_id, mid_t ue_id, logical_chan_id_t channel_id) {
  if (!mac_is_present(mod_id)) return 0;

  rnti_t rnti = flexran_get_mac_ue_crnti(mod_id,ue_id);
  frame_t frame = flexran_get_current_frame(mod_id);
  sub_frame_t subframe = flexran_get_current_subframe(mod_id);
  mac_rlc_status_resp_t rlc_status = mac_rlc_status_ind(mod_id, rnti, mod_id, frame, subframe, ENB_FLAG_YES, MBMS_FLAG_NO, channel_id, 0, 0
                                                       );
  return rlc_status.head_sdu_creation_time;
}

int32_t flexran_get_TA(mid_t mod_id, mid_t ue_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  int32_t tau = RC.eNB[mod_id][cc_id]->UE_stats[ue_id].timing_advance_update;

  switch (flexran_get_N_RB_DL(mod_id, cc_id)) {
    case 6:
      return tau;

    case 15:
      return tau / 2;

    case 25:
      return tau / 4;

    case 50:
      return tau / 8;

    case 75:
      return tau / 12;

    case 100:
      if (flexran_get_threequarter_fs(mod_id, cc_id) == 0)
        return tau / 16;
      else
        return tau / 12;

    default:
      return 0;
  }
}

uint32_t flexran_get_total_size_dl_mac_sdus(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].total_sdu_bytes;
}

uint32_t flexran_get_total_size_ul_mac_sdus(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  uint64_t bytes = 0;

  for (int i = 0; i < NB_RB_MAX; ++i) {
    bytes += RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].num_bytes_rx[i];
  }

  return bytes;
}

uint32_t flexran_get_TBS_dl(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].TBS;
}

uint32_t flexran_get_TBS_ul(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].ulsch_TBS;
}

uint16_t flexran_get_num_prb_retx_dl_per_ue(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].rbs_used_retx;
}

uint32_t flexran_get_num_prb_retx_ul_per_ue(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].rbs_used_retx_rx;
}

uint16_t flexran_get_num_prb_dl_tx_per_ue(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].rbs_used;
}

uint16_t flexran_get_num_prb_ul_rx_per_ue(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].rbs_used_rx;
}

uint8_t flexran_get_ue_wpmi(mid_t mod_id, mid_t ue_id, uint8_t cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.UE_sched_ctrl[ue_id].periodic_wideband_pmi[cc_id];
}

uint8_t flexran_get_mcs1_dl(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].dlsch_mcs1;
}

uint8_t flexran_get_mcs2_dl(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].dlsch_mcs2;
}

uint8_t flexran_get_mcs1_ul(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].ulsch_mcs1;
}

uint8_t flexran_get_mcs2_ul(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].ulsch_mcs2;
}

uint32_t flexran_get_total_prb_dl_tx_per_ue(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].total_rbs_used;
}

uint32_t flexran_get_total_prb_ul_rx_per_ue(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].total_rbs_used_rx;
}

uint32_t flexran_get_total_num_pdu_dl(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].total_num_pdus;
}

uint32_t flexran_get_total_num_pdu_ul(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].total_num_pdus_rx;
}

uint64_t flexran_get_total_TBS_dl(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].total_pdu_bytes;
}

uint64_t flexran_get_total_TBS_ul(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].total_ulsch_TBS;
}

int flexran_get_harq_round(mid_t mod_id, uint8_t cc_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].harq_round;
}

uint32_t flexran_get_num_mac_sdu_tx(mid_t mod_id, mid_t ue_id, int cc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].num_mac_sdu_tx;
}

unsigned char flexran_get_mac_sdu_lcid_index(mid_t mod_id, mid_t ue_id, int cc_id, int index) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].lcid_sdu[index];
}

uint32_t flexran_get_mac_sdu_size(mid_t mod_id, mid_t ue_id, int cc_id, int lcid) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.eNB_UE_stats[cc_id][ue_id].sdu_length_tx[lcid];
}


/* TODO needs to be revised */
void flexran_update_TA(mid_t mod_id, mid_t ue_id, uint8_t cc_id) {
  /*
    UE_info_t *UE_info=&eNB_mac_inst[mod_id].UE_info;
    UE_sched_ctrl *ue_sched_ctl = &UE_info->UE_sched_ctrl[ue_id];

    if (ue_sched_ctl->ta_timer == 0) {

      // WE SHOULD PROTECT the eNB_UE_stats with a mutex here ...
      //    LTE_eNB_UE_stats    *eNB_UE_stats = mac_xface->get_eNB_UE_stats(mod_id, CC_id, rnti);
      //ue_sched_ctl->ta_timer          = 20; // wait 20 subframes before taking TA measurement from PHY
      ue_sched_ctl->ta_update = flexran_get_TA(mod_id, ue_id, CC_id);

      // clear the update in case PHY does not have a new measurement after timer expiry
      //    eNB_UE_stats->timing_advance_update       = 0;
    } else {
      ue_sched_ctl->ta_timer--;
      ue_sched_ctl->ta_update         = 0;  // don't trigger a timing advance command
    }
  #warning "Implement flexran_update_TA() in RAN API"
  */
}

/* TODO needs to be revised, looks suspicious: why do we need UE stats? */
int flexran_get_MAC_CE_bitmap_TA(mid_t mod_id, mid_t ue_id, uint8_t cc_id) {
  /*
  #warning "Implement flexran_get_MAC_CE_bitmap_TA() in RAN API"
  */
  if (!phy_is_present(mod_id, cc_id)) return 0;

  /* UE_stats can not be null, they are an array in RC
  LTE_eNB_UE_stats *eNB_UE_stats = mac_xface->get_eNB_UE_stats(mod_id,CC_id,rnti);

  if (eNB_UE_stats == NULL) {
    return 0;
  }
  */

  if (flexran_get_TA(mod_id, ue_id, cc_id) != 0) {
    return PROTOCOL__FLEX_CE_TYPE__FLPCET_TA;
  } else {
    return 0;
  }
}

int flexran_get_active_CC(mid_t mod_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.numactiveCCs[ue_id];
}

uint8_t flexran_get_current_RI(mid_t mod_id, mid_t ue_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->UE_stats[ue_id].rank;
}

int flexran_get_tpc(mid_t mod_id, mid_t ue_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  /* before: tested that UL_rssi != NULL and set parameter ([0]), but it is a
   * static array -> target_rx_power is useless in old ifs?! */
  int pCCid = UE_PCCID(mod_id,ue_id);
  int32_t target_rx_power = RC.eNB[mod_id][pCCid]->frame_parms.ul_power_control_config_common.p0_NominalPUSCH;
  int32_t normalized_rx_power = RC.eNB[mod_id][cc_id]->UE_stats[ue_id].UL_rssi[0];
  int tpc;

  if (normalized_rx_power > target_rx_power + 1)
    tpc = 0;  //-1
  else if (normalized_rx_power < target_rx_power - 1)
    tpc = 2;  //+1
  else
    tpc = 1;  //0

  return tpc;
}

int flexran_get_harq(mid_t       mod_id,
                     uint8_t     cc_id,
                     mid_t       ue_id,
                     frame_t     frame,
                     sub_frame_t subframe,
                     uint8_t    *pid,
                     uint8_t    *round,
                     uint8_t     harq_flag) {
  /* TODO: Add int TB in function parameters to get the status of the second
   * TB. This can be done to by editing in get_ue_active_harq_pid function in
   * line 272 file: phy_procedures_lte_eNB.c to add DLSCH_ptr =
   * PHY_vars_eNB_g[Mod_id][CC_id]->dlsch_eNB[(uint32_t)UE_id][1];*/
  /* TODO IMPLEMENT */
  /*
  uint8_t harq_pid;
  uint8_t harq_round;

  if (mac_xface_not_ready()) return 0 ;

  uint16_t rnti = flexran_get_mac_ue_crnti(mod_id,ue_id);
  if (harq_flag == openair_harq_DL){

      mac_xface->get_ue_active_harq_pid(mod_id,CC_id,rnti,frame,subframe,&harq_pid,&harq_round,openair_harq_DL);

   } else if (harq_flag == openair_harq_UL){

     mac_xface->get_ue_active_harq_pid(mod_id,CC_id,rnti,frame,subframe,&harq_pid,round,openair_harq_UL);
   }
   else {

      LOG_W(FLEXRAN_AGENT,"harq_flag is not recongnized");
   }


  *pid = harq_pid;
  *round = harq_round;*/
  /* if (round > 0) { */
  /*   *status = 1; */
  /* } else { */
  /*   *status = 0; */
  /* } */
  /*return *round;*/
  /*
  #warning "Implement flexran_get_harq() in RAN API"
  */
  return 0;
}

int32_t flexran_get_p0_pucch_dbm(mid_t mod_id, mid_t ue_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->UE_stats[ue_id].Po_PUCCH_dBm;
}

int8_t flexran_get_p0_nominal_pucch(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.ul_power_control_config_common.p0_NominalPUCCH;
}

int32_t flexran_get_p0_pucch_status(mid_t mod_id, mid_t ue_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->UE_stats[ue_id].Po_PUCCH_update;
}

int flexran_update_p0_pucch(mid_t mod_id, mid_t ue_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  RC.eNB[mod_id][cc_id]->UE_stats[ue_id].Po_PUCCH_update = 0;
  return 0;
}


/*
 * ************************************
 * Get Messages for eNB Configuration Reply
 * ************************************
 */
uint8_t flexran_get_threequarter_fs(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.threequarter_fs;
}


uint8_t flexran_get_hopping_offset(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.pusch_config_common.pusch_HoppingOffset;
}

Protocol__FlexHoppingMode flexran_get_hopping_mode(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return -1;

  switch (RC.eNB[mod_id][cc_id]->frame_parms.pusch_config_common.hoppingMode) {
    case interSubFrame:
      return PROTOCOL__FLEX_HOPPING_MODE__FLHM_INTER;

    case intraAndInterSubFrame:
      return PROTOCOL__FLEX_HOPPING_MODE__FLHM_INTERINTRA;
  }

  return -1;
}

uint8_t flexran_get_n_SB(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.pusch_config_common.n_SB;
}

Protocol__FlexQam flexran_get_enable64QAM(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  if (RC.eNB[mod_id][cc_id]->frame_parms.pusch_config_common.enable64QAM == TRUE)
    return PROTOCOL__FLEX_QAM__FLEQ_MOD_64QAM;
  else
    return PROTOCOL__FLEX_QAM__FLEQ_MOD_16QAM;
}

Protocol__FlexPhichDuration flexran_get_phich_duration(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return -1;

  switch (RC.eNB[mod_id][cc_id]->frame_parms.phich_config_common.phich_duration) {
    case normal:
      return PROTOCOL__FLEX_PHICH_DURATION__FLPD_NORMAL;

    case extended:
      return PROTOCOL__FLEX_PHICH_DURATION__FLPD_EXTENDED;
  }

  return -1;
}

Protocol__FlexPhichResource flexran_get_phich_resource(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return -1;

  switch (RC.eNB[mod_id][cc_id]->frame_parms.phich_config_common.phich_resource) {
    case oneSixth:
      return PROTOCOL__FLEX_PHICH_RESOURCE__FLPR_ONE_SIXTH;

    case half:
      return PROTOCOL__FLEX_PHICH_RESOURCE__FLPR_HALF;

    case one:
      return PROTOCOL__FLEX_PHICH_RESOURCE__FLPR_ONE;

    case two:
      return PROTOCOL__FLEX_PHICH_RESOURCE__FLPR_TWO;
  }

  return -1;
}

uint16_t flexran_get_n1pucch_an(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.pucch_config_common.n1PUCCH_AN;
}

uint8_t flexran_get_nRB_CQI(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.pucch_config_common.nRB_CQI;
}

uint8_t flexran_get_deltaPUCCH_Shift(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.pucch_config_common.deltaPUCCH_Shift;
}

uint8_t flexran_get_prach_ConfigIndex(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.prach_config_common.prach_ConfigInfo.prach_ConfigIndex;
}

uint8_t flexran_get_prach_FreqOffset(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.prach_config_common.prach_ConfigInfo.prach_FreqOffset;
}

uint8_t flexran_get_maxHARQ_Msg3Tx(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.maxHARQ_Msg3Tx;
}

Protocol__FlexUlCyclicPrefixLength flexran_get_ul_cyclic_prefix_length(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return -1;

  switch (RC.eNB[mod_id][cc_id]->frame_parms.Ncp_UL) {
    case EXTENDED:
      return PROTOCOL__FLEX_UL_CYCLIC_PREFIX_LENGTH__FLUCPL_EXTENDED;

    case NORMAL:
      return PROTOCOL__FLEX_UL_CYCLIC_PREFIX_LENGTH__FLUCPL_NORMAL;
  }

  return -1;
}

Protocol__FlexDlCyclicPrefixLength flexran_get_dl_cyclic_prefix_length(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return -1;

  switch (RC.eNB[mod_id][cc_id]->frame_parms.Ncp) {
    case EXTENDED:
      return PROTOCOL__FLEX_DL_CYCLIC_PREFIX_LENGTH__FLDCPL_EXTENDED;

    case NORMAL:
      return PROTOCOL__FLEX_DL_CYCLIC_PREFIX_LENGTH__FLDCPL_NORMAL;
  }

  return -1;
}

uint16_t flexran_get_cell_id(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.Nid_cell;
}

uint8_t flexran_get_srs_BandwidthConfig(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.soundingrs_ul_config_common.srs_BandwidthConfig;
}

uint8_t flexran_get_srs_SubframeConfig(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.soundingrs_ul_config_common.srs_SubframeConfig;
}

uint8_t flexran_get_srs_MaxUpPts(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.soundingrs_ul_config_common.srs_MaxUpPts;
}

uint8_t flexran_get_N_RB_DL(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.N_RB_DL;
}

uint8_t flexran_get_N_RB_UL(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.N_RB_UL;
}

uint8_t flexran_get_N_RBG(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.N_RBG;
}

uint8_t flexran_get_subframe_assignment(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.tdd_config;
}

uint8_t flexran_get_special_subframe_assignment(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.tdd_config_S;
}

long flexran_get_ra_ResponseWindowSize(mid_t mod_id, uint8_t cc_id) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->configuration.radioresourceconfig[cc_id].rach_raResponseWindowSize;
}

long flexran_get_mac_ContentionResolutionTimer(mid_t mod_id, uint8_t cc_id) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->configuration.radioresourceconfig[cc_id].rach_macContentionResolutionTimer;
}

Protocol__FlexDuplexMode flexran_get_duplex_mode(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  switch (RC.eNB[mod_id][cc_id]->frame_parms.frame_type) {
    case TDD:
      return PROTOCOL__FLEX_DUPLEX_MODE__FLDM_TDD;

    case FDD:
      return PROTOCOL__FLEX_DUPLEX_MODE__FLDM_FDD;

    default:
      return -1;
  }
}

long flexran_get_si_window_length(mid_t mod_id, uint8_t cc_id) {
  if (!rrc_is_present(mod_id) || !RC.rrc[mod_id]->carrier[cc_id].sib1) return 0;

  return RC.rrc[mod_id]->carrier[cc_id].sib1->si_WindowLength;
}

uint8_t flexran_get_sib1_length(mid_t mod_id, uint8_t cc_id) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->carrier[cc_id].sizeof_SIB1;
}

uint8_t flexran_get_num_pdcch_symb(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->pdcch_vars[0].num_pdcch_symbols;
}



/*
 * ************************************
 * Get Messages for UE Configuration Reply
 * ************************************
 */
int flexran_get_rrc_num_ues(mid_t mod_id) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->Nb_ue;
}

rnti_t flexran_get_rrc_rnti_nth_ue(mid_t mod_id, int index) {
  if (!rrc_is_present(mod_id)) return 0;

  struct rrc_eNB_ue_context_s *ue_context_p = NULL;
  RB_FOREACH(ue_context_p, rrc_ue_tree_s, &RC.rrc[mod_id]->rrc_ue_head) {
    if (index == 0) return ue_context_p->ue_context.rnti;

    --index;
  }
  return 0;
}

int flexran_get_rrc_rnti_list(mid_t mod_id, rnti_t *list, int max_list) {
  if (!rrc_is_present(mod_id)) return 0;

  int n = 0;
  struct rrc_eNB_ue_context_s *ue_context_p = NULL;
  RB_FOREACH(ue_context_p, rrc_ue_tree_s, &RC.rrc[mod_id]->rrc_ue_head) {
    if (n >= max_list) break;

    list[n] = ue_context_p->ue_context.rnti;
    ++n;
  }
  return n;
}

LTE_TimeAlignmentTimer_t  flexran_get_time_alignment_timer(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.mac_MainConfig) return -1;

  return ue_context_p->ue_context.mac_MainConfig->timeAlignmentTimerDedicated;
}

Protocol__FlexMeasGapConfigPattern flexran_get_meas_gap_config(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measGapConfig) return -1;

  if (ue_context_p->ue_context.measGapConfig->present != LTE_MeasGapConfig_PR_setup) return -1;

  switch (ue_context_p->ue_context.measGapConfig->choice.setup.gapOffset.present) {
    case LTE_MeasGapConfig__setup__gapOffset_PR_gp0:
      return PROTOCOL__FLEX_MEAS_GAP_CONFIG_PATTERN__FLMGCP_GP1;

    case LTE_MeasGapConfig__setup__gapOffset_PR_gp1:
      return PROTOCOL__FLEX_MEAS_GAP_CONFIG_PATTERN__FLMGCP_GP2;

    default:
      return PROTOCOL__FLEX_MEAS_GAP_CONFIG_PATTERN__FLMGCP_OFF;
  }
}


long flexran_get_meas_gap_config_offset(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measGapConfig) return -1;

  if (ue_context_p->ue_context.measGapConfig->present != LTE_MeasGapConfig_PR_setup) return -1;

  switch (ue_context_p->ue_context.measGapConfig->choice.setup.gapOffset.present) {
    case LTE_MeasGapConfig__setup__gapOffset_PR_gp0:
      return ue_context_p->ue_context.measGapConfig->choice.setup.gapOffset.choice.gp0;

    case LTE_MeasGapConfig__setup__gapOffset_PR_gp1:
      return ue_context_p->ue_context.measGapConfig->choice.setup.gapOffset.choice.gp1;

    default:
      return -1;
  }
}

uint8_t flexran_get_rrc_status(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return 0;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return RRC_INACTIVE;

  return ue_context_p->ue_context.Status;
}

uint64_t flexran_get_ue_aggregated_max_bitrate_dl(mid_t mod_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return 0;

  return 0; //RC.mac[mod_id]->UE_info.UE_sched_ctrl[ue_id].ue_AggregatedMaximumBitrateDL;
}

uint64_t flexran_get_ue_aggregated_max_bitrate_ul(mid_t mod_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return 0;

  return 0; //RC.mac[mod_id]->UE_info.UE_sched_ctrl[ue_id].ue_AggregatedMaximumBitrateUL;
}

int flexran_get_half_duplex(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.UE_Capability) return -1;

  LTE_SupportedBandListEUTRA_t *bands = &ue_context_p->ue_context.UE_Capability->rf_Parameters.supportedBandListEUTRA;

  for (int i = 0; i < bands->list.count; i++) {
    if (bands->list.array[i]->halfDuplex > 0) return 1;
  }

  return 0;
}

int flexran_get_intra_sf_hopping(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.UE_Capability) return -1;

  if (!ue_context_p->ue_context.UE_Capability->featureGroupIndicators) return -1;

  /* According to TS 36.331 Annex B.1, Intra SF Hopping is bit 1 (leftmost bit)
   * in this bitmap, i.e. the eighth bit (from right) in the first bye (from
   * left) */
  BIT_STRING_t *fgi = ue_context_p->ue_context.UE_Capability->featureGroupIndicators;
  uint8_t buf = fgi->buf[0];
  return (buf >> 7) & 1;
}

int flexran_get_type2_sb_1(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.UE_Capability) return -1;

  if (!ue_context_p->ue_context.UE_Capability->featureGroupIndicators) return -1;

  /* According to TS 36.331 Annex B.1, Predefined intra- and inter-sf or
   * predfined inter-sf frequency hopping for PUSCH with N_sb>1 is bit 21 (bit
   * 1 is leftmost bit) in this bitmap, i.e. the fourth bit (from right) in the
   * third byte (from left) */
  BIT_STRING_t *fgi = ue_context_p->ue_context.UE_Capability->featureGroupIndicators;
  uint8_t buf = fgi->buf[2];
  return (buf >> 3) & 1;
}

long flexran_get_ue_category(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.UE_Capability) return -1;

  return ue_context_p->ue_context.UE_Capability->ue_Category;
}

int flexran_get_res_alloc_type1(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.UE_Capability) return -1;

  if (!ue_context_p->ue_context.UE_Capability->featureGroupIndicators) return -1;

  /* According to TS 36.331 Annex B.1, Resource allocation type 1 for PDSCH is
   * bit 2 (bit 1 is leftmost bit) in this bitmap, i.e. the seventh bit (from
   * right) in the first byte (from left) */
  BIT_STRING_t *fgi = ue_context_p->ue_context.UE_Capability->featureGroupIndicators;
  uint8_t buf = fgi->buf[0];
  return (buf >> 6) & 1;
}

long flexran_get_ue_transmission_mode(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->antennaInfo) return -1;

  return ue_context_p->ue_context.physicalConfigDedicated->antennaInfo->choice.explicitValue.transmissionMode;
}

BOOLEAN_t flexran_get_tti_bundling(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.mac_MainConfig) return -1;

  if (!ue_context_p->ue_context.mac_MainConfig->ul_SCH_Config) return -1;

  return ue_context_p->ue_context.mac_MainConfig->ul_SCH_Config->ttiBundling;
}

long flexran_get_maxHARQ_TX(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.mac_MainConfig) return -1;

  if (!ue_context_p->ue_context.mac_MainConfig->ul_SCH_Config) return -1;

  return *(ue_context_p->ue_context.mac_MainConfig->ul_SCH_Config->maxHARQ_Tx);
}

long flexran_get_beta_offset_ack_index(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->pusch_ConfigDedicated) return -1;

  return ue_context_p->ue_context.physicalConfigDedicated->pusch_ConfigDedicated->betaOffset_ACK_Index;
}

long flexran_get_beta_offset_ri_index(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->pusch_ConfigDedicated) return -1;

  return ue_context_p->ue_context.physicalConfigDedicated->pusch_ConfigDedicated->betaOffset_RI_Index;
}

long flexran_get_beta_offset_cqi_index(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->pusch_ConfigDedicated) return -1;

  return ue_context_p->ue_context.physicalConfigDedicated->pusch_ConfigDedicated->betaOffset_CQI_Index;
}

BOOLEAN_t flexran_get_simultaneous_ack_nack_cqi(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->cqi_ReportConfig) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->cqi_ReportConfig->cqi_ReportPeriodic) return -1;

  return ue_context_p->ue_context.physicalConfigDedicated->cqi_ReportConfig->cqi_ReportPeriodic->choice.setup.simultaneousAckNackAndCQI;
}

BOOLEAN_t flexran_get_ack_nack_simultaneous_trans(mid_t mod_id, uint8_t cc_id) {
  if (!rrc_is_present(mod_id)) return -1;

  if (!RC.rrc[mod_id]->carrier[cc_id].sib2) return -1;

  return RC.rrc[mod_id]->carrier[cc_id].sib2->radioResourceConfigCommon.soundingRS_UL_ConfigCommon.choice.setup.ackNackSRS_SimultaneousTransmission;
}

Protocol__FlexAperiodicCqiReportMode flexran_get_aperiodic_cqi_rep_mode(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->cqi_ReportConfig) return -1;

  switch (*ue_context_p->ue_context.physicalConfigDedicated->cqi_ReportConfig->cqi_ReportModeAperiodic) {
    case LTE_CQI_ReportModeAperiodic_rm12:
      return PROTOCOL__FLEX_APERIODIC_CQI_REPORT_MODE__FLACRM_RM12;

    case LTE_CQI_ReportModeAperiodic_rm20:
      return PROTOCOL__FLEX_APERIODIC_CQI_REPORT_MODE__FLACRM_RM20;

    case LTE_CQI_ReportModeAperiodic_rm22:
      return PROTOCOL__FLEX_APERIODIC_CQI_REPORT_MODE__FLACRM_RM22;

    case LTE_CQI_ReportModeAperiodic_rm30:
      return PROTOCOL__FLEX_APERIODIC_CQI_REPORT_MODE__FLACRM_RM30;

    case LTE_CQI_ReportModeAperiodic_rm31:
      return PROTOCOL__FLEX_APERIODIC_CQI_REPORT_MODE__FLACRM_RM31;

    case LTE_CQI_ReportModeAperiodic_rm32_v1250:
      return PROTOCOL__FLEX_APERIODIC_CQI_REPORT_MODE__FLACRM_RM32_v1250;

    case LTE_CQI_ReportModeAperiodic_rm10_v1310:
      return PROTOCOL__FLEX_APERIODIC_CQI_REPORT_MODE__FLACRM_RM10_v1310;

    case LTE_CQI_ReportModeAperiodic_rm11_v1310:
      return PROTOCOL__FLEX_APERIODIC_CQI_REPORT_MODE__FLACRM_RM11_v1310;

    default:
      return PROTOCOL__FLEX_APERIODIC_CQI_REPORT_MODE__FLACRM_NONE;
  }
}

long flexran_get_tdd_ack_nack_feedback_mode(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->pucch_ConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->pucch_ConfigDedicated->tdd_AckNackFeedbackMode) return -1;

  return *(ue_context_p->ue_context.physicalConfigDedicated->pucch_ConfigDedicated->tdd_AckNackFeedbackMode);
}

long flexran_get_ack_nack_repetition_factor(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->pucch_ConfigDedicated) return -1;

  return ue_context_p->ue_context.physicalConfigDedicated->pucch_ConfigDedicated->ackNackRepetition.choice.setup.repetitionFactor;
}

long flexran_get_extended_bsr_size(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.mac_MainConfig) return -1;

  if (!ue_context_p->ue_context.mac_MainConfig->ext2) return -1;

  if (!ue_context_p->ue_context.mac_MainConfig->ext2->mac_MainConfig_v1020) return -1;

  return *(ue_context_p->ue_context.mac_MainConfig->ext2->mac_MainConfig_v1020->extendedBSR_Sizes_r10);
}

int flexran_get_ue_transmission_antenna(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated) return -1;

  if (!ue_context_p->ue_context.physicalConfigDedicated->antennaInfo) return -1;

  switch (ue_context_p->ue_context.physicalConfigDedicated->antennaInfo->choice.explicitValue.ue_TransmitAntennaSelection.choice.setup) {
    case LTE_AntennaInfoDedicated__ue_TransmitAntennaSelection__setup_closedLoop:
      return 2;

    case LTE_AntennaInfoDedicated__ue_TransmitAntennaSelection__setup_openLoop:
      return 1;

    default:
      return 0;
  }
}

uint64_t flexran_get_ue_imsi(mid_t mod_id, rnti_t rnti) {
  uint64_t imsi;

  if (!rrc_is_present(mod_id)) return 0;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return 0;

  imsi  = ue_context_p->ue_context.imsi.digit15;
  imsi += ue_context_p->ue_context.imsi.digit14 * 10;              // pow(10, 1)
  imsi += ue_context_p->ue_context.imsi.digit13 * 100;             // pow(10, 2)
  imsi += ue_context_p->ue_context.imsi.digit12 * 1000;            // pow(10, 3)
  imsi += ue_context_p->ue_context.imsi.digit11 * 10000;           // pow(10, 4)
  imsi += ue_context_p->ue_context.imsi.digit10 * 100000;          // pow(10, 5)
  imsi += ue_context_p->ue_context.imsi.digit9  * 1000000;         // pow(10, 6)
  imsi += ue_context_p->ue_context.imsi.digit8  * 10000000;        // pow(10, 7)
  imsi += ue_context_p->ue_context.imsi.digit7  * 100000000;       // pow(10, 8)
  imsi += ue_context_p->ue_context.imsi.digit6  * 1000000000;      // pow(10, 9)
  imsi += ue_context_p->ue_context.imsi.digit5  * 10000000000;     // pow(10, 10)
  imsi += ue_context_p->ue_context.imsi.digit4  * 100000000000;    // pow(10, 11)
  imsi += ue_context_p->ue_context.imsi.digit3  * 1000000000000;   // pow(10, 12)
  imsi += ue_context_p->ue_context.imsi.digit2  * 10000000000000;  // pow(10, 13)
  imsi += ue_context_p->ue_context.imsi.digit1  * 100000000000000; // pow(10, 14)
  return imsi;
}

long flexran_get_lcg(mid_t mod_id, mid_t ue_id, mid_t lc_id) {
  if (!mac_is_present(mod_id)) return 0;

  return RC.mac[mod_id]->UE_info.UE_template[UE_PCCID(mod_id, ue_id)][ue_id].lcgidmap[lc_id];
}

/* TODO Navid: needs to be revised */
int flexran_get_direction(mid_t ue_id, mid_t lc_id) {
  switch (lc_id) {
    case DCCH:
    case DCCH1:
      return 2;

    case DTCH:
      return 1;

    default:
      return -1;
  }
}

uint8_t flexran_get_antenna_ports(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.nb_antenna_ports_eNB;
}

uint32_t flexran_agent_get_operating_dl_freq(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.dl_CarrierFreq / 1000000;
}

uint32_t flexran_agent_get_operating_ul_freq(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.ul_CarrierFreq / 1000000;
}

uint8_t flexran_agent_get_operating_eutra_band(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.eutra_band;
}

int8_t flexran_agent_get_operating_pdsch_refpower(mid_t mod_id, uint8_t cc_id) {
  if (!phy_is_present(mod_id, cc_id)) return 0;

  return RC.eNB[mod_id][cc_id]->frame_parms.pdsch_config_common.referenceSignalPower;
}

long flexran_agent_get_operating_pusch_p0(mid_t mod_id, uint8_t cc_id) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->configuration.radioresourceconfig[cc_id].pusch_p0_Nominal;
}

void flexran_agent_set_operating_dl_freq(mid_t mod_id, uint8_t cc_id, uint32_t dl_freq_mhz) {
  if (phy_is_present(mod_id, cc_id)) {
    RC.eNB[mod_id][cc_id]->frame_parms.dl_CarrierFreq = dl_freq_mhz * 1000000;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set dl_CarrierFreq to %d MHz in PHY: PHY is not present\n", dl_freq_mhz);
  }

  if (rrc_is_present(mod_id)) {
    RC.rrc[mod_id]->configuration.downlink_frequency[cc_id] = dl_freq_mhz * 1000000;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set downlink_frequency to %d MHz in RRC: RRC is not present\n", dl_freq_mhz);
  }
}

void flexran_agent_set_operating_ul_freq(mid_t mod_id, uint8_t cc_id, int32_t ul_freq_mhz_offset) {
  if (phy_is_present(mod_id, cc_id)) {
    uint32_t new_ul_freq_mhz = flexran_agent_get_operating_dl_freq(mod_id, cc_id) + ul_freq_mhz_offset;
    RC.eNB[mod_id][cc_id]->frame_parms.ul_CarrierFreq = new_ul_freq_mhz * 1000000;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set ul_CarrierFreq using offset %d MHz in PHY: PHY is not present\n", ul_freq_mhz_offset);
  }

  if (rrc_is_present(mod_id)) {
    RC.rrc[mod_id]->configuration.uplink_frequency_offset[cc_id] = ul_freq_mhz_offset;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set uplink_frequency_offset to %d MHz in RRC: RRC is not present\n", ul_freq_mhz_offset);
  }
}

void flexran_agent_set_operating_eutra_band(mid_t mod_id, uint8_t cc_id, uint8_t eutra_band) {
  if (phy_is_present(mod_id, cc_id)) {
    RC.eNB[mod_id][cc_id]->frame_parms.eutra_band = eutra_band;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set eutra_band to %d in PHY: PHY is not present\n", eutra_band);
  }

  if (rrc_is_present(mod_id)) {
    RC.rrc[mod_id]->configuration.eutra_band[cc_id] = eutra_band;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set eutra_band to %d in RRC: RRC is not present\n", eutra_band);
  }
}

/* Sets both DL/UL */
void flexran_agent_set_operating_bandwidth(mid_t mod_id, uint8_t cc_id, uint8_t N_RB) {
  if (phy_is_present(mod_id, cc_id)) {
    RC.eNB[mod_id][cc_id]->frame_parms.N_RB_DL = N_RB;
    RC.eNB[mod_id][cc_id]->frame_parms.N_RB_UL = N_RB;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set N_RB_DL and N_RB_UL to %d in PHY: PHY is not present\n", N_RB);
  }

  if (rrc_is_present(mod_id)) {
    RC.rrc[mod_id]->configuration.N_RB_DL[cc_id] = N_RB;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set N_RB_DL to %d in RRC: RRC is not present\n", N_RB);
  }
}

void flexran_agent_set_operating_frame_type(mid_t mod_id, uint8_t cc_id, lte_frame_type_t frame_type) {
  if (phy_is_present(mod_id, cc_id)) {
    RC.eNB[mod_id][cc_id]->frame_parms.frame_type = frame_type;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set frame_type to %d in PHY: PHY is not present\n", frame_type);
  }

  if (rrc_is_present(mod_id)) {
    RC.rrc[mod_id]->configuration.frame_type[cc_id] = frame_type;
  } else {
    LOG_E(FLEXRAN_AGENT, "can not set frame_type to %d in RRC: RRC is not present\n", frame_type);
  }
}

/*********** PDCP  *************/

uint16_t flexran_get_pdcp_uid_from_rnti(mid_t mod_id, rnti_t rnti) {
  if (rnti == NOT_A_RNTI) return 0;

  if (mod_id < 0 || mod_id >= RC.nb_inst)
    return 0;

  for (uint16_t pdcp_uid = 0; pdcp_uid < MAX_MOBILES_PER_ENB; ++pdcp_uid) {
    if (pdcp_enb[mod_id].rnti[pdcp_uid] == rnti)
      return pdcp_uid;
  }

  return 0;
}

/*PDCP super frame counter flexRAN*/
uint32_t flexran_get_pdcp_sfn(mid_t mod_id) {
  if (mod_id < 0 || mod_id >= RC.nb_inst)
    return 0;

  return pdcp_enb[mod_id].sfn;
}

/*PDCP super frame counter flexRAN*/
void flexran_set_pdcp_tx_stat_window(mid_t mod_id, uint16_t uid, uint16_t obs_window) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB)
    return;

  Pdcp_stats_tx_window_ms[mod_id][uid] = obs_window > 0 ? obs_window : 1000;
}

/*PDCP super frame counter flexRAN*/
void flexran_set_pdcp_rx_stat_window(mid_t mod_id, uint16_t uid, uint16_t obs_window) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB)
    return;

  Pdcp_stats_rx_window_ms[mod_id][uid] = obs_window > 0 ? obs_window : 1000;
}

/*PDCP num tx pdu status flexRAN*/
uint32_t flexran_get_pdcp_tx(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_tx[mod_id][uid][lcid];
}

/*PDCP num tx bytes status flexRAN*/
uint32_t flexran_get_pdcp_tx_bytes(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_tx_bytes[mod_id][uid][lcid];
}

/*PDCP number of transmit packet / second status flexRAN*/
uint32_t flexran_get_pdcp_tx_w(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_tx_w[mod_id][uid][lcid];
}

/*PDCP throughput (bit/s) status flexRAN*/
uint32_t flexran_get_pdcp_tx_bytes_w(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_tx_bytes_w[mod_id][uid][lcid];
}

/*PDCP tx sequence number flexRAN*/
uint32_t flexran_get_pdcp_tx_sn(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_tx_sn[mod_id][uid][lcid];
}

/*PDCP tx aggregated packet arrival  flexRAN*/
uint32_t flexran_get_pdcp_tx_aiat(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_tx_aiat[mod_id][uid][lcid];
}

/*PDCP tx aggregated packet arrival  flexRAN*/
uint32_t flexran_get_pdcp_tx_aiat_w(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_tx_aiat_w[mod_id][uid][lcid];
}

/*PDCP num rx pdu status flexRAN*/
uint32_t flexran_get_pdcp_rx(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_rx[mod_id][uid][lcid];
}

/*PDCP num rx bytes status flexRAN*/
uint32_t flexran_get_pdcp_rx_bytes(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_rx_bytes[mod_id][uid][lcid];
}

/*PDCP number of received packet / second  flexRAN*/
uint32_t flexran_get_pdcp_rx_w(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_rx_w[mod_id][uid][lcid];
}

/*PDCP gootput (bit/s) status flexRAN*/
uint32_t flexran_get_pdcp_rx_bytes_w(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_rx_bytes_w[mod_id][uid][lcid];
}

/*PDCP rx sequence number flexRAN*/
uint32_t flexran_get_pdcp_rx_sn(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_rx_sn[mod_id][uid][lcid];
}

/*PDCP rx aggregated packet arrival  flexRAN*/
uint32_t flexran_get_pdcp_rx_aiat(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_rx_aiat[mod_id][uid][lcid];
}

/*PDCP rx aggregated packet arrival  flexRAN*/
uint32_t flexran_get_pdcp_rx_aiat_w(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_rx_aiat_w[mod_id][uid][lcid];
}

/*PDCP num of received outoforder pdu status flexRAN*/
uint32_t flexran_get_pdcp_rx_oo(mid_t mod_id, uint16_t uid, lcid_t lcid) {
  if (mod_id < 0 || mod_id >= RC.nb_inst || uid < 0
      || uid >= MAX_MOBILES_PER_ENB || lcid < 0 || lcid >= NB_RB_MAX)
    return 0;

  return Pdcp_stats_rx_outoforder[mod_id][uid][lcid];
}

/******************** RRC *****************************/
/* RRC Wrappers */
int flexran_call_rrc_reconfiguration (mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  protocol_ctxt_t  ctxt;
  memset(&ctxt, 0, sizeof(ctxt));
  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, mod_id, ENB_FLAG_YES, ue_context_p->ue_context.rnti, flexran_get_current_frame(mod_id), flexran_get_current_subframe (mod_id), mod_id);
  flexran_rrc_eNB_generate_defaultRRCConnectionReconfiguration(&ctxt, ue_context_p, 0);
  return 0;
}

int flexran_call_rrc_trigger_handover (mid_t mod_id, rnti_t rnti, int target_cell_id) {
  if (!rrc_is_present(mod_id)) return -1;

  protocol_ctxt_t  ctxt;
  memset(&ctxt, 0, sizeof(ctxt));
  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, mod_id, ENB_FLAG_YES, ue_context_p->ue_context.rnti, flexran_get_current_frame(mod_id), flexran_get_current_subframe (mod_id), mod_id);
  return flexran_rrc_eNB_trigger_handover(mod_id, &ctxt, ue_context_p, target_cell_id);
}

/* RRC Getters */

LTE_MeasId_t  flexran_get_rrc_pcell_measid(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measResults) return -1;

  return ue_context_p->ue_context.measResults->measId;
}

float flexran_get_rrc_pcell_rsrp(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measResults) return -1;

  return RSRP_meas_mapping[ue_context_p->ue_context.measResults->measResultPCell.rsrpResult];
}

float flexran_get_rrc_pcell_rsrq(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measResults) return -1;

  return RSRQ_meas_mapping[ue_context_p->ue_context.measResults->measResultPCell.rsrqResult];
}

/*Number of neighbouring cells for specific UE*/
int flexran_get_rrc_num_ncell(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return 0;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return 0;

  if (!ue_context_p->ue_context.measResults) return 0;

  if (!ue_context_p->ue_context.measResults->measResultNeighCells) return 0;

  //if (ue_context_p->ue_context.measResults->measResultNeighCells->present != LTE_MeasResults__measResultNeighCells_PR_measResultListEUTRA) return 0;
  return ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.count;
}

long flexran_get_rrc_neigh_phy_cell_id(mid_t mod_id, rnti_t rnti, long cell_id) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measResults) return -1;

  if (!ue_context_p->ue_context.measResults->measResultNeighCells) return -1;

  //if (ue_context_p->ue_context.measResults->measResultNeighCells->present != LTE_MeasResults__measResultNeighCells_PR_measResultListEUTRA) return -1;
  if (!ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]) return -1;

  return ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->physCellId;
}

int flexran_get_rrc_neigh_cgi(mid_t mod_id, rnti_t rnti, long cell_id) {
  if (!rrc_is_present(mod_id)) return 0;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return 0;

  if (!ue_context_p->ue_context.measResults) return 0;

  if (!ue_context_p->ue_context.measResults->measResultNeighCells) return 0;

  //if (ue_context_p->ue_context.measResults->measResultNeighCells->present != LTE_MeasResults__measResultNeighCells_PR_measResultListEUTRA) return 0;
  if (!ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]) return 0;

  return (!ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->cgi_Info)?0:1;
}

uint32_t flexran_get_rrc_neigh_cgi_cell_id(mid_t mod_id, rnti_t rnti, long cell_id) {
  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);
  uint8_t *cId = ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->cgi_Info->cellGlobalId.cellIdentity.buf;
  return ((cId[0] << 20) + (cId[1] << 12) + (cId[2] << 4) + (cId[3] >> 4));
}

uint32_t flexran_get_rrc_neigh_cgi_tac(mid_t mod_id, rnti_t rnti, long cell_id) {
  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);
  uint8_t *tac = ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->cgi_Info->trackingAreaCode.buf;
  return (tac[0] << 8) + (tac[1]);
}

int flexran_get_rrc_neigh_cgi_num_mnc(mid_t mod_id, rnti_t rnti, long cell_id) {
  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);
  return ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->cgi_Info->cellGlobalId.plmn_Identity.mnc.list.count;
}

int flexran_get_rrc_neigh_cgi_num_mcc(mid_t mod_id, rnti_t rnti, long cell_id) {
  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);
  return ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->cgi_Info->cellGlobalId.plmn_Identity.mcc->list.count;
}

uint32_t flexran_get_rrc_neigh_cgi_mnc(mid_t mod_id, rnti_t rnti, long cell_id, int mnc_id) {
  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);
  int num_mnc = ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->cgi_Info->cellGlobalId.plmn_Identity.mnc.list.count;
  return *(ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->cgi_Info->cellGlobalId.plmn_Identity.mnc.list.array[mnc_id]) *
         ((uint32_t) pow(10, num_mnc - mnc_id - 1));
}

uint32_t flexran_get_rrc_neigh_cgi_mcc(mid_t mod_id, rnti_t rnti, long cell_id, int mcc_id) {
  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);
  int num_mcc = ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->cgi_Info->cellGlobalId.plmn_Identity.mcc->list.count;
  return *(ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->cgi_Info->cellGlobalId.plmn_Identity.mcc->list.array[mcc_id]) *
         ((uint32_t) pow(10, num_mcc - mcc_id - 1));
}

float flexran_get_rrc_neigh_rsrp(mid_t mod_id, rnti_t rnti, long cell_id) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measResults) return -1;

  if (!ue_context_p->ue_context.measResults->measResultNeighCells) return -1;

  //if (ue_context_p->ue_context.measResults->measResultNeighCells->present != LTE_MeasResults__measResultNeighCells_PR_measResultListEUTRA) return -1;
  if (!ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]) return -1;

  if (!ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->measResult.rsrpResult) return -1;

  return RSRP_meas_mapping[*(ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->measResult.rsrpResult)];
}

float flexran_get_rrc_neigh_rsrq(mid_t mod_id, rnti_t rnti, long cell_id) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measResults) return -1;

  if (!ue_context_p->ue_context.measResults->measResultNeighCells) return -1;

  //if (ue_context_p->ue_context.measResults->measResultNeighCells->present != LTE_MeasResults__measResultNeighCells_PR_measResultListEUTRA) return -1;
  if (!ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->measResult.rsrqResult) return -1;

  return RSRQ_meas_mapping[*(ue_context_p->ue_context.measResults->measResultNeighCells->choice.measResultListEUTRA.list.array[cell_id]->measResult.rsrqResult)];
}

/* Measurement offsets */

long flexran_get_rrc_ofp(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  return ue_context_p->ue_context.measurement_info->offsetFreq;
}

long flexran_get_rrc_ofn(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  return ue_context_p->ue_context.measurement_info->offsetFreq;
}

long flexran_get_rrc_ocp(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  switch (ue_context_p->ue_context.measurement_info->cellIndividualOffset[0]) {
    case LTE_Q_OffsetRange_dB_24:
      return -24;

    case LTE_Q_OffsetRange_dB_22:
      return -22;

    case LTE_Q_OffsetRange_dB_20:
      return -20;

    case LTE_Q_OffsetRange_dB_18:
      return -18;

    case LTE_Q_OffsetRange_dB_16:
      return -16;

    case LTE_Q_OffsetRange_dB_14:
      return -14;

    case LTE_Q_OffsetRange_dB_12:
      return -12;

    case LTE_Q_OffsetRange_dB_10:
      return -10;

    case LTE_Q_OffsetRange_dB_8:
      return -8;

    case LTE_Q_OffsetRange_dB_6:
      return -6;

    case LTE_Q_OffsetRange_dB_5:
      return -5;

    case LTE_Q_OffsetRange_dB_4:
      return -4;

    case LTE_Q_OffsetRange_dB_3:
      return -3;

    case LTE_Q_OffsetRange_dB_2:
      return -2;

    case LTE_Q_OffsetRange_dB_1:
      return -1;

    case LTE_Q_OffsetRange_dB0:
      return  0;

    case LTE_Q_OffsetRange_dB1:
      return  1;

    case LTE_Q_OffsetRange_dB2:
      return  2;

    case LTE_Q_OffsetRange_dB3:
      return  3;

    case LTE_Q_OffsetRange_dB4:
      return  4;

    case LTE_Q_OffsetRange_dB5:
      return  5;

    case LTE_Q_OffsetRange_dB6:
      return  6;

    case LTE_Q_OffsetRange_dB8:
      return  8;

    case LTE_Q_OffsetRange_dB10:
      return 10;

    case LTE_Q_OffsetRange_dB12:
      return 12;

    case LTE_Q_OffsetRange_dB14:
      return 14;

    case LTE_Q_OffsetRange_dB16:
      return 16;

    case LTE_Q_OffsetRange_dB18:
      return 18;

    case LTE_Q_OffsetRange_dB20:
      return 20;

    case LTE_Q_OffsetRange_dB22:
      return 22;

    case LTE_Q_OffsetRange_dB24:
      return 24;

    default:
      return -99;
  }
}

long flexran_get_rrc_ocn(mid_t mod_id, rnti_t rnti, long cell_id) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  switch (ue_context_p->ue_context.measurement_info->cellIndividualOffset[cell_id+1]) {
    case LTE_Q_OffsetRange_dB_24:
      return -24;

    case LTE_Q_OffsetRange_dB_22:
      return -22;

    case LTE_Q_OffsetRange_dB_20:
      return -20;

    case LTE_Q_OffsetRange_dB_18:
      return -18;

    case LTE_Q_OffsetRange_dB_16:
      return -16;

    case LTE_Q_OffsetRange_dB_14:
      return -14;

    case LTE_Q_OffsetRange_dB_12:
      return -12;

    case LTE_Q_OffsetRange_dB_10:
      return -10;

    case LTE_Q_OffsetRange_dB_8:
      return -8;

    case LTE_Q_OffsetRange_dB_6:
      return -6;

    case LTE_Q_OffsetRange_dB_5:
      return -5;

    case LTE_Q_OffsetRange_dB_4:
      return -4;

    case LTE_Q_OffsetRange_dB_3:
      return -3;

    case LTE_Q_OffsetRange_dB_2:
      return -2;

    case LTE_Q_OffsetRange_dB_1:
      return -1;

    case LTE_Q_OffsetRange_dB0:
      return  0;

    case LTE_Q_OffsetRange_dB1:
      return  1;

    case LTE_Q_OffsetRange_dB2:
      return  2;

    case LTE_Q_OffsetRange_dB3:
      return  3;

    case LTE_Q_OffsetRange_dB4:
      return  4;

    case LTE_Q_OffsetRange_dB5:
      return  5;

    case LTE_Q_OffsetRange_dB6:
      return  6;

    case LTE_Q_OffsetRange_dB8:
      return  8;

    case LTE_Q_OffsetRange_dB10:
      return 10;

    case LTE_Q_OffsetRange_dB12:
      return 12;

    case LTE_Q_OffsetRange_dB14:
      return 14;

    case LTE_Q_OffsetRange_dB16:
      return 16;

    case LTE_Q_OffsetRange_dB18:
      return 18;

    case LTE_Q_OffsetRange_dB20:
      return 20;

    case LTE_Q_OffsetRange_dB22:
      return 22;

    case LTE_Q_OffsetRange_dB24:
      return 24;

    default:
      return -99;
  }
}

long flexran_get_filter_coeff_rsrp(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  switch (ue_context_p->ue_context.measurement_info->filterCoefficientRSRP) {
    case LTE_FilterCoefficient_fc0:
      return 0;

    case LTE_FilterCoefficient_fc1:
      return 1;

    case LTE_FilterCoefficient_fc2:
      return 2;

    case LTE_FilterCoefficient_fc3:
      return 3;

    case LTE_FilterCoefficient_fc4:
      return 4;

    case LTE_FilterCoefficient_fc5:
      return 5;

    case LTE_FilterCoefficient_fc6:
      return 6;

    case LTE_FilterCoefficient_fc7:
      return 7;

    case LTE_FilterCoefficient_fc8:
      return 8;

    case LTE_FilterCoefficient_fc9:
      return 9;

    case LTE_FilterCoefficient_fc11:
      return 11;

    case LTE_FilterCoefficient_fc13:
      return 13;

    case LTE_FilterCoefficient_fc15:
      return 15;

    case LTE_FilterCoefficient_fc17:
      return 17;

    case LTE_FilterCoefficient_fc19:
      return 19;

    case LTE_FilterCoefficient_spare1:
      return -1; /* spare means no coefficient */

    default:
      return -1;
  }
}

long flexran_get_filter_coeff_rsrq(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  switch (ue_context_p->ue_context.measurement_info->filterCoefficientRSRQ) {
    case LTE_FilterCoefficient_fc0:
      return 0;

    case LTE_FilterCoefficient_fc1:
      return 1;

    case LTE_FilterCoefficient_fc2:
      return 2;

    case LTE_FilterCoefficient_fc3:
      return 3;

    case LTE_FilterCoefficient_fc4:
      return 4;

    case LTE_FilterCoefficient_fc5:
      return 5;

    case LTE_FilterCoefficient_fc6:
      return 6;

    case LTE_FilterCoefficient_fc7:
      return 7;

    case LTE_FilterCoefficient_fc8:
      return 8;

    case LTE_FilterCoefficient_fc9:
      return 9;

    case LTE_FilterCoefficient_fc11:
      return 11;

    case LTE_FilterCoefficient_fc13:
      return 13;

    case LTE_FilterCoefficient_fc15:
      return 15;

    case LTE_FilterCoefficient_fc17:
      return 17;

    case LTE_FilterCoefficient_fc19:
      return 19;

    case LTE_FilterCoefficient_spare1:
      return -1; /* spare means no coefficient */

    default:
      return -1;
  }
}

/* Periodic event */

long flexran_get_rrc_per_event_maxReportCells(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->per_event) return -1;

  return ue_context_p->ue_context.measurement_info->events->per_event->maxReportCells;
}

/* A3 event */

long flexran_get_rrc_a3_event_hysteresis(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  return ue_context_p->ue_context.measurement_info->events->a3_event->hysteresis;
}

long flexran_get_rrc_a3_event_timeToTrigger(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  switch (ue_context_p->ue_context.measurement_info->events->a3_event->timeToTrigger) {
    case LTE_TimeToTrigger_ms0:
      return 0;

    case LTE_TimeToTrigger_ms40:
      return 40;

    case LTE_TimeToTrigger_ms64:
      return 64;

    case LTE_TimeToTrigger_ms80:
      return 80;

    case LTE_TimeToTrigger_ms100:
      return 100;

    case LTE_TimeToTrigger_ms128:
      return 128;

    case LTE_TimeToTrigger_ms160:
      return 160;

    case LTE_TimeToTrigger_ms256:
      return 256;

    case LTE_TimeToTrigger_ms320:
      return 320;

    case LTE_TimeToTrigger_ms480:
      return 480;

    case LTE_TimeToTrigger_ms512:
      return 512;

    case LTE_TimeToTrigger_ms640:
      return 640;

    case LTE_TimeToTrigger_ms1024:
      return 1024;

    case LTE_TimeToTrigger_ms1280:
      return 1280;

    case LTE_TimeToTrigger_ms2560:
      return 2560;

    case LTE_TimeToTrigger_ms5120:
      return 5120;

    default:
      return -1;
  }
}

long flexran_get_rrc_a3_event_maxReportCells(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  return ue_context_p->ue_context.measurement_info->events->a3_event->maxReportCells;
}

long flexran_get_rrc_a3_event_a3_offset(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  return ue_context_p->ue_context.measurement_info->events->a3_event->a3_offset;
}

int flexran_get_rrc_a3_event_reportOnLeave(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  return ue_context_p->ue_context.measurement_info->events->a3_event->reportOnLeave;
}

/* RRC Setters */

/* Measurement offsets */

int flexran_set_rrc_ofp(mid_t mod_id, rnti_t rnti, long offsetFreq) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!((offsetFreq >= -15) && (offsetFreq <= 15))) return -1;

  ue_context_p->ue_context.measurement_info->offsetFreq = offsetFreq;
  return 0;
}

int flexran_set_rrc_ofn(mid_t mod_id, rnti_t rnti, long offsetFreq) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!((offsetFreq >= -15) && (offsetFreq <= 15))) return -1;

  ue_context_p->ue_context.measurement_info->offsetFreq = offsetFreq;
  return 0;
}

int flexran_set_rrc_ocp(mid_t mod_id, rnti_t rnti, long cellIndividualOffset) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  LTE_Q_OffsetRange_t *cio = &ue_context_p->ue_context.measurement_info->cellIndividualOffset[0];

  switch (cellIndividualOffset) {
    case -24:
      *cio = LTE_Q_OffsetRange_dB_24;
      break;

    case -22:
      *cio = LTE_Q_OffsetRange_dB_22;
      break;

    case -20:
      *cio = LTE_Q_OffsetRange_dB_20;
      break;

    case -18:
      *cio = LTE_Q_OffsetRange_dB_18;
      break;

    case -16:
      *cio = LTE_Q_OffsetRange_dB_16;
      break;

    case -14:
      *cio = LTE_Q_OffsetRange_dB_14;
      break;

    case -12:
      *cio = LTE_Q_OffsetRange_dB_12;
      break;

    case -10:
      *cio = LTE_Q_OffsetRange_dB_10;
      break;

    case -8:
      *cio = LTE_Q_OffsetRange_dB_8;
      break;

    case -6:
      *cio = LTE_Q_OffsetRange_dB_6;
      break;

    case -5:
      *cio = LTE_Q_OffsetRange_dB_5;
      break;

    case -4:
      *cio = LTE_Q_OffsetRange_dB_4;
      break;

    case -3:
      *cio = LTE_Q_OffsetRange_dB_3;
      break;

    case -2:
      *cio = LTE_Q_OffsetRange_dB_2;
      break;

    case -1:
      *cio = LTE_Q_OffsetRange_dB_1;
      break;

    case 0:
      *cio = LTE_Q_OffsetRange_dB0;
      break;

    case 1:
      *cio = LTE_Q_OffsetRange_dB1;
      break;

    case 2:
      *cio = LTE_Q_OffsetRange_dB2;
      break;

    case 3:
      *cio = LTE_Q_OffsetRange_dB3;
      break;

    case 4:
      *cio = LTE_Q_OffsetRange_dB4;
      break;

    case 5:
      *cio = LTE_Q_OffsetRange_dB5;
      break;

    case 6:
      *cio = LTE_Q_OffsetRange_dB6;
      break;

    case 8:
      *cio = LTE_Q_OffsetRange_dB8;
      break;

    case 10:
      *cio = LTE_Q_OffsetRange_dB10;
      break;

    case 12:
      *cio = LTE_Q_OffsetRange_dB12;
      break;

    case 14:
      *cio = LTE_Q_OffsetRange_dB14;
      break;

    case 16:
      *cio = LTE_Q_OffsetRange_dB16;
      break;

    case 18:
      *cio = LTE_Q_OffsetRange_dB18;
      break;

    case 20:
      *cio = LTE_Q_OffsetRange_dB20;
      break;

    case 22:
      *cio = LTE_Q_OffsetRange_dB22;
      break;

    case 24:
      *cio = LTE_Q_OffsetRange_dB24;
      break;

    default:
      return -1;
  }

  return 0;
}

int flexran_set_rrc_ocn(mid_t mod_id, rnti_t rnti, long cell_id, long cellIndividualOffset) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  LTE_Q_OffsetRange_t *cio = &ue_context_p->ue_context.measurement_info->cellIndividualOffset[cell_id+1];

  switch (cellIndividualOffset) {
    case -24:
      *cio = LTE_Q_OffsetRange_dB_24;
      break;

    case -22:
      *cio = LTE_Q_OffsetRange_dB_22;
      break;

    case -20:
      *cio = LTE_Q_OffsetRange_dB_20;
      break;

    case -18:
      *cio = LTE_Q_OffsetRange_dB_18;
      break;

    case -16:
      *cio = LTE_Q_OffsetRange_dB_16;
      break;

    case -14:
      *cio = LTE_Q_OffsetRange_dB_14;
      break;

    case -12:
      *cio = LTE_Q_OffsetRange_dB_12;
      break;

    case -10:
      *cio = LTE_Q_OffsetRange_dB_10;
      break;

    case -8:
      *cio = LTE_Q_OffsetRange_dB_8;
      break;

    case -6:
      *cio = LTE_Q_OffsetRange_dB_6;
      break;

    case -5:
      *cio = LTE_Q_OffsetRange_dB_5;
      break;

    case -4:
      *cio = LTE_Q_OffsetRange_dB_4;
      break;

    case -3:
      *cio = LTE_Q_OffsetRange_dB_3;
      break;

    case -2:
      *cio = LTE_Q_OffsetRange_dB_2;
      break;

    case -1:
      *cio = LTE_Q_OffsetRange_dB_1;
      break;

    case 0:
      *cio = LTE_Q_OffsetRange_dB0;
      break;

    case 1:
      *cio = LTE_Q_OffsetRange_dB1;
      break;

    case 2:
      *cio = LTE_Q_OffsetRange_dB2;
      break;

    case 3:
      *cio = LTE_Q_OffsetRange_dB3;
      break;

    case 4:
      *cio = LTE_Q_OffsetRange_dB4;
      break;

    case 5:
      *cio = LTE_Q_OffsetRange_dB5;
      break;

    case 6:
      *cio = LTE_Q_OffsetRange_dB6;
      break;

    case 8:
      *cio = LTE_Q_OffsetRange_dB8;
      break;

    case 10:
      *cio = LTE_Q_OffsetRange_dB10;
      break;

    case 12:
      *cio = LTE_Q_OffsetRange_dB12;
      break;

    case 14:
      *cio = LTE_Q_OffsetRange_dB14;
      break;

    case 16:
      *cio = LTE_Q_OffsetRange_dB16;
      break;

    case 18:
      *cio = LTE_Q_OffsetRange_dB18;
      break;

    case 20:
      *cio = LTE_Q_OffsetRange_dB20;
      break;

    case 22:
      *cio = LTE_Q_OffsetRange_dB22;
      break;

    case 24:
      *cio = LTE_Q_OffsetRange_dB24;
      break;

    default:
      return -1;
  }

  return 0;
}

int flexran_set_filter_coeff_rsrp(mid_t mod_id, rnti_t rnti, long filterCoefficientRSRP) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  LTE_FilterCoefficient_t *fc = &ue_context_p->ue_context.measurement_info->filterCoefficientRSRP;

  switch (filterCoefficientRSRP) {
    case 0:
      *fc = LTE_FilterCoefficient_fc0;
      break;

    case 1:
      *fc = LTE_FilterCoefficient_fc1;
      break;

    case 2:
      *fc = LTE_FilterCoefficient_fc2;
      break;

    case 3:
      *fc = LTE_FilterCoefficient_fc3;
      break;

    case 4:
      *fc = LTE_FilterCoefficient_fc4;
      break;

    case 5:
      *fc = LTE_FilterCoefficient_fc5;
      break;

    case 6:
      *fc = LTE_FilterCoefficient_fc6;
      break;

    case 7:
      *fc = LTE_FilterCoefficient_fc7;
      break;

    case 8:
      *fc = LTE_FilterCoefficient_fc8;
      break;

    case 9:
      *fc = LTE_FilterCoefficient_fc9;
      break;

    case 11:
      *fc = LTE_FilterCoefficient_fc11;
      break;

    case 13:
      *fc = LTE_FilterCoefficient_fc13;
      break;

    case 15:
      *fc = LTE_FilterCoefficient_fc15;
      break;

    case 17:
      *fc = LTE_FilterCoefficient_fc17;
      break;

    case 19:
      *fc = LTE_FilterCoefficient_fc19;
      break;

    case -1:
      *fc = LTE_FilterCoefficient_spare1;
      break;

    default:
      return -1;
  }

  return 0;
}

int flexran_set_filter_coeff_rsrq(mid_t mod_id, rnti_t rnti, long filterCoefficientRSRQ) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  LTE_FilterCoefficient_t *fc = &ue_context_p->ue_context.measurement_info->filterCoefficientRSRQ;

  switch (filterCoefficientRSRQ) {
    case 0:
      *fc = LTE_FilterCoefficient_fc0;
      break;

    case 1:
      *fc = LTE_FilterCoefficient_fc1;
      break;

    case 2:
      *fc = LTE_FilterCoefficient_fc2;
      break;

    case 3:
      *fc = LTE_FilterCoefficient_fc3;
      break;

    case 4:
      *fc = LTE_FilterCoefficient_fc4;
      break;

    case 5:
      *fc = LTE_FilterCoefficient_fc5;
      break;

    case 6:
      *fc = LTE_FilterCoefficient_fc6;
      break;

    case 7:
      *fc = LTE_FilterCoefficient_fc7;
      break;

    case 8:
      *fc = LTE_FilterCoefficient_fc8;
      break;

    case 9:
      *fc = LTE_FilterCoefficient_fc9;
      break;

    case 11:
      *fc = LTE_FilterCoefficient_fc11;
      break;

    case 13:
      *fc = LTE_FilterCoefficient_fc13;
      break;

    case 15:
      *fc = LTE_FilterCoefficient_fc15;
      break;

    case 17:
      *fc = LTE_FilterCoefficient_fc17;
      break;

    case 19:
      *fc = LTE_FilterCoefficient_fc19;
      break;

    case -1:
      *fc = LTE_FilterCoefficient_spare1;
      break;

    default:
      return -1;
  }

  return 0;
}

/* Periodic event */

int flexran_set_rrc_per_event_maxReportCells(mid_t mod_id, rnti_t rnti, long maxReportCells) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->per_event) return -1;

  if (!((maxReportCells >= 1) && (maxReportCells <= 8))) return -1;

  ue_context_p->ue_context.measurement_info->events->per_event->maxReportCells = maxReportCells;
  return 0;
}

/* A3 event */

int flexran_set_rrc_a3_event_hysteresis(mid_t mod_id, rnti_t rnti, long hysteresis) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  if (!((hysteresis >=0) && (hysteresis <= 30))) return -1;

  ue_context_p->ue_context.measurement_info->events->a3_event->hysteresis = hysteresis;
  return 0;
}

int flexran_set_rrc_a3_event_timeToTrigger(mid_t mod_id, rnti_t rnti, long timeToTrigger) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  LTE_TimeToTrigger_t *ttt = &ue_context_p->ue_context.measurement_info->events->a3_event->timeToTrigger;

  switch (timeToTrigger) {
    case 0:
      *ttt = LTE_TimeToTrigger_ms0;
      break;

    case 40:
      *ttt = LTE_TimeToTrigger_ms40;
      break;

    case 64:
      *ttt = LTE_TimeToTrigger_ms64;
      break;

    case 80:
      *ttt = LTE_TimeToTrigger_ms80;
      break;

    case 100:
      *ttt = LTE_TimeToTrigger_ms100;
      break;

    case 128:
      *ttt = LTE_TimeToTrigger_ms128;
      break;

    case 160:
      *ttt = LTE_TimeToTrigger_ms160;
      break;

    case 256:
      *ttt = LTE_TimeToTrigger_ms256;
      break;

    case 320:
      *ttt = LTE_TimeToTrigger_ms320;
      break;

    case 480:
      *ttt = LTE_TimeToTrigger_ms480;
      break;

    case 512:
      *ttt = LTE_TimeToTrigger_ms512;
      break;

    case 640:
      *ttt = LTE_TimeToTrigger_ms640;
      break;

    case 1024:
      *ttt = LTE_TimeToTrigger_ms1024;
      break;

    case 1280:
      *ttt = LTE_TimeToTrigger_ms1280;
      break;

    case 2560:
      *ttt = LTE_TimeToTrigger_ms2560;
      break;

    case 5120:
      *ttt = LTE_TimeToTrigger_ms5120;
      break;

    default:
      return -1;
  }

  return 0;
}

int flexran_set_rrc_a3_event_maxReportCells(mid_t mod_id, rnti_t rnti, long maxReportCells) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  if (!((maxReportCells >= 1) && (maxReportCells <= 8))) return -1;

  ue_context_p->ue_context.measurement_info->events->a3_event->maxReportCells = maxReportCells;
  return 0;
}

int flexran_set_rrc_a3_event_a3_offset(mid_t mod_id, rnti_t rnti, long a3_offset) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  if (!((a3_offset >= -30) && (a3_offset <= 30))) return -1;

  ue_context_p->ue_context.measurement_info->events->a3_event->a3_offset = a3_offset;
  return 0;
}

int flexran_set_rrc_a3_event_reportOnLeave(mid_t mod_id, rnti_t rnti, int reportOnLeave) {
  if (!rrc_is_present(mod_id)) return -1;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return -1;

  if (!ue_context_p->ue_context.measurement_info) return -1;

  if (!ue_context_p->ue_context.measurement_info->events) return -1;

  if (!ue_context_p->ue_context.measurement_info->events->a3_event) return -1;

  if (!((reportOnLeave == 0) || (reportOnLeave == 1))) return -1;

  ue_context_p->ue_context.measurement_info->events->a3_event->reportOnLeave = reportOnLeave;
  return 0;
}

int flexran_set_x2_ho_net_control(mid_t mod_id, int x2_ho_net_control) {
  if (!rrc_is_present(mod_id)) return -1;

  if (!((x2_ho_net_control == 0) || (x2_ho_net_control == 1))) return -1;

  RC.rrc[mod_id]->x2_ho_net_control = x2_ho_net_control;
  return 0;
}

int flexran_get_x2_ho_net_control(mid_t mod_id) {
  if (!rrc_is_present(mod_id)) return -1;

  return RC.rrc[mod_id]->x2_ho_net_control;
}

uint8_t flexran_get_rrc_num_plmn_ids(mid_t mod_id) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->configuration.num_plmn;
}

uint16_t flexran_get_rrc_mcc(mid_t mod_id, uint8_t index) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->configuration.mcc[index];
}

uint16_t flexran_get_rrc_mnc(mid_t mod_id, uint8_t index) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->configuration.mnc[index];
}

uint8_t flexran_get_rrc_mnc_digit_length(mid_t mod_id, uint8_t index) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->configuration.mnc_digit_length[index];
}

int flexran_get_rrc_num_adj_cells(mid_t mod_id) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->num_neigh_cells;
}

int flexran_agent_rrc_gtp_num_e_rab(mid_t mod_id, rnti_t rnti) {
  if (!rrc_is_present(mod_id)) return 0;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return 0;

  return ue_context_p->ue_context.setup_e_rabs;
}

int flexran_agent_rrc_gtp_get_e_rab_id(mid_t mod_id, rnti_t rnti, int index) {
  if (!rrc_is_present(mod_id)) return 0;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return 0;

  return ue_context_p->ue_context.e_rab[index].param.e_rab_id;
}

int flexran_agent_rrc_gtp_get_teid_enb(mid_t mod_id, rnti_t rnti, int index) {
  if (!rrc_is_present(mod_id)) return 0;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return 0;

  return ue_context_p->ue_context.enb_gtp_teid[index];
}

int flexran_agent_rrc_gtp_get_teid_sgw(mid_t mod_id, rnti_t rnti, int index) {
  if (!rrc_is_present(mod_id)) return 0;

  struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);

  if (!ue_context_p) return 0;

  return ue_context_p->ue_context.e_rab[index].param.gtp_teid;
}

uint32_t flexran_get_rrc_enb_ue_s1ap_id(mid_t mod_id, rnti_t rnti)
{
  if (!rrc_is_present(mod_id)) return 0;
  struct rrc_eNB_ue_context_s* ue_context_p = rrc_eNB_get_ue_context(RC.rrc[mod_id], rnti);
  if (!ue_context_p) return -1;
  return ue_context_p->ue_context.eNB_ue_s1ap_id;
}

/**************************** SLICING ****************************/
Protocol__FlexSliceAlgorithm flexran_get_dl_slice_algo(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return PROTOCOL__FLEX_SLICE_ALGORITHM__None;

  switch (RC.mac[mod_id]->pre_processor_dl.algorithm) {
    case STATIC_SLICING:
      return PROTOCOL__FLEX_SLICE_ALGORITHM__Static;
    default:
      return PROTOCOL__FLEX_SLICE_ALGORITHM__None;
  }
}

int flexran_set_dl_slice_algo(mid_t mod_id, Protocol__FlexSliceAlgorithm algo) {
  if (!mac_is_present(mod_id)) return 0;
  eNB_MAC_INST *mac = RC.mac[mod_id];
  const int cc_id = 0;

  pp_impl_param_t dl = mac->pre_processor_dl;
  switch (algo) {
    case PROTOCOL__FLEX_SLICE_ALGORITHM__Static:
      mac->pre_processor_dl = static_dl_init(mod_id, cc_id);
      break;
    default:
      mac->pre_processor_dl.algorithm = 0;
      mac->pre_processor_dl.dl = dlsch_scheduler_pre_processor;
      mac->pre_processor_dl.dl_algo.data = mac->pre_processor_dl.dl_algo.setup();
      mac->pre_processor_dl.slices = NULL;
      break;
  }
  if (dl.slices)
    dl.destroy(&dl.slices);
  if (dl.dl_algo.data)
    dl.dl_algo.unset(&dl.dl_algo.data);
  return 1;
}

Protocol__FlexSliceAlgorithm flexran_get_ul_slice_algo(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return PROTOCOL__FLEX_SLICE_ALGORITHM__None;

  switch (RC.mac[mod_id]->pre_processor_ul.algorithm) {
    case STATIC_SLICING:
      return PROTOCOL__FLEX_SLICE_ALGORITHM__Static;
    default:
      return PROTOCOL__FLEX_SLICE_ALGORITHM__None;
  }
}

int flexran_set_ul_slice_algo(mid_t mod_id, Protocol__FlexSliceAlgorithm algo) {
  if (!mac_is_present(mod_id)) return 0;
  eNB_MAC_INST *mac = RC.mac[mod_id];
  const int cc_id = 0;

  pp_impl_param_t ul = mac->pre_processor_ul;
  switch (algo) {
    case PROTOCOL__FLEX_SLICE_ALGORITHM__Static:
      mac->pre_processor_ul = static_ul_init(mod_id, cc_id);
      break;
    default:
      mac->pre_processor_ul.algorithm = 0;
      mac->pre_processor_ul.ul = ulsch_scheduler_pre_processor;
      mac->pre_processor_ul.ul_algo.data = mac->pre_processor_ul.ul_algo.setup();
      mac->pre_processor_ul.slices = NULL;
      break;
  }
  if (ul.slices)
    ul.destroy(&ul.slices);
  if (ul.ul_algo.data)
    ul.ul_algo.unset(&ul.ul_algo.data);
  return 1;
}

int flexran_get_ue_dl_slice_id(mid_t mod_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return -1;
  slice_info_t *slices = RC.mac[mod_id]->pre_processor_dl.slices;
  if (!slices) return -1;
  const int idx = slices->UE_assoc_slice[ue_id];
  return slices->s[idx]->id;
}

int flexran_set_ue_dl_slice_id(mid_t mod_id, mid_t ue_id, slice_id_t slice_id) {
  if (!mac_is_present(mod_id)) return 0;
  int idx = flexran_find_dl_slice(mod_id, slice_id);
  if (idx < 0) return 0;
  pp_impl_param_t *dl = &RC.mac[mod_id]->pre_processor_dl;
  dl->move_UE(dl->slices, ue_id, idx);
  return 1;
}

int flexran_get_ue_ul_slice_id(mid_t mod_id, mid_t ue_id) {
  if (!mac_is_present(mod_id)) return -1;
  slice_info_t *slices = RC.mac[mod_id]->pre_processor_ul.slices;
  if (!slices) return -1;
  const int idx = slices->UE_assoc_slice[ue_id];
  return slices->s[idx]->id;
}

int flexran_set_ue_ul_slice_id(mid_t mod_id, mid_t ue_id, slice_id_t slice_id) {
  if (!mac_is_present(mod_id)) return 0;
  int idx = flexran_find_ul_slice(mod_id, slice_id);
  if (idx < 0) return 0;
  pp_impl_param_t *ul = &RC.mac[mod_id]->pre_processor_ul;
  ul->move_UE(ul->slices, ue_id, idx);
  return 1;
}

int flexran_create_dl_slice(mid_t mod_id, const Protocol__FlexSlice *s, void *object) {
  if (!mac_is_present(mod_id)) return 0;
  void *params = NULL;
  switch (s->params_case) {
    case PROTOCOL__FLEX_SLICE__PARAMS_STATIC:
      params = malloc(sizeof(static_slice_param_t));
      if (!params) return 0;
      ((static_slice_param_t *)params)->posLow = s->static_->poslow;
      ((static_slice_param_t *)params)->posHigh = s->static_->poshigh;
      break;
    default:
      break;
  }
  pp_impl_param_t *dl = &RC.mac[mod_id]->pre_processor_dl;
  char *l = s->label ? strdup(s->label) : NULL;
  void *algo = &dl->dl_algo; // default scheduler
  if (s->scheduler) {
    algo = dlsym(object, s->scheduler);
    if (!algo) {
      free(params);
      LOG_E(FLEXRAN_AGENT, "cannot locate scheduler '%s'\n", s->scheduler);
      return -15;
    }
  }
  return dl->addmod_slice(dl->slices, s->id, l, algo, params);
}

int flexran_remove_dl_slice(mid_t mod_id, const Protocol__FlexSlice *s) {
  if (!mac_is_present(mod_id)) return 0;
  const int idx = flexran_find_dl_slice(mod_id, s->id);
  if (idx < 0) return 0;
  pp_impl_param_t *dl = &RC.mac[mod_id]->pre_processor_dl;
  return dl->remove_slice(dl->slices, idx);
}

int flexran_find_dl_slice(mid_t mod_id, slice_id_t slice_id) {
  if (!mac_is_present(mod_id)) return -1;
  slice_info_t *si = RC.mac[mod_id]->pre_processor_dl.slices;
  for (int i = 0; i < si->num; ++i)
    if (si->s[i]->id == slice_id)
      return i;
  return -1;
}

void flexran_get_dl_slice(mid_t mod_id,
                          int slice_idx,
                          Protocol__FlexSlice *slice,
                          Protocol__FlexSliceAlgorithm algo) {
  if (!mac_is_present(mod_id)) return;
  slice_t *s_ = RC.mac[mod_id]->pre_processor_dl.slices->s[slice_idx];
  slice->has_id = 1;
  slice->id = s_->id;
  slice->label = s_->label;
  slice->scheduler = s_->dl_algo.name;
  slice->params_case = PROTOCOL__FLEX_SLICE__PARAMS__NOT_SET;
  switch (algo) {
    case PROTOCOL__FLEX_SLICE_ALGORITHM__Static:
      slice->static_ = malloc(sizeof(Protocol__FlexSliceStatic));
      if (!slice->static_) return;
      protocol__flex_slice_static__init(slice->static_);
      slice->static_->has_poslow = 1;
      slice->static_->poslow = ((static_slice_param_t *)s_->algo_data)->posLow;
      slice->static_->has_poshigh = 1;
      slice->static_->poshigh = ((static_slice_param_t *)s_->algo_data)->posHigh;
      slice->params_case = PROTOCOL__FLEX_SLICE__PARAMS_STATIC;
      break;
    default:
      break;
  }
}

int flexran_get_num_dl_slices(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return 0;
  if (!RC.mac[mod_id]->pre_processor_dl.slices) return 0;
  return RC.mac[mod_id]->pre_processor_dl.slices->num;
}

int flexran_create_ul_slice(mid_t mod_id, const Protocol__FlexSlice *s, void *object) {
  if (!mac_is_present(mod_id)) return -1;
  void *params = NULL;
  switch (s->params_case) {
    case PROTOCOL__FLEX_SLICE__PARAMS_STATIC:
      params = malloc(sizeof(static_slice_param_t));
      if (!params) return 0;
      ((static_slice_param_t *)params)->posLow = s->static_->poslow;
      ((static_slice_param_t *)params)->posHigh = s->static_->poshigh;
      break;
    default:
      break;
  }
  pp_impl_param_t *ul = &RC.mac[mod_id]->pre_processor_ul;
  char *l = s->label ? strdup(s->label) : NULL;
  void *algo = &ul->ul_algo; // default scheduler
  if (s->scheduler) {
    algo = dlsym(object, s->scheduler);
    if (!algo) {
      free(params);
      LOG_E(FLEXRAN_AGENT, "cannot locate scheduler '%s'\n", s->scheduler);
      return -15;
    }
  }
  return ul->addmod_slice(ul->slices, s->id, l, algo, params);
}

int flexran_remove_ul_slice(mid_t mod_id, const Protocol__FlexSlice *s) {
  if (!mac_is_present(mod_id)) return 0;
  const int idx = flexran_find_ul_slice(mod_id, s->id);
  if (idx < 0) return 0;
  pp_impl_param_t *ul = &RC.mac[mod_id]->pre_processor_ul;
  return ul->remove_slice(ul->slices, idx);
}

int flexran_find_ul_slice(mid_t mod_id, slice_id_t slice_id) {
  if (!mac_is_present(mod_id)) return -1;
  slice_info_t *si = RC.mac[mod_id]->pre_processor_ul.slices;
  for (int i = 0; i < si->num; ++i)
    if (si->s[i]->id == slice_id)
      return i;
  return -1;
}

void flexran_get_ul_slice(mid_t mod_id,
                          int slice_idx,
                          Protocol__FlexSlice *slice,
                          Protocol__FlexSliceAlgorithm algo) {
  if (!mac_is_present(mod_id)) return;
  slice_t *s_ = RC.mac[mod_id]->pre_processor_ul.slices->s[slice_idx];
  slice->has_id = 1;
  slice->id = s_->id;
  slice->label = s_->label;
  slice->scheduler = s_->ul_algo.name;
  slice->params_case = PROTOCOL__FLEX_SLICE__PARAMS__NOT_SET;
  switch (algo) {
    case PROTOCOL__FLEX_SLICE_ALGORITHM__Static:
      slice->static_ = malloc(sizeof(Protocol__FlexSliceStatic));
      if (!slice->static_) return;
      protocol__flex_slice_static__init(slice->static_);
      slice->static_->has_poslow = 1;
      slice->static_->poslow = ((static_slice_param_t *)s_->algo_data)->posLow;
      slice->static_->has_poshigh = 1;
      slice->static_->poshigh = ((static_slice_param_t *)s_->algo_data)->posHigh;
      slice->params_case = PROTOCOL__FLEX_SLICE__PARAMS_STATIC;
      break;
    default:
      break;
  }

}

int flexran_get_num_ul_slices(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return 0;
  if (!RC.mac[mod_id]->pre_processor_ul.slices) return 0;
  return RC.mac[mod_id]->pre_processor_ul.slices->num;
}

char *flexran_get_dl_scheduler_name(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return NULL;
  return RC.mac[mod_id]->pre_processor_dl.dl_algo.name;
}

int flexran_set_dl_scheduler(mid_t mod_id, char *sched, void *object) {
  if (!mac_is_present(mod_id)) return -1;
  void *d = dlsym(object, sched);
  if (!d) return -2;
  pp_impl_param_t *dl_pp = &RC.mac[mod_id]->pre_processor_dl;
  dl_pp->dl_algo.unset(&dl_pp->dl_algo.data);
  dl_pp->dl_algo = *(default_sched_dl_algo_t *) d;
  dl_pp->dl_algo.data = dl_pp->dl_algo.setup();
  return 0;
}

char *flexran_get_ul_scheduler_name(mid_t mod_id) {
  if (!mac_is_present(mod_id)) return NULL;
  return RC.mac[mod_id]->pre_processor_ul.ul_algo.name;
}

int flexran_set_ul_scheduler(mid_t mod_id, char *sched, void *object) {
  if (!mac_is_present(mod_id)) return -1;
  void *d = dlsym(object, sched);
  if (!d) return -2;
  pp_impl_param_t *ul_pp = &RC.mac[mod_id]->pre_processor_ul;
  ul_pp->ul_algo.unset(&ul_pp->ul_algo.data);
  ul_pp->ul_algo = *(default_sched_ul_algo_t *) d;
  ul_pp->ul_algo.data = ul_pp->ul_algo.setup();
  return 0;
}

/************************** S1AP **************************/
int flexran_get_s1ap_mme_pending(mid_t mod_id){
  if (!rrc_is_present(mod_id)) return -1;
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return -1;
  return s1ap->s1ap_mme_pending_nb;
}

int flexran_get_s1ap_mme_connected(mid_t mod_id){
  if (!rrc_is_present(mod_id)) return -1;
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return -1;
  return s1ap->s1ap_mme_associated_nb;
}

char* flexran_get_s1ap_enb_s1_ip(mid_t mod_id){
  if (!rrc_is_present(mod_id)) return NULL;
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return NULL;
  if (s1ap->eNB_s1_ip.ipv4)
    return &s1ap->eNB_s1_ip.ipv4_address[0];
  if (s1ap->eNB_s1_ip.ipv6)
    return &s1ap->eNB_s1_ip.ipv6_address[0];
  return NULL;
}

char* flexran_get_s1ap_enb_name(mid_t mod_id){
  if (!rrc_is_present(mod_id)) return NULL;
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return NULL;
  return s1ap->eNB_name;
}

int flexran_get_s1ap_nb_mme(mid_t mod_id) {
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return 0;
  struct s1ap_eNB_mme_data_s *mme = NULL;
  int count = 0;
  RB_FOREACH(mme, s1ap_mme_map, &s1ap->s1ap_mme_head) {
    count++;
  }
  return count;
}

int flexran_get_s1ap_nb_ue(mid_t mod_id) {
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return 0;
  struct s1ap_eNB_ue_context_s *ue = NULL;
  int count = 0;
  RB_FOREACH(ue, s1ap_ue_map, &s1ap->s1ap_ue_head) {
    count++;
  }
  return count;
}

int flexran_get_s1ap_mme_conf(mid_t mod_id, mid_t mme_index, Protocol__FlexS1apMme * mme_conf){
  if (!rrc_is_present(mod_id)) return -1;
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return -1;

  struct served_gummei_s   *gummei_p = NULL;
  struct plmn_identity_s   *served_plmn_p = NULL;
  struct served_group_id_s *group_id_p = NULL;
  struct mme_code_s        *mme_code_p = NULL;
  int i = 0;
  Protocol__FlexGummei  **served_gummeis;
  Protocol__FlexPlmn    **requested_plmns;

  struct s1ap_eNB_mme_data_s *mme = NULL;

  RB_FOREACH(mme, s1ap_mme_map, &s1ap->s1ap_mme_head){
    if (mme_index == 0) break;
    mme_index--;
  }
  if (mme_index > 0) return -1;

  if (mme->mme_s1_ip.ipv4) {
    mme_conf->s1_ip = (char*) &mme->mme_s1_ip.ipv4_address[0];
  } else if (mme->mme_s1_ip.ipv6) {
    mme_conf->s1_ip = (char*) &mme->mme_s1_ip.ipv6_address[0];
  }
  mme_conf->name = mme->mme_name;
  mme_conf->has_state = 1;
  mme_conf->state = mme->state;

  mme_conf->n_served_gummeis = 0;
  STAILQ_FOREACH(gummei_p, &mme->served_gummei, next) {
    mme_conf->n_served_gummeis++;
  }
  if (mme_conf->n_served_gummeis > 0) {
    served_gummeis = calloc(mme_conf->n_served_gummeis, sizeof(Protocol__FlexGummei*));
    if(served_gummeis == NULL) return -1;

    STAILQ_FOREACH(gummei_p, &mme->served_gummei, next) {
      served_plmn_p = STAILQ_FIRST(&gummei_p->served_plmns);
      group_id_p = STAILQ_FIRST(&gummei_p->served_group_ids);
      mme_code_p = STAILQ_FIRST(&gummei_p->mme_codes);

      served_gummeis[i] = malloc(sizeof(Protocol__FlexGummei));
      if (!served_gummeis[i]) return -1;
      protocol__flex_gummei__init(served_gummeis[i]);
      served_gummeis[i]->plmn = malloc(sizeof(Protocol__FlexPlmn));
      if (!served_gummeis[i]->plmn) return -1;
      protocol__flex_plmn__init(served_gummeis[i]->plmn);

      if (served_plmn_p) {
        served_gummeis[i]->plmn->has_mcc = 1;
        served_gummeis[i]->plmn->mcc = served_plmn_p->mcc;
        served_gummeis[i]->plmn->has_mnc = 1;
        served_gummeis[i]->plmn->mnc = served_plmn_p->mnc;
        served_gummeis[i]->plmn->has_mnc_length = 1;
        served_gummeis[i]->plmn->mnc_length = served_plmn_p-> mnc_digit_length;
        STAILQ_NEXT(served_plmn_p, next);
      }
      if (group_id_p) {
        served_gummeis[i]->has_mme_group_id = 1;
        served_gummeis[i]->mme_group_id = group_id_p->mme_group_id;
        STAILQ_NEXT(group_id_p, next);
      }
      if (mme_code_p){
        served_gummeis[i]->has_mme_code = 1;
        served_gummeis[i]->mme_code = mme_code_p->mme_code;
        STAILQ_NEXT(mme_code_p, next);
      }
      i++;
    }

    mme_conf->served_gummeis = served_gummeis;
  }

  // requested PLMNS
  mme_conf->n_requested_plmns = mme->broadcast_plmn_num;
  if (mme_conf->n_requested_plmns > 0){
    requested_plmns = calloc(mme_conf->n_requested_plmns, sizeof(Protocol__FlexPlmn*));
    if(requested_plmns == NULL) return -1;
    for(int i = 0; i < mme_conf->n_requested_plmns; i++) {
      requested_plmns[i] = malloc(sizeof(Protocol__FlexPlmn));
      if (!requested_plmns[i]) return -1;
      protocol__flex_plmn__init(requested_plmns[i]);
      requested_plmns[i]->mcc = s1ap->mcc[mme->broadcast_plmn_index[i]];
      requested_plmns[i]->has_mcc = 1;
      requested_plmns[i]->mnc = s1ap->mnc[mme->broadcast_plmn_index[i]];
      requested_plmns[i]->has_mnc = 1;
      requested_plmns[i]->mnc_length = s1ap->mnc_digit_length[mme->broadcast_plmn_index[i]];
      requested_plmns[i]->has_mnc_length = 1;
    }
    mme_conf->requested_plmns = requested_plmns;
  }

  mme_conf->has_rel_capacity = 1;
  mme_conf->rel_capacity = mme->relative_mme_capacity;
  return 0;
}

int flexran_add_s1ap_mme(mid_t mod_id, size_t n_mme, char **mme_ipv4) {
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return -1;
  if (!rrc_is_present(mod_id)) return -2;

  /* Reconstruct S1AP_REGISTER_ENB_REQ */
  MessageDef *m = itti_alloc_new_message(TASK_FLEXRAN_AGENT, 0, S1AP_REGISTER_ENB_REQ);
  RCconfig_S1(m, mod_id);

  const int CC_id = 0;
  eNB_RRC_INST *rrc = RC.rrc[CC_id];
  RrcConfigurationReq *conf = &rrc->configuration;
  S1AP_REGISTER_ENB_REQ(m).num_plmn = conf->num_plmn;
  for (int i = 0; i < conf->num_plmn; ++i) {
    S1AP_REGISTER_ENB_REQ(m).mcc[i] = conf->mcc[i];
    S1AP_REGISTER_ENB_REQ(m).mnc[i] = conf->mnc[i];
    S1AP_REGISTER_ENB_REQ(m).mnc_digit_length[i] = conf->mnc_digit_length[i];
  }

  /* reconstruct MME list, it might have been updated since initial
   * configuration */
  S1AP_REGISTER_ENB_REQ(m).nb_mme = 0;
  struct s1ap_eNB_mme_data_s *mme = NULL;
  RB_FOREACH(mme, s1ap_mme_map, &s1ap->s1ap_mme_head) {
    const int n = S1AP_REGISTER_ENB_REQ(m).nb_mme;
    S1AP_REGISTER_ENB_REQ(m).mme_ip_address[n].ipv4 = mme->mme_s1_ip.ipv4;
    strcpy(S1AP_REGISTER_ENB_REQ(m).mme_ip_address[n].ipv4_address, mme->mme_s1_ip.ipv4_address);
    S1AP_REGISTER_ENB_REQ(m).mme_ip_address[n].ipv6 = mme->mme_s1_ip.ipv6;
    strcpy(S1AP_REGISTER_ENB_REQ(m).mme_ip_address[n].ipv6_address, mme->mme_s1_ip.ipv6_address);
    S1AP_REGISTER_ENB_REQ(m).broadcast_plmn_num[n] = mme->broadcast_plmn_num;
    for (int i = 0; i < mme->broadcast_plmn_num; ++i)
      S1AP_REGISTER_ENB_REQ(m).broadcast_plmn_index[n][i] = mme->broadcast_plmn_index[i];
    S1AP_REGISTER_ENB_REQ(m).mme_port[n] = mme->mme_port;
    S1AP_REGISTER_ENB_REQ(m).nb_mme += 1;
  }

  if (S1AP_REGISTER_ENB_REQ(m).nb_mme + n_mme > S1AP_MAX_NB_MME_IP_ADDRESS)
    return -1;

  const int n = S1AP_REGISTER_ENB_REQ(m).nb_mme;
  strcpy(S1AP_REGISTER_ENB_REQ(m).mme_ip_address[n].ipv4_address, mme_ipv4[0]);
  S1AP_REGISTER_ENB_REQ(m).mme_ip_address[n].ipv4 = 1;
  S1AP_REGISTER_ENB_REQ(m).mme_ip_address[n].ipv6 = 0;
  S1AP_REGISTER_ENB_REQ(m).broadcast_plmn_num[n] = S1AP_REGISTER_ENB_REQ(m).num_plmn;
  for (int i = 0; i < S1AP_REGISTER_ENB_REQ(m).num_plmn; ++i)
    S1AP_REGISTER_ENB_REQ(m).broadcast_plmn_index[n][i] = i;
  S1AP_REGISTER_ENB_REQ(m).mme_port[n] = S1AP_PORT_NUMBER;
  S1AP_REGISTER_ENB_REQ(m).nb_mme += 1;

  itti_send_msg_to_task (TASK_S1AP, ENB_MODULE_ID_TO_INSTANCE(mod_id), m);

  return 0;
}

int flexran_remove_s1ap_mme(mid_t mod_id, size_t n_mme, char **mme_ipv4) {
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return -1;
  struct s1ap_eNB_mme_data_s *mme = NULL;
  RB_FOREACH(mme, s1ap_mme_map, &s1ap->s1ap_mme_head) {
    if (mme->mme_s1_ip.ipv4
        && strncmp(mme->mme_s1_ip.ipv4_address, mme_ipv4[0], 16) == 0)
        break;
  }
  if (!mme)
    return -2;

  MessageDef *m = itti_alloc_new_message(TASK_FLEXRAN_AGENT, 0, SCTP_CLOSE_ASSOCIATION);
  SCTP_CLOSE_ASSOCIATION(m).assoc_id = mme->assoc_id;
  itti_send_msg_to_task (TASK_SCTP, ENB_MODULE_ID_TO_INSTANCE(mod_id), m);

  switch (mme->state) {
    case S1AP_ENB_STATE_WAITING:
      s1ap->s1ap_mme_nb -= 1;
      if (s1ap->s1ap_mme_pending_nb > 0)
        s1ap->s1ap_mme_pending_nb -= 1;
      break;
    case S1AP_ENB_STATE_CONNECTED:
    case S1AP_ENB_OVERLOAD: /* I am not sure the decrements are right here */
      s1ap->s1ap_mme_nb -= 1;
      s1ap->s1ap_mme_associated_nb -= 1;
      break;
    case S1AP_ENB_STATE_DISCONNECTED:
    default:
      break;
  }
  RB_REMOVE(s1ap_mme_map, &s1ap->s1ap_mme_head, mme);

  return 0;
}

int flexran_set_new_plmn_id(mid_t mod_id, int CC_id, size_t n_plmn, Protocol__FlexPlmn **plmn_id) {
  if (!rrc_is_present(mod_id))
    return -1;
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap)
    return -2;

  eNB_RRC_INST *rrc = RC.rrc[CC_id];
  RrcConfigurationReq *conf = &rrc->configuration;

  uint8_t num_plmn_old = conf->num_plmn;
  uint16_t mcc[PLMN_LIST_MAX_SIZE];
  uint16_t mnc[PLMN_LIST_MAX_SIZE];
  uint8_t mnc_digit_length[PLMN_LIST_MAX_SIZE];
  for (int i = 0; i < num_plmn_old; ++i) {
    mcc[i] = conf->mcc[i];
    mnc[i] = conf->mnc[i];
    mnc_digit_length[i] = conf->mnc_digit_length[i];
  }

  conf->num_plmn = (uint8_t) n_plmn;
  for (int i = 0; i < conf->num_plmn; ++i) {
    conf->mcc[i] = plmn_id[i]->mcc;
    conf->mnc[i] = plmn_id[i]->mnc;
    conf->mnc_digit_length[i] = plmn_id[i]->mnc_length;
  }

  rrc_eNB_carrier_data_t *carrier = &rrc->carrier[CC_id];
  extern uint8_t do_SIB1(rrc_eNB_carrier_data_t *carrier,
                         int Mod_id,
                         int CC_id,
                         BOOLEAN_t brOption,
                         RrcConfigurationReq *configuration);
  carrier->sizeof_SIB1 = do_SIB1(carrier, mod_id, CC_id, FALSE, conf);
  if (carrier->sizeof_SIB1 < 0)
    return -1337; /* SIB1 encoding failed, hell will probably break loose */

  s1ap->num_plmn = (uint8_t) n_plmn;
  for (int i = 0; i < conf->num_plmn; ++i) {
    s1ap->mcc[i] = plmn_id[i]->mcc;
    s1ap->mnc[i] = plmn_id[i]->mnc;
    s1ap->mnc_digit_length[i] = plmn_id[i]->mnc_length;
  }

  int bpn_failed = 0;
  struct s1ap_eNB_mme_data_s *mme = NULL;
  RB_FOREACH(mme, s1ap_mme_map, &s1ap->s1ap_mme_head) {
    for (int i = 0; i < mme->broadcast_plmn_num; ++i) {
      /* search the new index and update. If we don't find, we count this using
       * bpn_failed to now how many broadcast_plmns could not be updated */
      int idx = mme->broadcast_plmn_index[i];
      int omcc = mcc[idx];
      int omnc = mnc[idx];
      int omncl = mnc_digit_length[idx];
      int j = 0;
      for (j = 0; j < s1ap->num_plmn; ++j) {
        if (s1ap->mcc[j] == omcc
            && s1ap->mnc[j] == omnc
            && s1ap->mnc_digit_length[j] == omncl) {
          mme->broadcast_plmn_index[i] = j;
          break;
        }
      }
      if (j == s1ap->num_plmn) /* could not find the old PLMN in the new ones */
        bpn_failed++;
    }
  }
  if (bpn_failed > 0)
    return -10000 - bpn_failed;
  return 0;
}

int flexran_get_s1ap_ue(mid_t mod_id, rnti_t rnti, Protocol__FlexS1apUe * ue_conf){
  if (!rrc_is_present(mod_id)) return -1;
  s1ap_eNB_instance_t *s1ap = s1ap_eNB_get_instance(mod_id);
  if (!s1ap) return -1;

  uint32_t enb_ue_s1ap_id = flexran_get_rrc_enb_ue_s1ap_id(mod_id, rnti);
  struct s1ap_eNB_ue_context_s *ue = NULL;
  RB_FOREACH(ue, s1ap_ue_map, &s1ap->s1ap_ue_head){
    if (ue->eNB_ue_s1ap_id == enb_ue_s1ap_id) break;
  }
  if (!ue) return 0; // UE does not exist: it might be connected but CN did not answer

  if (ue->mme_ref->mme_s1_ip.ipv4)
    ue_conf->mme_s1_ip = (char*) &ue->mme_ref->mme_s1_ip.ipv4_address[0];
  else if (ue->mme_ref->mme_s1_ip.ipv6)
    ue_conf->mme_s1_ip = (char*) &ue->mme_ref->mme_s1_ip.ipv6_address[0];

  ue_conf->has_enb_ue_s1ap_id = 1;
  ue_conf->enb_ue_s1ap_id = ue->eNB_ue_s1ap_id;
  ue_conf->has_mme_ue_s1ap_id = 1;
  ue_conf->mme_ue_s1ap_id = ue->mme_ue_s1ap_id;

  ue_conf->selected_plmn = malloc(sizeof(Protocol__FlexPlmn));
  if (!ue_conf->selected_plmn) return -1;
  protocol__flex_plmn__init(ue_conf->selected_plmn);

  ue_conf->selected_plmn->has_mcc = 1;
  ue_conf->selected_plmn->mcc = s1ap->mcc[ue->selected_plmn_identity];
  ue_conf->selected_plmn->has_mnc = 1;
  ue_conf->selected_plmn->mnc = s1ap->mnc[ue->selected_plmn_identity];
  ue_conf->selected_plmn->has_mnc_length = 1;
  ue_conf->selected_plmn->mnc_length = s1ap->mnc_digit_length[ue->selected_plmn_identity];
  return 0;
}

/**************************** General BS info  ****************************/
uint64_t flexran_get_bs_id(mid_t mod_id) {
  if (!rrc_is_present(mod_id)) return 0;

  return RC.rrc[mod_id]->nr_cellid;
}

size_t flexran_get_capabilities(mid_t mod_id, Protocol__FlexBsCapability **caps) {
  if (!caps) return 0;

  if (!rrc_is_present(mod_id)) return 0;

  size_t n_caps = 0;

  switch (RC.rrc[mod_id]->node_type) {
    case ngran_eNB_CU:
    case ngran_ng_eNB_CU:
    case ngran_gNB_CU:
      n_caps = 4;
      *caps = calloc(n_caps, sizeof(Protocol__FlexBsCapability));
      AssertFatal(*caps, "could not allocate %zu bytes for Protocol__FlexBsCapability array\n",
                  n_caps * sizeof(Protocol__FlexBsCapability));
      (*caps)[0] = PROTOCOL__FLEX_BS_CAPABILITY__PDCP;
      (*caps)[1] = PROTOCOL__FLEX_BS_CAPABILITY__SDAP;
      (*caps)[2] = PROTOCOL__FLEX_BS_CAPABILITY__RRC;
      (*caps)[3] = PROTOCOL__FLEX_BS_CAPABILITY__S1AP;
      break;
    case ngran_eNB_DU:
    case ngran_gNB_DU:
      n_caps = 5;
      *caps = calloc(n_caps, sizeof(Protocol__FlexBsCapability));
      AssertFatal(*caps, "could not allocate %zu bytes for Protocol__FlexBsCapability array\n",
                  n_caps * sizeof(Protocol__FlexBsCapability));
      (*caps)[0] = PROTOCOL__FLEX_BS_CAPABILITY__LOPHY;
      (*caps)[1] = PROTOCOL__FLEX_BS_CAPABILITY__HIPHY;
      (*caps)[2] = PROTOCOL__FLEX_BS_CAPABILITY__LOMAC;
      (*caps)[3] = PROTOCOL__FLEX_BS_CAPABILITY__HIMAC;
      (*caps)[4] = PROTOCOL__FLEX_BS_CAPABILITY__RLC;
      break;
    case ngran_eNB:
    case ngran_ng_eNB:
    case ngran_gNB:
      n_caps = 9;
      *caps = calloc(n_caps, sizeof(Protocol__FlexBsCapability));
      AssertFatal(*caps, "could not allocate %zu bytes for Protocol__FlexBsCapability array\n",
                  n_caps * sizeof(Protocol__FlexBsCapability));
      (*caps)[0] = PROTOCOL__FLEX_BS_CAPABILITY__LOPHY;
      (*caps)[1] = PROTOCOL__FLEX_BS_CAPABILITY__HIPHY;
      (*caps)[2] = PROTOCOL__FLEX_BS_CAPABILITY__LOMAC;
      (*caps)[3] = PROTOCOL__FLEX_BS_CAPABILITY__HIMAC;
      (*caps)[4] = PROTOCOL__FLEX_BS_CAPABILITY__RLC;
      (*caps)[5] = PROTOCOL__FLEX_BS_CAPABILITY__PDCP;
      (*caps)[6] = PROTOCOL__FLEX_BS_CAPABILITY__SDAP;
      (*caps)[7] = PROTOCOL__FLEX_BS_CAPABILITY__RRC;
      (*caps)[8] = PROTOCOL__FLEX_BS_CAPABILITY__S1AP;
      break;
    case ngran_eNB_MBMS_STA:
      AssertFatal(0, "MBMS STA not supported by FlexRAN!\n");
     break;
  }

  return n_caps;
}

uint32_t flexran_get_capabilities_mask(mid_t mod_id) {
  if (!rrc_is_present(mod_id)) return 0;
  uint32_t mask = 0;
  switch (RC.rrc[mod_id]->node_type) {
    case ngran_eNB_CU:
    case ngran_ng_eNB_CU:
    case ngran_gNB_CU:
      mask = (1 << PROTOCOL__FLEX_BS_CAPABILITY__PDCP)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__SDAP)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__RRC)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__S1AP);
      break;
    case ngran_eNB_DU:
    case ngran_gNB_DU:
      mask = (1 << PROTOCOL__FLEX_BS_CAPABILITY__LOPHY)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__HIPHY)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__LOMAC)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__HIMAC)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__RLC);
      break;
    case ngran_eNB:
    case ngran_ng_eNB:
    case ngran_gNB:
      mask = (1 << PROTOCOL__FLEX_BS_CAPABILITY__LOPHY)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__HIPHY)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__LOMAC)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__HIMAC)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__RLC)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__PDCP)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__SDAP)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__RRC)
           | (1 << PROTOCOL__FLEX_BS_CAPABILITY__S1AP);
      break;
    case ngran_eNB_MBMS_STA:
      AssertFatal(0, "MBMS STA not supported by FlexRAN!\n");
     break;
  }

  return mask;
}

size_t flexran_get_splits(mid_t mod_id, Protocol__FlexBsSplit **splits) {
  size_t n_splits = 0;
  *splits = NULL;
  if (rrc_is_present(mod_id) && !NODE_IS_MONOLITHIC(RC.rrc[mod_id]->node_type))
    n_splits++;
  if (NFAPI_MODE != NFAPI_MONOLITHIC)
    n_splits++;
  if (RC.ru && RC.ru[mod_id] && RC.ru[mod_id]->if_south != LOCAL_RF)
    n_splits++;
  if (n_splits == 0)
    return 0;

  AssertFatal(n_splits < 3, "illegal number of splits (%lu)\n", n_splits);
  *splits = calloc(n_splits, sizeof(Protocol__FlexBsSplit));
  AssertFatal(*splits, "could not allocate Protocol__FlexBsSplit array\n");
  int n = 0;
  if (rrc_is_present(mod_id) && !NODE_IS_MONOLITHIC(RC.rrc[mod_id]->node_type))
    (*splits)[n++] = PROTOCOL__FLEX_BS_SPLIT__F1;
  if (NFAPI_MODE != NFAPI_MONOLITHIC)
    (*splits)[n++] = PROTOCOL__FLEX_BS_SPLIT__nFAPI;
  if (RC.ru && RC.ru[mod_id] && RC.ru[mod_id]->if_south == REMOTE_IF4p5)
    (*splits)[n++] = PROTOCOL__FLEX_BS_SPLIT__IF4p5;
  if (RC.ru && RC.ru[mod_id] && RC.ru[mod_id]->if_south == REMOTE_IF5)
    (*splits)[n++] = PROTOCOL__FLEX_BS_SPLIT__IF5;
  DevAssert(n == n_splits);
  return n_splits;
}