/*
 * SPDX-License-Identifier: LicenseRef-CSSL-1.0
 */

#include "nr_radio_config.h"

#include <endian.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "BIT_STRING.h"
#include "NULL.h"
#include "asn_SET_OF.h"
#include "asn_codecs.h"
#include "asn_internal.h"
#include "assertions.h"
#include "common/openairinterface5g_limits.h"
#include "common/utils/T/T.h"
#include "common/utils/nr/nr_common.h"
#include "constr_TYPE.h"
#include "executables/softmodem-common.h"
#include "oai_asn1.h"
#include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
#include "openair2/LAYER2/NR_MAC_gNB/mac_proto.h"
#include "openair3/UTILS/conversions.h"
#include "LAYER2/nr_rlc/nr_rlc_asn1_utils.h"
#include "bits.h"

#include "NR_MeasurementTimingConfiguration.h"
#include "uper_decoder.h"
#include "uper_encoder.h"
#include "utils.h"
#include "xer_encoder.h"

#define NR_MAX_SUPPORTED_DL_LAYERS 4

/* Default values for measurement gap configuration */
#define DEFAULT_MGRP NR_GapConfig__mgrp_ms160
#define DEFAULT_MGTA NR_GapConfig__mgta_ms0dot5
#define DEFAULT_MGL NR_GapConfig__mgl_ms6

const uint8_t slotsperframe[5] = {10, 20, 40, 80, 160};

static NR_BWP_t clone_generic_parameters(const NR_BWP_t *gp)
{
  NR_BWP_t clone = {0};
  clone.locationAndBandwidth = gp->locationAndBandwidth;
  clone.subcarrierSpacing = gp->subcarrierSpacing;
  if (gp->cyclicPrefix) {
    asn1cCallocOne(clone.cyclicPrefix, *gp->cyclicPrefix);
  }
  return clone;
}

/**
 * @brief Verifies the aggregation level candidates
 *
 * This function checks the input aggregation level candidates and translates the value provided
 * in the config to a valid field in RRC message.
 *
 * @param[in] num_cce_in_coreset number of CCE in coreset
 * @param[in] in_num_agg_level_candidates input array of aggregation level candidates, interpreted as number of candidates.
 * @param[in] coresetid coreset id
 * @param[in] searchspaceid searchspace id
 * @param[out] out_num_agg_level_candidates array of aggregation level candidates, output is a valid 3gpp field value.
 * 
 */
static void verify_agg_levels(int num_cce_in_coreset,
                              const int in_num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS],
                              int coresetid,
                              int out_num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS])
{
  int agg_level_to_n_cces[] = {1, 2, 4, 8, 16};
  for (int i = 0; i < NUM_PDCCH_AGG_LEVELS; i++) {
    // 7 is not a valid value, the mapping in 38.331 is from 0-7 mapped to 0-8 candidates and value 7 means 8 candidates instead. If
    // the user wants 7 candidates round it up to 8.
    int num_agg_level_candidates = in_num_agg_level_candidates[i];
    if (num_agg_level_candidates == 7) {
      num_agg_level_candidates = 8;
    }
    if (num_agg_level_candidates * agg_level_to_n_cces[i] > num_cce_in_coreset) {
      int new_agg_level_candidates = num_cce_in_coreset / agg_level_to_n_cces[i];
      LOG_E(NR_RRC,
            "Invalid configuration: Not enough CCEs in coreset %d, agg_level %d, number of requested "
            "candidates = %d, number of CCES in coreset %d. Aggregation level candidates limited to %d\n",
            coresetid,
            agg_level_to_n_cces[i],
            in_num_agg_level_candidates[i],
            num_cce_in_coreset,
            new_agg_level_candidates);
      num_agg_level_candidates = new_agg_level_candidates;
    }
    out_num_agg_level_candidates[i] = min(num_agg_level_candidates, 7);
  }
}

static NR_SetupRelease_RACH_ConfigCommon_t *clone_rach_configcommon(const NR_SetupRelease_RACH_ConfigCommon_t *rcc)
{
  if (rcc == NULL || rcc->present == NR_SetupRelease_RACH_ConfigCommon_PR_NOTHING)
    return NULL;
  NR_SetupRelease_RACH_ConfigCommon_t *clone = calloc_or_fail(1, sizeof(*clone));
  clone->present = rcc->present;
  if (clone->present == NR_SetupRelease_RACH_ConfigCommon_PR_release)
    return clone;

  uint8_t buf[1024];
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_RACH_ConfigCommon, NULL, rcc->choice.setup, buf, sizeof(buf));
  AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded < sizeof(buf), "could not clone NR_RACH_ConfigCommon: problem while encoding\n");
  asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_RACH_ConfigCommon, (void **)&clone->choice.setup, buf, enc_rval.encoded, 0, 0);
  AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed == enc_rval.encoded, "could not clone NR_RACH_ConfigCommon: problem while decoding\n");
  return clone;
}

static NR_SetupRelease_MsgA_ConfigCommon_r16_t *clone_msga_configcommon(const NR_SetupRelease_MsgA_ConfigCommon_r16_t *mcc)
{
  if (mcc == NULL || mcc->present == NR_SetupRelease_MsgA_ConfigCommon_r16_PR_NOTHING)
    return NULL;
  NR_SetupRelease_MsgA_ConfigCommon_r16_t *clone = calloc_or_fail(1, sizeof(*clone));
  clone->present = mcc->present;
  if (clone->present == NR_SetupRelease_MsgA_ConfigCommon_r16_PR_release)
    return clone;
  uint8_t buf[1024];
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_MsgA_ConfigCommon_r16, NULL, mcc->choice.setup, buf, sizeof(buf));
  AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded < sizeof(buf),
              "could not clone NR_MsgA_ConfigCommon_r16: problem while encoding\n");
  asn_dec_rval_t dec_rval =
      uper_decode(NULL, &asn_DEF_NR_MsgA_ConfigCommon_r16, (void **)&clone->choice.setup, buf, enc_rval.encoded, 0, 0);
  AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed == enc_rval.encoded,
              "could not clone NR_MsgA_ConfigCommon_r16:: problem while decoding\n");
  return clone;
}

static NR_SetupRelease_PUSCH_ConfigCommon_t *clone_pusch_configcommon(const NR_SetupRelease_PUSCH_ConfigCommon_t *pcc)
{
  if (pcc == NULL || pcc->present == NR_SetupRelease_PUSCH_ConfigCommon_PR_NOTHING)
    return NULL;
  NR_SetupRelease_PUSCH_ConfigCommon_t *clone = calloc_or_fail(1, sizeof(*clone));
  clone->present = pcc->present;
  if (clone->present == NR_SetupRelease_PUSCH_ConfigCommon_PR_release)
    return clone;

  uint8_t buf[1024];
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_PUSCH_ConfigCommon, NULL, pcc->choice.setup, buf, sizeof(buf));
  AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded < sizeof(buf), "could not clone NR_PUSCH_ConfigCommon: problem while encoding\n");
  asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_PUSCH_ConfigCommon, (void **)&clone->choice.setup, buf, enc_rval.encoded, 0, 0);
  AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed == enc_rval.encoded, "could not clone NR_PUSCH_ConfigCommon: problem while decoding\n");
  return clone;
}

static NR_SetupRelease_PUCCH_ConfigCommon_t *clone_pucch_configcommon(const NR_SetupRelease_PUCCH_ConfigCommon_t *pcc)
{
  if (pcc == NULL || pcc->present == NR_SetupRelease_PUCCH_ConfigCommon_PR_NOTHING)
    return NULL;
  NR_SetupRelease_PUCCH_ConfigCommon_t *clone = calloc_or_fail(1, sizeof(*clone));
  clone->present = pcc->present;
  if (clone->present == NR_SetupRelease_PUCCH_ConfigCommon_PR_release)
    return clone;

  uint8_t buf[1024];
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_PUCCH_ConfigCommon, NULL, pcc->choice.setup, buf, sizeof(buf));
  AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded < sizeof(buf), "could not clone NR_PUCCH_ConfigCommon: problem while encoding\n");
  asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_PUCCH_ConfigCommon, (void **)&clone->choice.setup, buf, enc_rval.encoded, 0, 0);
  AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed == enc_rval.encoded, "could not clone NR_PUCCH_ConfigCommon: problem while decoding\n");
  return clone;
}

static NR_SetupRelease_PDCCH_ConfigCommon_t *clone_pdcch_configcommon(const NR_SetupRelease_PDCCH_ConfigCommon_t *pcc)
{
  if (pcc == NULL || pcc->present == NR_SetupRelease_PDCCH_ConfigCommon_PR_NOTHING)
    return NULL;
  NR_SetupRelease_PDCCH_ConfigCommon_t *clone = calloc(1, sizeof(*clone));
  clone->present = pcc->present;
  if (clone->present == NR_SetupRelease_PDCCH_ConfigCommon_PR_release)
    return clone;

  uint8_t buf[1024];
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_PDCCH_ConfigCommon, NULL, pcc->choice.setup, buf, sizeof(buf));
  AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded < sizeof(buf), "could not clone NR_PDCCH_ConfigCommon: problem while encoding\n");
  asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_PDCCH_ConfigCommon, (void **)&clone->choice.setup, buf, enc_rval.encoded, 0, 0);
  AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed == enc_rval.encoded, "could not clone NR_PDCCH_ConfigCommon: problem while decoding\n");
  return clone;
}

static NR_SetupRelease_PDSCH_ConfigCommon_t *clone_pdsch_configcommon(const NR_SetupRelease_PDSCH_ConfigCommon_t *pcc)
{
  if (pcc == NULL || pcc->present == NR_SetupRelease_PDSCH_ConfigCommon_PR_NOTHING)
    return NULL;
  NR_SetupRelease_PDSCH_ConfigCommon_t *clone = calloc_or_fail(1, sizeof(*clone));
  clone->present = pcc->present;
  if (clone->present == NR_SetupRelease_PDSCH_ConfigCommon_PR_release)
    return clone;

  uint8_t buf[1024];
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_PDSCH_ConfigCommon, NULL, pcc->choice.setup, buf, sizeof(buf));
  AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded < sizeof(buf), "could not clone NR_PDSCH_ConfigCommon: problem while encoding\n");
  asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_PDSCH_ConfigCommon, (void **)&clone->choice.setup, buf, enc_rval.encoded, 0, 0);
  AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed == enc_rval.encoded, "could not clone NR_PDSCH_ConfigCommon: problem while decoding\n");
  return clone;
}

static int get_pucch2_size(const int num_ant_ports)
{
  // TODO the logic to set the number of PRBs needs to be improved
  //      it should involve the code rate parameter and the max number of bits to be transmitted
  return (num_ant_ports <= 4 ? 8 : 16);
}

static int get_nb_pucch2_per_slot(const NR_ServingCellConfigCommon_t *scc, int bwp_size, const nr_pdsch_AntennaPorts_t *ap)
{
  const NR_TDD_UL_DL_Pattern_t *tdd = scc->tdd_UL_DL_ConfigurationCommon ? &scc->tdd_UL_DL_ConfigurationCommon->pattern1 : NULL;
  const int n_slots_frame = slotsperframe[*scc->ssbSubcarrierSpacing];
  int ul_slots_period = tdd ? tdd->nrofUplinkSlots + (tdd->nrofUplinkSymbols > 0) : n_slots_frame;
  int n_slots_period = tdd ? n_slots_frame/get_nb_periods_per_frame(tdd->dl_UL_TransmissionPeriodicity) : n_slots_frame;
  int max_meas_report_period = 320; // slots
  int max_csi_reports = MAX_MOBILES_PER_GNB << 1; // 2 reports per UE (RSRP and RI-PMI-CQI)
  int available_report_occasions = max_meas_report_period * ul_slots_period / n_slots_period;
  int nb_pucch2 = (max_csi_reports / (available_report_occasions + 1)) + 1;
  int pucch2_size = get_pucch2_size(ap->N1 * ap->N2 * ap->XP);
  // in current implementation we need (nb_pucch2 * pucch2_size) prbs for PUCCH2
  // and MAX_MOBILES_PER_GNB prbs for PUCCH1
  // checked for validity in verify_radio_configuration
  AssertFatal((nb_pucch2 * pucch2_size) + MAX_MOBILES_PER_GNB <= bwp_size,
              "Cannot allocate all required PUCCH resources for max number of %d UEs in BWP with %d PRBs\n",
              MAX_MOBILES_PER_GNB,
              bwp_size);
  return nb_pucch2;
}

NR_SearchSpace_t *rrc_searchspace_config(bool is_common,
                                         int searchspaceid,
                                         int coresetid,
                                         const int num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS])
{
  NR_SearchSpace_t *ss = calloc(1,sizeof(*ss));
  ss->searchSpaceId = searchspaceid;
  ss->controlResourceSetId = calloc(1,sizeof(*ss->controlResourceSetId));
  *ss->controlResourceSetId = coresetid;
  ss->monitoringSlotPeriodicityAndOffset = calloc(1,sizeof(*ss->monitoringSlotPeriodicityAndOffset));
  ss->monitoringSlotPeriodicityAndOffset->present = NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl1;
  ss->monitoringSlotPeriodicityAndOffset->choice.sl1 = (NULL_t)0;
  ss->duration = NULL;
  ss->monitoringSymbolsWithinSlot = calloc(1,sizeof(*ss->monitoringSymbolsWithinSlot));
  ss->monitoringSymbolsWithinSlot->buf = calloc(1,2);
  ss->monitoringSymbolsWithinSlot->size = 2;
  ss->monitoringSymbolsWithinSlot->buf[0] = 0x80;
  ss->monitoringSymbolsWithinSlot->buf[1] = 0x0;
  ss->monitoringSymbolsWithinSlot->bits_unused = 2;
  ss->nrofCandidates = calloc(1,sizeof(*ss->nrofCandidates));
  ss->nrofCandidates->aggregationLevel1 = num_agg_level_candidates[PDCCH_AGG_LEVEL1];
  ss->nrofCandidates->aggregationLevel2 = num_agg_level_candidates[PDCCH_AGG_LEVEL2];
  ss->nrofCandidates->aggregationLevel4 = num_agg_level_candidates[PDCCH_AGG_LEVEL4];
  ss->nrofCandidates->aggregationLevel8 = num_agg_level_candidates[PDCCH_AGG_LEVEL8];
  ss->nrofCandidates->aggregationLevel16 = num_agg_level_candidates[PDCCH_AGG_LEVEL16];
  ss->searchSpaceType = calloc(1,sizeof(*ss->searchSpaceType));
  if (is_common) {
    ss->searchSpaceType->present = NR_SearchSpace__searchSpaceType_PR_common;
    ss->searchSpaceType->choice.common = calloc(1,sizeof(*ss->searchSpaceType->choice.common));
    ss->searchSpaceType->choice.common->dci_Format0_0_AndFormat1_0 = calloc(1,sizeof(*ss->searchSpaceType->choice.common->dci_Format0_0_AndFormat1_0));
  }
  else {
    ss->searchSpaceType->present = NR_SearchSpace__searchSpaceType_PR_ue_Specific;
    ss->searchSpaceType->choice.ue_Specific = calloc(1,sizeof(*ss->searchSpaceType->choice.ue_Specific));
    ss->searchSpaceType->choice.ue_Specific->dci_Formats=NR_SearchSpace__searchSpaceType__ue_Specific__dci_Formats_formats0_1_And_1_1;
  }
  return ss;
}

static NR_ControlResourceSet_t *get_coreset_config(int bwp_id,
                                                   int cset_offset,
                                                   int cset_limit,
                                                   int bwp_start,
                                                   int bwp_size,
                                                   uint64_t ssb_bitmap,
                                                   bool doTCI)
{
  NR_ControlResourceSet_t *coreset = calloc(1, sizeof(*coreset));
  AssertFatal(coreset != NULL, "out of memory\n");
  // frequency domain resources depending on BWP size
  int additional_offset = (bwp_start + 5) / 6 * 6 - bwp_start;
  int cset_shift = 0;
  if (cset_offset > additional_offset) {
    cset_shift = cset_offset / 6 + (cset_offset % 6 > 0);
    additional_offset += cset_shift * 6;
  }
  int eff_bwp_size = bwp_size - additional_offset;
  if (cset_limit && eff_bwp_size > cset_limit)
    eff_bwp_size = cset_limit;
  AssertFatal(eff_bwp_size >= 24, "CORESET effective BWP size %d is less than 24 PRB, can't allocate\n", eff_bwp_size);
  coreset->frequencyDomainResources.buf = calloc(1,6);
  uint64_t buf = 0;
  if (eff_bwp_size < 48)
    buf = 0xf;
  else {
    for (int i = 0; i < eff_bwp_size / 48; i++) {
      buf <<= 8;
      buf += 0xff;
    }
  }
  buf <<= cset_shift;
  for (int b = 0; b < 6; b++) {
    uint8_t temp_buf = (buf >> (8 * b)) & 0xff;
    reverse_bits_u8(&temp_buf, 1, &coreset->frequencyDomainResources.buf[b]);
  }
  coreset->frequencyDomainResources.size = 6;
  coreset->frequencyDomainResources.bits_unused = 3;
  coreset->duration = (eff_bwp_size < 48) ? 2 : 1;
  coreset->cce_REG_MappingType.present = NR_ControlResourceSet__cce_REG_MappingType_PR_nonInterleaved;
  coreset->precoderGranularity = NR_ControlResourceSet__precoderGranularity_sameAsREG_bundle;

  // The ID space is used across the BWPs of a Serving Cell as per 38.331
  coreset->controlResourceSetId = bwp_id + 1;

  if (doTCI) {
    coreset->tci_StatesPDCCH_ToAddList = calloc(1,sizeof(*coreset->tci_StatesPDCCH_ToAddList));
    int num_ssb = 0;
    for (int i = 0; i < 64; i++) {
      if ((ssb_bitmap >> (63 - i)) & 0x01) {
        NR_TCI_StateId_t *tci = calloc(1, sizeof(*tci));
        *tci = num_ssb++;
        asn1cSeqAdd(&coreset->tci_StatesPDCCH_ToAddList->list, tci);
      }
    }
  }
  coreset->tci_StatesPDCCH_ToReleaseList = NULL;
  coreset->tci_PresentInDCI = NULL;
  coreset->pdcch_DMRS_ScramblingID = NULL;
  return coreset;
}

uint64_t get_ssb_bitmap(const NR_ServingCellConfigCommon_t *scc)
{
  uint64_t bitmap=0;
  switch (scc->ssb_PositionsInBurst->present) {
    case 1 :
      bitmap = ((uint64_t) scc->ssb_PositionsInBurst->choice.shortBitmap.buf[0])<<56;
      break;
    case 2 :
      bitmap = ((uint64_t) scc->ssb_PositionsInBurst->choice.mediumBitmap.buf[0])<<56;
      break;
    case 3 :
      for (int i=0; i<8; i++) {
        bitmap |= (((uint64_t) scc->ssb_PositionsInBurst->choice.longBitmap.buf[i])<<((7-i)*8));
      }
      break;
    default:
      AssertFatal(1==0,"SSB bitmap size value %d undefined (allowed values 1,2,3) \n", scc->ssb_PositionsInBurst->present);
  }
  return bitmap;
}

static bool check_periodicity(int val, int ideal_period, const frame_structure_t *fs)
{
  bool valid_periodicity_for_tdd_period = fs->frame_type == FDD ? true : (val % fs->numb_slots_period == 0);
  return (ideal_period < val + 1) && valid_periodicity_for_tdd_period;
}

static int set_ideal_period(bool is_csi)
{
  const frame_structure_t *fs = &RC.nrmac[0]->frame_structure;
  const int nb_slots_per_period = fs->numb_slots_period;
  const int n_ul_slots_per_period = get_ul_slots_per_period(fs); // full UL + mixed with UL symbols
  // 2 reports per UE (RSRP and RI-PMI-CQI)
  return is_csi ? MAX_MOBILES_PER_GNB * 2 * nb_slots_per_period / n_ul_slots_per_period : nb_slots_per_period * MAX_MOBILES_PER_GNB;
}

static void set_csirs_periodicity(NR_NZP_CSI_RS_Resource_t *nzpcsi0,
                                  int id,
                                  int ideal_period,
                                  const frame_structure_t *fs)
{
  nzpcsi0->periodicityAndOffset = calloc(1,sizeof(*nzpcsi0->periodicityAndOffset));
  // TODO ideal period to be set according to estimation by the gNB on how fast the channel changes
  const int offset = id; // id = ssb_index/2, offset should be set to ssb_index/2.
  if (check_periodicity(4, ideal_period, fs)) {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots4;
    nzpcsi0->periodicityAndOffset->choice.slots4 = offset;
  }
  else if (check_periodicity(5, ideal_period, fs)) {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots5;
    nzpcsi0->periodicityAndOffset->choice.slots5 = offset;
  }
  else if (check_periodicity(8, ideal_period, fs)) {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots8;
    nzpcsi0->periodicityAndOffset->choice.slots8 = offset;
  }
  else if (check_periodicity(10, ideal_period, fs)) {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots10;
    nzpcsi0->periodicityAndOffset->choice.slots10 = offset;
  }
  else if (check_periodicity(16, ideal_period, fs)) {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots16;
    nzpcsi0->periodicityAndOffset->choice.slots16 = offset;
  }
  else if (check_periodicity(20, ideal_period, fs)) {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots20;
    nzpcsi0->periodicityAndOffset->choice.slots20 = offset;
  }
  else if (check_periodicity(40, ideal_period, fs)) {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots40;
    nzpcsi0->periodicityAndOffset->choice.slots40 = offset;
  }
  else if (check_periodicity(80, ideal_period, fs)) {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots80;
    nzpcsi0->periodicityAndOffset->choice.slots80 = offset;
  }
  else if (check_periodicity(160, ideal_period, fs)) {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots160;
    nzpcsi0->periodicityAndOffset->choice.slots160 = offset;
  }
  else {
    nzpcsi0->periodicityAndOffset->present = NR_CSI_ResourcePeriodicityAndOffset_PR_slots320;
    const int nb_dl_slots_period = get_full_dl_slots_per_period(fs); // full DL slots
    // checked for validity in verify_radio_configuration
    AssertFatal(offset / 320 < nb_dl_slots_period, "Cannot allocate CSI-RS for BWP %d. Not enough resources for CSI-RS\n", id);
    nzpcsi0->periodicityAndOffset->choice.slots320 = (offset % 320) + (offset / 320);
  }
}

static void config_csirs(const NR_ServingCellConfigCommon_t *servingcellconfigcommon,
                         NR_CSI_MeasConfig_t *csi_MeasConfig,
                         int num_dl_antenna_ports,
                         int curr_bwp,
                         int do_csirs,
                         int id)
{
  if (do_csirs) {

    if(!csi_MeasConfig->nzp_CSI_RS_ResourceSetToAddModList)
      csi_MeasConfig->nzp_CSI_RS_ResourceSetToAddModList  = calloc(1,sizeof(*csi_MeasConfig->nzp_CSI_RS_ResourceSetToAddModList));
    NR_NZP_CSI_RS_ResourceSet_t *nzpcsirs0 = calloc(1,sizeof(*nzpcsirs0));
    nzpcsirs0->nzp_CSI_ResourceSetId = id;
    NR_NZP_CSI_RS_ResourceId_t *nzpid0 = calloc(1,sizeof(*nzpid0));
    *nzpid0 = id;
    asn1cSeqAdd(&nzpcsirs0->nzp_CSI_RS_Resources,nzpid0);
    nzpcsirs0->repetition = NULL;
    nzpcsirs0->aperiodicTriggeringOffset = NULL;
    nzpcsirs0->trs_Info = NULL;
    asn1cSeqAdd(&csi_MeasConfig->nzp_CSI_RS_ResourceSetToAddModList->list,nzpcsirs0);
    if(!csi_MeasConfig->nzp_CSI_RS_ResourceToAddModList)
      csi_MeasConfig->nzp_CSI_RS_ResourceToAddModList = calloc(1,sizeof(*csi_MeasConfig->nzp_CSI_RS_ResourceToAddModList));
    NR_NZP_CSI_RS_Resource_t *nzpcsi0 = calloc(1,sizeof(*nzpcsi0));
    nzpcsi0->nzp_CSI_RS_ResourceId = id;
    NR_CSI_RS_ResourceMapping_t resourceMapping = {0};
    switch (num_dl_antenna_ports) {
      case 1:
        resourceMapping.frequencyDomainAllocation.present = NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row2;
        resourceMapping.frequencyDomainAllocation.choice.row2.buf = calloc(2, sizeof(uint8_t));
        resourceMapping.frequencyDomainAllocation.choice.row2.size = 2;
        resourceMapping.frequencyDomainAllocation.choice.row2.bits_unused = 4;
        resourceMapping.frequencyDomainAllocation.choice.row2.buf[0] = 0;
        resourceMapping.frequencyDomainAllocation.choice.row2.buf[1] = 16;
        resourceMapping.nrofPorts = NR_CSI_RS_ResourceMapping__nrofPorts_p1;
        resourceMapping.cdm_Type = NR_CSI_RS_ResourceMapping__cdm_Type_noCDM;
        break;
      case 2:
        resourceMapping.frequencyDomainAllocation.present = NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_other;
        resourceMapping.frequencyDomainAllocation.choice.other.buf = calloc(1, sizeof(uint8_t));
        resourceMapping.frequencyDomainAllocation.choice.other.size = 1;
        resourceMapping.frequencyDomainAllocation.choice.other.bits_unused = 2;
        resourceMapping.frequencyDomainAllocation.choice.other.buf[0] = 4;
        resourceMapping.nrofPorts = NR_CSI_RS_ResourceMapping__nrofPorts_p2;
        resourceMapping.cdm_Type = NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2;
        break;
      case 4:
        resourceMapping.frequencyDomainAllocation.present = NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row4;
        resourceMapping.frequencyDomainAllocation.choice.row4.buf = calloc(1, sizeof(uint8_t));
        resourceMapping.frequencyDomainAllocation.choice.row4.size = 1;
        resourceMapping.frequencyDomainAllocation.choice.row4.bits_unused = 5;
        resourceMapping.frequencyDomainAllocation.choice.row4.buf[0] = 32;
        resourceMapping.nrofPorts = NR_CSI_RS_ResourceMapping__nrofPorts_p4;
        resourceMapping.cdm_Type = NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2;
        break;
      case 8:
        resourceMapping.frequencyDomainAllocation.present = NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_other;
        resourceMapping.frequencyDomainAllocation.choice.other.buf = calloc(1, sizeof(uint8_t));
        resourceMapping.frequencyDomainAllocation.choice.other.size = 1;
        resourceMapping.frequencyDomainAllocation.choice.other.bits_unused = 2;
        resourceMapping.frequencyDomainAllocation.choice.other.buf[0] = 60;
        resourceMapping.nrofPorts = NR_CSI_RS_ResourceMapping__nrofPorts_p8;
        resourceMapping.cdm_Type = NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2;
        break;
      case 12:
        resourceMapping.frequencyDomainAllocation.present = NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_other;
        resourceMapping.frequencyDomainAllocation.choice.other.buf = calloc(1, sizeof(uint8_t));
        resourceMapping.frequencyDomainAllocation.choice.other.size = 1;
        resourceMapping.frequencyDomainAllocation.choice.other.bits_unused = 2;
        resourceMapping.frequencyDomainAllocation.choice.other.buf[0] = 252;
        resourceMapping.nrofPorts = NR_CSI_RS_ResourceMapping__nrofPorts_p12;
        resourceMapping.cdm_Type = NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2;
        break;
      default:
        AssertFatal(false, "Number of ports not yet supported\n");
    }
    resourceMapping.firstOFDMSymbolInTimeDomain = 13;  // last symbol of slot
    resourceMapping.firstOFDMSymbolInTimeDomain2 = NULL;
    resourceMapping.density.present = NR_CSI_RS_ResourceMapping__density_PR_one;
    resourceMapping.density.choice.one = (NULL_t)0;
    resourceMapping.freqBand.startingRB = 0;
    resourceMapping.freqBand.nrofRBs = ((curr_bwp >> 2) + (curr_bwp % 4 > 0)) << 2;
    nzpcsi0->resourceMapping = resourceMapping;
    nzpcsi0->powerControlOffset = 0;
    nzpcsi0->powerControlOffsetSS = calloc(1,sizeof(*nzpcsi0->powerControlOffsetSS));
    *nzpcsi0->powerControlOffsetSS = NR_NZP_CSI_RS_Resource__powerControlOffsetSS_db0;
    nzpcsi0->scramblingID = *servingcellconfigcommon->physCellId;

    const int ideal_period = set_ideal_period(true); // same periodicity as CSI measurement report
    const frame_structure_t *fs = &(RC.nrmac[0]->frame_structure);
    set_csirs_periodicity(nzpcsi0, id, ideal_period, fs);

    nzpcsi0->qcl_InfoPeriodicCSI_RS = calloc(1,sizeof(*nzpcsi0->qcl_InfoPeriodicCSI_RS));
    *nzpcsi0->qcl_InfoPeriodicCSI_RS = 0;
    asn1cSeqAdd(&csi_MeasConfig->nzp_CSI_RS_ResourceToAddModList->list,nzpcsi0);
  }
  else {
    csi_MeasConfig->nzp_CSI_RS_ResourceToAddModList = NULL;
    csi_MeasConfig->nzp_CSI_RS_ResourceSetToAddModList  = NULL;
  }
  csi_MeasConfig->nzp_CSI_RS_ResourceSetToReleaseList = NULL;
  csi_MeasConfig->nzp_CSI_RS_ResourceToReleaseList = NULL;
}

static void set_csiim_offset(struct NR_CSI_ResourcePeriodicityAndOffset *periodicityAndOffset,
                             struct NR_CSI_ResourcePeriodicityAndOffset *target_periodicityAndOffset)
{
  switch(periodicityAndOffset->present) {
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots4:
      periodicityAndOffset->choice.slots4 = target_periodicityAndOffset->choice.slots4;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots5:
      periodicityAndOffset->choice.slots5 = target_periodicityAndOffset->choice.slots5;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots8:
      periodicityAndOffset->choice.slots8 = target_periodicityAndOffset->choice.slots8;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots10:
      periodicityAndOffset->choice.slots10 = target_periodicityAndOffset->choice.slots10;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots16:
      periodicityAndOffset->choice.slots16 = target_periodicityAndOffset->choice.slots16;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots20:
      periodicityAndOffset->choice.slots20 = target_periodicityAndOffset->choice.slots20;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots32:
      periodicityAndOffset->choice.slots32 = target_periodicityAndOffset->choice.slots32;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots40:
      periodicityAndOffset->choice.slots40 = target_periodicityAndOffset->choice.slots40;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots64:
      periodicityAndOffset->choice.slots64 = target_periodicityAndOffset->choice.slots64;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots80:
      periodicityAndOffset->choice.slots80 = target_periodicityAndOffset->choice.slots80;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots160:
      periodicityAndOffset->choice.slots160 = target_periodicityAndOffset->choice.slots160;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots320:
      periodicityAndOffset->choice.slots320 = target_periodicityAndOffset->choice.slots320;
      break;
    case NR_CSI_ResourcePeriodicityAndOffset_PR_slots640:
      periodicityAndOffset->choice.slots640 = target_periodicityAndOffset->choice.slots640;
      break;
    default:
      AssertFatal(1==0,"CSI periodicity not among allowed values\n");
  }

}

static void config_csiim(int do_csirs,
                         int dl_antenna_ports,
                         int curr_bwp,
                         NR_CSI_MeasConfig_t *csi_MeasConfig,
                         int id)
{
 if (do_csirs && dl_antenna_ports > 1) {
   if (!csi_MeasConfig->csi_IM_ResourceToAddModList)
     csi_MeasConfig->csi_IM_ResourceToAddModList = calloc(1, sizeof(*csi_MeasConfig->csi_IM_ResourceToAddModList));
   NR_CSI_IM_Resource_t *imres = calloc(1,sizeof(*imres));
   imres->csi_IM_ResourceId = id;
   NR_NZP_CSI_RS_Resource_t *nzpcsi = NULL;
   for (int i=0; i<csi_MeasConfig->nzp_CSI_RS_ResourceToAddModList->list.count; i++){
     nzpcsi = csi_MeasConfig->nzp_CSI_RS_ResourceToAddModList->list.array[i];
     if (nzpcsi->nzp_CSI_RS_ResourceId == imres->csi_IM_ResourceId)
       break;
   }
   AssertFatal(nzpcsi->nzp_CSI_RS_ResourceId == imres->csi_IM_ResourceId, "Couldn't find NZP CSI-RS corresponding to CSI-IM\n");
   imres->csi_IM_ResourceElementPattern = calloc(1,sizeof(*imres->csi_IM_ResourceElementPattern));
   imres->csi_IM_ResourceElementPattern->present = NR_CSI_IM_Resource__csi_IM_ResourceElementPattern_PR_pattern1;
   imres->csi_IM_ResourceElementPattern->choice.pattern1 = calloc(1,sizeof(*imres->csi_IM_ResourceElementPattern->choice.pattern1));
   // starting subcarrier is 4 in the following configuration
   // this is ok for current possible CSI-RS configurations (using only the first 4 symbols)
   // TODO needs a more dynamic setting if CSI-RS is changed
   imres->csi_IM_ResourceElementPattern->choice.pattern1->subcarrierLocation_p1 = NR_CSI_IM_Resource__csi_IM_ResourceElementPattern__pattern1__subcarrierLocation_p1_s4;
   imres->csi_IM_ResourceElementPattern->choice.pattern1->symbolLocation_p1 = nzpcsi->resourceMapping.firstOFDMSymbolInTimeDomain; // same symbol as CSI-RS
   imres->freqBand = calloc(1,sizeof(*imres->freqBand));
   imres->freqBand->startingRB = 0;
   imres->freqBand->nrofRBs = ((curr_bwp>>2)+(curr_bwp%4>0))<<2;
   imres->periodicityAndOffset = calloc(1,sizeof(*imres->periodicityAndOffset));
   // same period and offset of the associated CSI-RS
   imres->periodicityAndOffset->present = nzpcsi->periodicityAndOffset->present;
   set_csiim_offset(imres->periodicityAndOffset, nzpcsi->periodicityAndOffset);
   asn1cSeqAdd(&csi_MeasConfig->csi_IM_ResourceToAddModList->list,imres);
   if (!csi_MeasConfig->csi_IM_ResourceSetToAddModList)
     csi_MeasConfig->csi_IM_ResourceSetToAddModList = calloc(1, sizeof(*csi_MeasConfig->csi_IM_ResourceSetToAddModList));
   NR_CSI_IM_ResourceSet_t *imset = calloc(1,sizeof(*imset));
   imset->csi_IM_ResourceSetId = id;
   NR_CSI_IM_ResourceId_t *res = calloc(1,sizeof(*res));
   *res = id;
   asn1cSeqAdd(&imset->csi_IM_Resources,res);
   asn1cSeqAdd(&csi_MeasConfig->csi_IM_ResourceSetToAddModList->list,imset);
 }
 else {
   csi_MeasConfig->csi_IM_ResourceToAddModList = NULL;
   csi_MeasConfig->csi_IM_ResourceSetToAddModList = NULL;
 }

 csi_MeasConfig->csi_IM_ResourceToReleaseList = NULL;
 csi_MeasConfig->csi_IM_ResourceSetToReleaseList = NULL;
}


long ue_supported_dl_layers(const NR_ServingCellConfigCommon_t *scc, const NR_UE_NR_Capability_t *uecap)
{
  NR_SCS_SpecificCarrier_t *scs_carrier = scc->downlinkConfigCommon->frequencyInfoDL->scs_SpecificCarrierList.list.array[0];
  int band = *scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0];
  const frequency_range_t freq_range = get_freq_range_from_band(band);
  const int scs = scs_carrier->subcarrierSpacing;
  const int bw_size = scs_carrier->carrierBandwidth;
  NR_FeatureSets_t *fs = uecap ? uecap->featureSets : NULL;
  if (fs) {
    const int bw_mhz = get_supported_bw_mhz(freq_range, get_supported_band_index(scs, freq_range, bw_size));
    // go through UL feature sets and look for one with current SCS
    for (int i = 0; i < fs->featureSetsDownlinkPerCC->list.count; i++) {
      NR_FeatureSetDownlinkPerCC_t *dl_fs = fs->featureSetsDownlinkPerCC->list.array[i];
      if (scs == dl_fs->supportedSubcarrierSpacingDL &&
          supported_bw_comparison(bw_mhz, &dl_fs->supportedBandwidthDL, dl_fs->channelBW_90mhz) &&
          dl_fs->maxNumberMIMO_LayersPDSCH) {
        return (2 << *dl_fs->maxNumberMIMO_LayersPDSCH);
      }
    }
  }
  return -1;
}

static void set_dl_maxmimolayers(NR_PDSCH_ServingCellConfig_t *pdsch_servingcellconfig,
                                 const NR_ServingCellConfigCommon_t *scc,
                                 const NR_UE_NR_Capability_t *uecap,
                                 int maxMIMO_layers)
{
  if(!pdsch_servingcellconfig->ext1)
    pdsch_servingcellconfig->ext1 = calloc(1, sizeof(*pdsch_servingcellconfig->ext1));
  if(!pdsch_servingcellconfig->ext1->maxMIMO_Layers)
    pdsch_servingcellconfig->ext1->maxMIMO_Layers = calloc(1, sizeof(*pdsch_servingcellconfig->ext1->maxMIMO_Layers));

  long l = ue_supported_dl_layers(scc, uecap);
  long ue_supported_layers = l > 1 ? l : 2; // min UE supported layers = 2
  if (maxMIMO_layers == -1) 
    *pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(NR_MAX_SUPPORTED_DL_LAYERS, ue_supported_layers);
  else 
    *pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(maxMIMO_layers, ue_supported_layers);
}

static struct NR_SRS_Resource__resourceType__periodic *configure_periodic_srs(const int uid)
{
  frame_structure_t *fs = &RC.nrmac[0]->frame_structure;
  int offset = get_ul_slot_offset(fs, uid, false); // only full UL slots for SRS
  // checked for validity in verify_radio_configuration
  AssertFatal(offset < 2560, "Cannot allocate SRS configuration for uid %d, not enough resources\n", uid);
  const int ideal_period = set_ideal_period(false);

  struct NR_SRS_Resource__resourceType__periodic *periodic_srs = calloc(1,sizeof(*periodic_srs));
  if (check_periodicity(4, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl4;
    periodic_srs->periodicityAndOffset_p.choice.sl4 = offset;
  }
  else if (check_periodicity(5, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl5;
    periodic_srs->periodicityAndOffset_p.choice.sl5 = offset;
  }
  else if (check_periodicity(8, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl8;
    periodic_srs->periodicityAndOffset_p.choice.sl8 = offset;
  }
  else if (check_periodicity(10, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl10;
    periodic_srs->periodicityAndOffset_p.choice.sl10 = offset;
  }
  else if (check_periodicity(16, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl16;
    periodic_srs->periodicityAndOffset_p.choice.sl16 = offset;
  }
  else if (check_periodicity(20, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl20;
    periodic_srs->periodicityAndOffset_p.choice.sl20 = offset;
  }
  else if (check_periodicity(32, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl32;
    periodic_srs->periodicityAndOffset_p.choice.sl32 = offset;
  }
  else if (check_periodicity(40, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl40;
    periodic_srs->periodicityAndOffset_p.choice.sl40 = offset;
  }
  else if (check_periodicity(64, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl64;
    periodic_srs->periodicityAndOffset_p.choice.sl64 = offset;
  }
  else if (check_periodicity(80, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl80;
    periodic_srs->periodicityAndOffset_p.choice.sl80 = offset;
  }
  else if (check_periodicity(160, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl160;
    periodic_srs->periodicityAndOffset_p.choice.sl160 = offset;
  }
  else if (check_periodicity(320, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl320;
    periodic_srs->periodicityAndOffset_p.choice.sl320 = offset;
  }
  else if (check_periodicity(640, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl640;
    periodic_srs->periodicityAndOffset_p.choice.sl640 = offset;
  }
  else if (check_periodicity(1280, ideal_period, fs)) {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl1280;
    periodic_srs->periodicityAndOffset_p.choice.sl1280 = offset;
  }
  else {
    periodic_srs->periodicityAndOffset_p.present = NR_SRS_PeriodicityAndOffset_PR_sl2560;
    periodic_srs->periodicityAndOffset_p.choice.sl2560 = offset;
  }
  return periodic_srs;
}

static NR_SRS_ResourceSet_t *get_srs_resourceset(const int resset_id,
                                                 const int res_id,
                                                 const long usage,
                                                 const int minRXTXTIME,
                                                 int do_srs)
{
  NR_SRS_ResourceSet_t *srs_resset = calloc_or_fail(1, sizeof(*srs_resset));
  srs_resset->srs_ResourceSetId = resset_id;
  srs_resset->srs_ResourceIdList = calloc_or_fail(1, sizeof(*srs_resset->srs_ResourceIdList));
  NR_SRS_ResourceId_t *srs_resset_id = calloc_or_fail(1, sizeof(*srs_resset_id));
  *srs_resset_id = res_id;
  asn1cSeqAdd(&srs_resset->srs_ResourceIdList->list, srs_resset_id);
  if (do_srs) {
    srs_resset->resourceType.present = NR_SRS_ResourceSet__resourceType_PR_periodic;
    srs_resset->resourceType.choice.periodic = calloc_or_fail(1, sizeof(*srs_resset->resourceType.choice.periodic));
    srs_resset->resourceType.choice.periodic->associatedCSI_RS = NULL;
  } else {
    srs_resset->resourceType.present = NR_SRS_ResourceSet__resourceType_PR_aperiodic;
    srs_resset->resourceType.choice.aperiodic = calloc_or_fail(1, sizeof(*srs_resset->resourceType.choice.aperiodic));
    srs_resset->resourceType.choice.aperiodic->aperiodicSRS_ResourceTrigger = 1;
    srs_resset->resourceType.choice.aperiodic->csi_RS = NULL;
    srs_resset->resourceType.choice.aperiodic->slotOffset =
        calloc_or_fail(1, sizeof(*srs_resset->resourceType.choice.aperiodic->slotOffset));
    *srs_resset->resourceType.choice.aperiodic->slotOffset = minRXTXTIME;
    srs_resset->resourceType.choice.aperiodic->ext1 = NULL;
  }
  srs_resset->usage = usage;
  srs_resset->alpha = calloc_or_fail(1, sizeof(*srs_resset->alpha));
  *srs_resset->alpha = NR_Alpha_alpha1;
  srs_resset->p0 = calloc_or_fail(1, sizeof(*srs_resset->p0));
  *srs_resset->p0 = -80;
  srs_resset->pathlossReferenceRS = NULL;
  srs_resset->srs_PowerControlAdjustmentStates = NULL;
  return srs_resset;
}

static NR_SRS_Resource_t *get_srs_resource(const NR_UE_NR_Capability_t *uecap,
                                           const int curr_bwp,
                                           const int uid,
                                           const int res_id,
                                           const long maxMIMO_Layers,
                                           const NR_SRS_Resource__transmissionComb_PR tx_comb,
                                           int do_srs)
{
  NR_SRS_Resource_t *srs_res = calloc_or_fail(1, sizeof(*srs_res));
  srs_res->srs_ResourceId = res_id;
  srs_res->nrofSRS_Ports = NR_SRS_Resource__nrofSRS_Ports_port1;
  if (do_srs) {
    long nrofSRS_Ports = 1;
    if (uecap && uecap->featureSets && uecap->featureSets->featureSetsUplink
        && uecap->featureSets->featureSetsUplink->list.count > 0) {
      NR_FeatureSetUplink_t *ul_feature_setup = uecap->featureSets->featureSetsUplink->list.array[0];
      switch (ul_feature_setup->supportedSRS_Resources->maxNumberSRS_Ports_PerResource) {
        case NR_SRS_Resources__maxNumberSRS_Ports_PerResource_n1:
          nrofSRS_Ports = 1;
          break;
        case NR_SRS_Resources__maxNumberSRS_Ports_PerResource_n2:
          nrofSRS_Ports = 2;
          break;
        case NR_SRS_Resources__maxNumberSRS_Ports_PerResource_n4:
          nrofSRS_Ports = 4;
          break;
        default:
          LOG_E(NR_RRC,
                "Max Number of SRS Ports Per Resource %ld is invalid!\n",
                ul_feature_setup->supportedSRS_Resources->maxNumberSRS_Ports_PerResource);
      }
      nrofSRS_Ports = min(nrofSRS_Ports, maxMIMO_Layers);
      switch (nrofSRS_Ports) {
        case 1:
          srs_res->nrofSRS_Ports = NR_SRS_Resource__nrofSRS_Ports_port1;
          break;
        case 2:
          srs_res->nrofSRS_Ports = NR_SRS_Resource__nrofSRS_Ports_ports2;
          break;
        case 4:
          srs_res->nrofSRS_Ports = NR_SRS_Resource__nrofSRS_Ports_ports4;
          break;
        default:
          LOG_E(NR_RRC,
                "Number of SRS Ports Per Resource %ld is invalid!\n",
                ul_feature_setup->supportedSRS_Resources->maxNumberSRS_Ports_PerResource);
      }
    }
    LOG_I(NR_RRC, "SRS configured with %d ports\n", 1 << srs_res->nrofSRS_Ports);
  }
  srs_res->ptrs_PortIndex = NULL;
  srs_res->transmissionComb.present = tx_comb;
  switch (tx_comb) {
    case NR_SRS_Resource__transmissionComb_PR_n2:
      srs_res->transmissionComb.choice.n2 = calloc_or_fail(1, sizeof(*srs_res->transmissionComb.choice.n2));
      srs_res->transmissionComb.choice.n2->combOffset_n2 = 0;
      srs_res->transmissionComb.choice.n2->cyclicShift_n2 = 0;
      break;
    case NR_SRS_Resource__transmissionComb_PR_n4:
      srs_res->transmissionComb.choice.n4 = calloc_or_fail(1, sizeof(*srs_res->transmissionComb.choice.n4));
      srs_res->transmissionComb.choice.n4->combOffset_n4 = 0;
      srs_res->transmissionComb.choice.n4->cyclicShift_n4 = 0;
      break;
    default:
      AssertFatal(1 == 0, "Invalid transmission comb %d\n", tx_comb);
  }
  srs_res->resourceMapping.startPosition = 1;
  srs_res->resourceMapping.nrofSymbols = NR_SRS_Resource__resourceMapping__nrofSymbols_n1;
  srs_res->resourceMapping.repetitionFactor = NR_SRS_Resource__resourceMapping__repetitionFactor_n1;
  srs_res->freqDomainPosition = 0;
  srs_res->freqDomainShift = 0;
  srs_res->freqHopping.b_SRS = 0;
  srs_res->freqHopping.b_hop = 0;
  srs_res->freqHopping.c_SRS = rrc_get_max_nr_csrs(curr_bwp, srs_res->freqHopping.b_SRS);
  srs_res->groupOrSequenceHopping = NR_SRS_Resource__groupOrSequenceHopping_neither;
  if (do_srs) {
    srs_res->resourceType.present = NR_SRS_Resource__resourceType_PR_periodic;
    srs_res->resourceType.choice.periodic = configure_periodic_srs(uid);
  } else {
    srs_res->resourceType.present = NR_SRS_Resource__resourceType_PR_aperiodic;
    srs_res->resourceType.choice.aperiodic = calloc_or_fail(1, sizeof(*srs_res->resourceType.choice.aperiodic));
  }
  srs_res->sequenceId = 40;
  srs_res->spatialRelationInfo = calloc_or_fail(1, sizeof(*srs_res->spatialRelationInfo));
  srs_res->spatialRelationInfo->servingCellId = NULL;
  // TODO include CSI as reference signal when BWPs are handled properly
  srs_res->spatialRelationInfo->referenceSignal.present = NR_SRS_SpatialRelationInfo__referenceSignal_PR_ssb_Index;
  srs_res->spatialRelationInfo->referenceSignal.choice.ssb_Index = 0;
  return srs_res;
}

static NR_SetupRelease_SRS_Config_t *get_config_srs(const NR_UE_NR_Capability_t *uecap,
                                                    const int curr_bwp,
                                                    const int uid,
                                                    const int res_id,
                                                    const long maxMIMO_Layers,
                                                    const int minRXTXTIME,
                                                    int do_srs)
{
  NR_SetupRelease_SRS_Config_t *setup_release_srs_Config = calloc_or_fail(1, sizeof(*setup_release_srs_Config));
  setup_release_srs_Config->present = NR_SetupRelease_SRS_Config_PR_setup;
  setup_release_srs_Config->choice.setup = calloc_or_fail(1, sizeof(*setup_release_srs_Config->choice.setup));
  NR_SRS_Config_t *srs_Config = setup_release_srs_Config->choice.setup;

  srs_Config->srs_ResourceToAddModList = calloc_or_fail(1, sizeof(*srs_Config->srs_ResourceToAddModList));
  NR_SRS_Resource_t *srs_res0 =
      get_srs_resource(uecap, curr_bwp, uid, res_id, maxMIMO_Layers, NR_SRS_Resource__transmissionComb_PR_n2, do_srs);
  asn1cSeqAdd(&srs_Config->srs_ResourceToAddModList->list, srs_res0);

  srs_Config->srs_ResourceSetToAddModList = calloc_or_fail(1, sizeof(*srs_Config->srs_ResourceSetToAddModList));
  NR_SRS_ResourceSet_t *srs_resset0 = get_srs_resourceset(res_id, res_id, NR_SRS_ResourceSet__usage_codebook, minRXTXTIME, do_srs);
  asn1cSeqAdd(&srs_Config->srs_ResourceSetToAddModList->list, srs_resset0);

  srs_Config->srs_ResourceSetToReleaseList = NULL;
  srs_Config->srs_ResourceToReleaseList = NULL;

  return setup_release_srs_Config;
}

static int get_SupportedBandwidth_fr1_bw_index(int bw_index)
{
  int val = -1;

  switch (bw_index) {
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz5:
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz10:
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz15:
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz20:
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz25:
      val = bw_index;
      break;
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz30:
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz35:
      val = NR_SupportedBandwidth__fr1_mhz30;
      break;
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz40:
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz45:
      val = NR_SupportedBandwidth__fr1_mhz40;
      break;
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz50:
      val = NR_SupportedBandwidth__fr1_mhz50;
      break;
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz60:
      val = NR_SupportedBandwidth__fr1_mhz60;
      break;
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz70:
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz80:
      val = NR_SupportedBandwidth__fr1_mhz80;
      break;
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz90:
    case NR_SupportedBandwidth_v1700__fr1_r17_mhz100:
      val = NR_SupportedBandwidth__fr1_mhz100;
      break;
    default:
      AssertFatal(1==0, "Invalid bw index\n");
  }

  return val;
}


void prepare_sim_uecap(NR_UE_NR_Capability_t *cap,
                       NR_ServingCellConfigCommon_t *scc,
                       int numerology,
                       int rbsize,
                       int mcs_table_dl,
                       int mcs_table_ul)
{
  NR_Phy_Parameters_t *phy_Parameters = &cap->phy_Parameters;
  int band = *scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0];
  NR_BandNR_t *nr_bandnr = calloc(1, sizeof(NR_BandNR_t));
  nr_bandnr->bandNR = band;
  asn1cSeqAdd(&cap->rf_Parameters.supportedBandListNR.list,
                   nr_bandnr);
  NR_BandNR_t *bandNRinfo = cap->rf_Parameters.supportedBandListNR.list.array[0];

  if (mcs_table_ul == 1) {
    bandNRinfo->pusch_256QAM = calloc(1,sizeof(*bandNRinfo->pusch_256QAM));
    *bandNRinfo->pusch_256QAM = NR_BandNR__pusch_256QAM_supported;
  }
  if (mcs_table_dl == 1) {
    const frequency_range_t freq_range = get_freq_range_from_band(band);
    if (freq_range == FR2) {
      bandNRinfo->pdsch_256QAM_FR2 = calloc(1, sizeof(*bandNRinfo->pdsch_256QAM_FR2));
      *bandNRinfo->pdsch_256QAM_FR2 = NR_BandNR__pdsch_256QAM_FR2_supported;
    }
    else{
      phy_Parameters->phy_ParametersFR1 = calloc(1, sizeof(*phy_Parameters->phy_ParametersFR1));
      NR_Phy_ParametersFR1_t *phy_fr1 = phy_Parameters->phy_ParametersFR1;
      phy_fr1->pdsch_256QAM_FR1 = calloc(1, sizeof(*phy_fr1->pdsch_256QAM_FR1));
      *phy_fr1->pdsch_256QAM_FR1 = NR_Phy_ParametersFR1__pdsch_256QAM_FR1_supported;
    }
    cap->featureSets = calloc(1, sizeof(*cap->featureSets));
    NR_FeatureSets_t *fs=cap->featureSets;
    fs->featureSetsDownlinkPerCC = calloc(1, sizeof(*fs->featureSetsDownlinkPerCC));
    NR_FeatureSetDownlinkPerCC_t *fs_cc = calloc(1, sizeof(*fs_cc));
    fs_cc->supportedSubcarrierSpacingDL = numerology;
    int bw_index = get_supported_band_index(numerology, freq_range, rbsize);
    int bw = get_supported_bw_mhz(freq_range, bw_index);
    if (bw == 90) // 90MHz
      fs_cc->channelBW_90mhz = calloc(1, sizeof(*fs_cc->channelBW_90mhz));
    if(freq_range == FR2) {
      fs_cc->supportedBandwidthDL.present = NR_SupportedBandwidth_PR_fr2;
      fs_cc->supportedBandwidthDL.choice.fr2 = bw_index;
    }
    else{
      fs_cc->supportedBandwidthDL.present = NR_SupportedBandwidth_PR_fr1;
      fs_cc->supportedBandwidthDL.choice.fr1 = get_SupportedBandwidth_fr1_bw_index(bw_index);
    }
    fs_cc->maxNumberMIMO_LayersPDSCH = calloc(1, sizeof(*fs_cc->maxNumberMIMO_LayersPDSCH));
    *fs_cc->maxNumberMIMO_LayersPDSCH = NR_MIMO_LayersDL_fourLayers;
    fs_cc->supportedModulationOrderDL = calloc(1, sizeof(*fs_cc->supportedModulationOrderDL));
    *fs_cc->supportedModulationOrderDL = NR_ModulationOrder_qam256;
    asn1cSeqAdd(&fs->featureSetsDownlinkPerCC->list, fs_cc);

    if (bw == 35 || bw == 45 || bw == 70) {
      fs->ext6 = calloc(1, sizeof(*fs->ext6));
      fs->ext6->featureSetsDownlinkPerCC_v1700 = calloc(1, sizeof(*fs->ext6->featureSetsDownlinkPerCC_v1700));
      NR_FeatureSetDownlinkPerCC_v1700_t *fs_dlcc_v1700 = calloc(1, sizeof(*fs_dlcc_v1700));
      fs_dlcc_v1700->supportedBandwidthDL_v1710 = calloc(1, sizeof(*fs_dlcc_v1700->supportedBandwidthDL_v1710));
      fs_dlcc_v1700->supportedBandwidthDL_v1710->present = NR_SupportedBandwidth_v1700_PR_fr1_r17;
      fs_dlcc_v1700->supportedBandwidthDL_v1710->choice.fr1_r17 = bw_index;
      asn1cSeqAdd(&fs->ext6->featureSetsDownlinkPerCC_v1700->list, fs_dlcc_v1700);
    }
  }

  phy_Parameters->phy_ParametersFRX_Diff = calloc(1, sizeof(*phy_Parameters->phy_ParametersFRX_Diff));
  phy_Parameters->phy_ParametersFRX_Diff->pucch_F0_2WithoutFH = NULL;

  if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
    xer_fprint(stdout, &asn_DEF_NR_UE_NR_Capability, cap);
  }
}

void nr_rrc_config_dl_tda(struct NR_PDSCH_TimeDomainResourceAllocationList *pdsch_TimeDomainAllocationList,
                          frame_type_t frame_type,
                          NR_TDD_UL_DL_ConfigCommon_t *tdd_UL_DL_ConfigurationCommon,
                          int curr_bwp)
{
  // coreset duration setting to be improved in the framework of RRC harmonization, potentially using a common function
  int len_coreset = 1;
  if (curr_bwp < 48)
    len_coreset = 2;
  // setting default TDA for DL with TDA index 0
  struct NR_PDSCH_TimeDomainResourceAllocation *timedomainresourceallocation = CALLOC(1,sizeof(NR_PDSCH_TimeDomainResourceAllocation_t));
  // k0: Slot offset between DCI and its scheduled PDSCH (see TS 38.214 clause 5.1.2.1) When the field is absent the UE applies the value 0.
  //timedomainresourceallocation->k0 = calloc(1,sizeof(*timedomainresourceallocation->k0));
  //*timedomainresourceallocation->k0 = 0;
  timedomainresourceallocation->mappingType = NR_PDSCH_TimeDomainResourceAllocation__mappingType_typeA;
  timedomainresourceallocation->startSymbolAndLength = get_SLIV(len_coreset,14-len_coreset); // basic slot configuration starting in symbol 1 til the end of the slot
  asn1cSeqAdd(&pdsch_TimeDomainAllocationList->list, timedomainresourceallocation);
  // setting TDA for CSI-RS symbol with index 1
  struct NR_PDSCH_TimeDomainResourceAllocation *timedomainresourceallocation1 = CALLOC(1,sizeof(NR_PDSCH_TimeDomainResourceAllocation_t));
  timedomainresourceallocation1->mappingType = NR_PDSCH_TimeDomainResourceAllocation__mappingType_typeA;
  timedomainresourceallocation1->startSymbolAndLength = get_SLIV(len_coreset,14-len_coreset-1); // 1 symbol CSI-RS
  asn1cSeqAdd(&pdsch_TimeDomainAllocationList->list, timedomainresourceallocation1);
  if(frame_type==TDD) {
    // TDD
    if(tdd_UL_DL_ConfigurationCommon) {
      int dl_symb = 0;
      if (tdd_UL_DL_ConfigurationCommon->pattern2 && tdd_UL_DL_ConfigurationCommon->pattern2->nrofDownlinkSymbols)
        AssertFatal(tdd_UL_DL_ConfigurationCommon->pattern2->nrofDownlinkSymbols == tdd_UL_DL_ConfigurationCommon->pattern1.nrofDownlinkSymbols,
                    "nrofDownlinkSymbols in pattern1 %ld and pattern2 %ld must be the same in current implementation\n",
                    tdd_UL_DL_ConfigurationCommon->pattern2->nrofDownlinkSymbols,
                    tdd_UL_DL_ConfigurationCommon->pattern1.nrofDownlinkSymbols);
      if (tdd_UL_DL_ConfigurationCommon->pattern1.nrofDownlinkSymbols != 0) {
        dl_symb = tdd_UL_DL_ConfigurationCommon->pattern1.nrofDownlinkSymbols;
      } else if (tdd_UL_DL_ConfigurationCommon->pattern2) {
        dl_symb = tdd_UL_DL_ConfigurationCommon->pattern2->nrofDownlinkSymbols;
      }
      if(dl_symb > 1) {
        // mixed slot TDA with TDA index 2
        struct NR_PDSCH_TimeDomainResourceAllocation *timedomainresourceallocation2 = CALLOC(1,sizeof(NR_PDSCH_TimeDomainResourceAllocation_t));
        timedomainresourceallocation2->mappingType = NR_PDSCH_TimeDomainResourceAllocation__mappingType_typeA;
        timedomainresourceallocation2->startSymbolAndLength = get_SLIV(len_coreset,dl_symb-len_coreset); // mixed slot configuration starting in symbol 1 til the end of the dl allocation
        asn1cSeqAdd(&pdsch_TimeDomainAllocationList->list, timedomainresourceallocation2);
      }
    }
  }
}

static struct NR_PUSCH_TimeDomainResourceAllocation *set_TimeDomainResourceAllocation(const int k2, long sliv)
{
  struct NR_PUSCH_TimeDomainResourceAllocation *puschTdrAlloc = calloc_or_fail(1, sizeof(*puschTdrAlloc));
  puschTdrAlloc->k2 = calloc_or_fail(1, sizeof(*puschTdrAlloc->k2));
  *puschTdrAlloc->k2 = k2;
  puschTdrAlloc->mappingType = NR_PUSCH_TimeDomainResourceAllocation__mappingType_typeB;
  puschTdrAlloc->startSymbolAndLength = sliv;
  return puschTdrAlloc;
}

static int tda_cmp(const void *tda_a, const void *tda_b)
{
  const NR_PUSCH_TimeDomainResourceAllocation_t *a = *(const NR_PUSCH_TimeDomainResourceAllocation_t **)tda_a;
  const NR_PUSCH_TimeDomainResourceAllocation_t *b = *(const NR_PUSCH_TimeDomainResourceAllocation_t **)tda_b;
  DevAssert(a->k2 && b->k2);
  if (*a->k2 < *b->k2)
    return -1; // smaller first
  if (*a->k2 > *b->k2)
    return 1;
  // same k2: order from big to small TDA number
  int as, al, bs, bl;
  SLIV2SL(a->startSymbolAndLength, &as, &al);
  SLIV2SL(b->startSymbolAndLength, &bs, &bl);
  if (al < bl)
    return 1; // bigger first
  if (al > bl)
    return -1;
  return 0;
}

/* \brief Set up a list of time domain allocations as suitable for the TDD
 * pattern. This will be used by get_num_ul_tda(), which requires a specific
 * ordering, hence we qsort() the list at the end according to tda_cmp(). */
void nr_rrc_config_ul_tda(NR_ServingCellConfigCommon_t *scc, int min_fb_delay, int do_SRS)
{
  NR_PUSCH_TimeDomainResourceAllocationList_t *tda_list =
      scc->uplinkConfigCommon->initialUplinkBWP->pusch_ConfigCommon->choice.setup->pusch_TimeDomainAllocationList;
  AssertFatal(tda_list->list.count == 0, "already have pusch_TimeDomainAllocationList members\n");

  const int k2 = min_fb_delay;
  const int mu = scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.subcarrierSpacing;

  // UL TDA index 0 is basic slot configuration starting in symbol 0 til the last but one symbol
  NR_PUSCH_TimeDomainResourceAllocation_t *tda;
  tda = set_TimeDomainResourceAllocation(k2, get_SLIV(0, 13));
  asn1cSeqAdd(&tda_list->list, tda);

  // UL TDA index 1 in case of SRS
  if (do_SRS) {
    tda = set_TimeDomainResourceAllocation(k2, get_SLIV(0, 12));
    asn1cSeqAdd(&tda_list->list, tda);
  }

  if (scc->tdd_UL_DL_ConfigurationCommon) {
    NR_TDD_UL_DL_Pattern_t *p1 = &scc->tdd_UL_DL_ConfigurationCommon->pattern1;
    int ul_symb = p1->nrofUplinkSymbols;
    NR_TDD_UL_DL_Pattern_t *p2 = scc->tdd_UL_DL_ConfigurationCommon->pattern2;
    if (p2 && p2->nrofUplinkSymbols)
      AssertFatal(p2->nrofUplinkSymbols == ul_symb,
                  "nrofDownlinkSymbols in pattern1 %d and pattern2 %ld must be the same in current implementation\n",
                  ul_symb,
                  p2->nrofUplinkSymbols);
    AssertFatal(!p2 || p2->nrofDownlinkSlots >= p2->nrofUplinkSlots,
                "nrofDownlinkSlots must be larger than nrofUplinkSlots for pattern2 in current implementation\n");

    int N_dl1 = p1->nrofDownlinkSlots;
    int N_ul1 = p1->nrofUplinkSlots;
    int N_dl2 = p2 ? p2->nrofDownlinkSlots : 0;
    int N_ul2 = p2 ? p2->nrofUplinkSlots : 0;
    int tdd_period_idx = get_tdd_period_idx(scc->tdd_UL_DL_ConfigurationCommon);
    int nb_periods_per_frame = get_nb_periods_per_frame(tdd_period_idx);
    int nb_slots_per_period = ((1 << mu) * 10) / nb_periods_per_frame;

    // make TDA for the mixed slot
    long mixed_sliv = get_SLIV(NR_SYMBOLS_PER_SLOT - ul_symb, ul_symb - 1);
    int mixed_min_ul_symb = 3; // need at least two symbols PUSCH + PUCCH
    bool has_ul_mixed = ul_symb >= mixed_min_ul_symb;
    if (has_ul_mixed && (k2 <= N_dl1 || (p2 && N_ul2 == 0))) {
      // UL TDA index 2 for mixed slot (TDD) (no pattern 2 and enough DL slots, or pattern2 only DL)
      tda = set_TimeDomainResourceAllocation(k2, mixed_sliv); // to be reached from suitable DL slot
      asn1cSeqAdd(&tda_list->list, tda);
    } else if (has_ul_mixed && !p2 && k2 > N_dl1) { // k2 > N_Dl1
      // UL TDA for mixed slot without pattern 2 and less DL slots than k2
      tda = set_TimeDomainResourceAllocation(nb_slots_per_period, mixed_sliv); // to be reached from last DL (mixed) slot, if any
      asn1cSeqAdd(&tda_list->list, tda);
    } else if (has_ul_mixed && N_ul2 > 0 && k2 > N_dl1) {
      // we have pattern 2 with UL, and less p1 DL slots than k2
      tda = set_TimeDomainResourceAllocation(N_dl1 + N_ul2 + 1, mixed_sliv);
      asn1cSeqAdd(&tda_list->list, tda);
    } else {
      LOG_I(NR_RRC, "mixed slot has %d UL symbols, cannot create mixed slot TDA\n", ul_symb);
    }

    // make TDA for UL slots that are not reachable within k2/min_rxtxtime
    int N_ul = max(N_ul1, N_ul2);
    if (N_ul > k2) {
      // enforce that with k2, we could reach all but one slot (which is mixed
      // and taken into account above
      AssertFatal(N_dl1 >= k2 - 1, "cannot fulfil TDD pattern: N_dl1 %d, k2 %d\n", N_dl1, k2);
      for (int i = k2 + 1; i <= N_ul; ++i) {
        tda = set_TimeDomainResourceAllocation(i, get_SLIV(0, 13));
        asn1cSeqAdd(&tda_list->list, tda);
        if (do_SRS) {
          tda = set_TimeDomainResourceAllocation(i, get_SLIV(0, 12));
          asn1cSeqAdd(&tda_list->list, tda);
        }
      }
    }

    // for Msg3, an additional get_delta_for_k2(mu) is added to k2.
    // check that any UL slot is reachable under this condition
    // the below is designed to only hit specific (known) cases
    // examples: DDDSU (no TDA in S),
    int N_dl = max(N_dl1, N_dl2);
    int delta = get_delta_for_k2(mu);
    int k2_msg3 = k2 + delta;
    if (nb_slots_per_period % k2_msg3 == 0 /* see example above */
        && k2_msg3 > N_dl /* otherwise, can always have a D that reaches U */
        && k2_msg3 > N_ul /* otherwise, can always have a D that reaches U */
        && !has_ul_mixed /* if mixed slot, even for DDDSU would reach */) {
      /* reach next UL from mixed slot in previous period */
      tda = set_TimeDomainResourceAllocation(k2_msg3, get_SLIV(0, 13));
      asn1cSeqAdd(&tda_list->list, tda);
    }
  }

  AssertFatal(tda_list->list.count <= 16, "cannot reach all UL slots with current TDD configuration\n");
  qsort(tda_list->list.array, tda_list->list.count, sizeof(tda_list->list.array), tda_cmp);
}

static void set_dl_DataToUL_ACK(NR_PUCCH_Config_t *pucch_Config, int min_feedback_time)
{
  pucch_Config->dl_DataToUL_ACK = calloc(1,sizeof(*pucch_Config->dl_DataToUL_ACK));
  long *delay[8];
  for (int i = 0; i < 8; i++) {
    int curr_delay = i + min_feedback_time;
    // dl-DataToUL-ACK SEQUENCE (SIZE (1..8)) OF INTEGER (0..15)
    if (curr_delay > 15)
      break;
    delay[i] = calloc(1,sizeof(*delay[i]));
    *delay[i] = curr_delay;
    asn1cSeqAdd(&pucch_Config->dl_DataToUL_ACK->list,delay[i]);
  }
  AssertFatal(pucch_Config->dl_DataToUL_ACK->list.count > 0,
              "Minimum feedback time too high %d, cannot set any dl_DataToUL_ACK value\n",
              min_feedback_time);
}

// PUCCH resource set 0 for configuration with O_uci <= 2 bits and/or a positive or negative SR (section 9.2.1 of 38.213)
static void config_pucch_resset0(const NR_ServingCellConfigCommon_t *scc,
                                 NR_PUCCH_Config_t *pucch_Config,
                                 int uid,
                                 int curr_bwp,
                                 const NR_UE_NR_Capability_t *uecap,
                                 const nr_pdsch_AntennaPorts_t *ap)
{
  NR_PUCCH_ResourceSet_t *pucchresset = calloc(1,sizeof(*pucchresset));
  pucchresset->pucch_ResourceSetId = 0;
  NR_PUCCH_ResourceId_t *pucchid = calloc(1,sizeof(*pucchid));
  *pucchid = 0;
  asn1cSeqAdd(&pucchresset->resourceList.list,pucchid);
  pucchresset->maxPayloadSize = NULL;

  if(uecap) {
    long *pucch_F0_2WithoutFH = uecap->phy_Parameters.phy_ParametersFRX_Diff->pucch_F0_2WithoutFH;
    AssertFatal(pucch_F0_2WithoutFH == NULL,"UE does not support PUCCH F0 without frequency hopping. Current configuration is without FH\n");
  }

  int pucch2_size = get_pucch2_size(ap->N1 * ap->N2 * ap->XP);
  NR_PUCCH_Resource_t *pucchres0 = calloc(1,sizeof(*pucchres0));
  pucchres0->pucch_ResourceId = *pucchid;
  int num_pucch2 = get_nb_pucch2_per_slot(scc, curr_bwp, ap);
  pucchres0->startingPRB = (pucch2_size * num_pucch2) + uid;
  // checked for validity in verify_radio_configuration
  AssertFatal(pucchres0->startingPRB < curr_bwp, "Not enough resources in current BWP (size %d) to allocate uid %d\n", curr_bwp, uid);
  pucchres0->intraSlotFrequencyHopping = NULL;
  pucchres0->secondHopPRB = NULL;
  pucchres0->format.present = NR_PUCCH_Resource__format_PR_format0;
  pucchres0->format.choice.format0 = calloc(1,sizeof(*pucchres0->format.choice.format0));
  pucchres0->format.choice.format0->initialCyclicShift = 0;
  pucchres0->format.choice.format0->nrofSymbols = 1;
  pucchres0->format.choice.format0->startingSymbolIndex = 13;
  asn1cSeqAdd(&pucch_Config->resourceToAddModList->list,pucchres0);

  asn1cSeqAdd(&pucch_Config->resourceSetToAddModList->list,pucchresset);
}


// PUCCH resource set 1 for configuration with O_uci > 2 bits (currently format2)
static void config_pucch_resset1(const NR_ServingCellConfigCommon_t *scc,
                                 NR_PUCCH_Config_t *pucch_Config,
                                 int uid,
                                 int curr_bwp,
                                 const NR_UE_NR_Capability_t *uecap,
                                 const nr_pdsch_AntennaPorts_t *ap)
{
  NR_PUCCH_ResourceSet_t *pucchresset=calloc(1,sizeof(*pucchresset));
  pucchresset->pucch_ResourceSetId = 1;
  NR_PUCCH_ResourceId_t *pucchressetid=calloc(1,sizeof(*pucchressetid));
  *pucchressetid = 2;
  asn1cSeqAdd(&pucchresset->resourceList.list,pucchressetid);
  pucchresset->maxPayloadSize = NULL;

  if(uecap) {
    long *pucch_F0_2WithoutFH = uecap->phy_Parameters.phy_ParametersFRX_Diff->pucch_F0_2WithoutFH;
    AssertFatal(pucch_F0_2WithoutFH == NULL,"UE does not support PUCCH F2 without frequency hopping. Current configuration is without FH\n");
  }

  int pucch2_size = get_pucch2_size(ap->N1 * ap->N2 * ap->XP);
  NR_PUCCH_Resource_t *pucchres2 = calloc(1,sizeof(*pucchres2));
  pucchres2->pucch_ResourceId = *pucchressetid;
  int num_pucch2 = get_nb_pucch2_per_slot(scc, curr_bwp, ap);
  pucchres2->startingPRB = pucch2_size * (uid % num_pucch2);
  pucchres2->intraSlotFrequencyHopping = NULL;
  pucchres2->secondHopPRB = NULL;
  pucchres2->format.present = NR_PUCCH_Resource__format_PR_format2;
  pucchres2->format.choice.format2 = calloc(1,sizeof(*pucchres2->format.choice.format2));
  pucchres2->format.choice.format2->nrofPRBs = pucch2_size;
  pucchres2->format.choice.format2->nrofSymbols = 1;
  pucchres2->format.choice.format2->startingSymbolIndex = 13;
  asn1cSeqAdd(&pucch_Config->resourceToAddModList->list,pucchres2);

  asn1cSeqAdd(&pucch_Config->resourceSetToAddModList->list,pucchresset);

  pucch_Config->format2 = calloc(1,sizeof(*pucch_Config->format2));
  pucch_Config->format2->present = NR_SetupRelease_PUCCH_FormatConfig_PR_setup;
  NR_PUCCH_FormatConfig_t *pucchfmt2 = calloc(1,sizeof(*pucchfmt2));
  pucch_Config->format2->choice.setup = pucchfmt2;
  pucchfmt2->interslotFrequencyHopping = NULL;
  pucchfmt2->additionalDMRS = NULL;
  pucchfmt2->maxCodeRate = calloc(1,sizeof(*pucchfmt2->maxCodeRate));
  *pucchfmt2->maxCodeRate = NR_PUCCH_MaxCodeRate_zeroDot15;
  pucchfmt2->nrofSlots = NULL;
  pucchfmt2->pi2BPSK = NULL;

  // to check UE capabilities for that in principle
  pucchfmt2->simultaneousHARQ_ACK_CSI = calloc(1,sizeof(*pucchfmt2->simultaneousHARQ_ACK_CSI));
  *pucchfmt2->simultaneousHARQ_ACK_CSI = NR_PUCCH_FormatConfig__simultaneousHARQ_ACK_CSI_true;
}

static void set_pucch_power_config(NR_PUCCH_Config_t *pucch_Config)
{
  pucch_Config->pucch_PowerControl = calloc(1,sizeof(*pucch_Config->pucch_PowerControl));
  NR_P0_PUCCH_t *p00 = calloc(1,sizeof(*p00));
  p00->p0_PUCCH_Id = 1;
  p00->p0_PUCCH_Value = 0;
  pucch_Config->pucch_PowerControl->p0_Set = calloc(1,sizeof(*pucch_Config->pucch_PowerControl->p0_Set));
  asn1cSeqAdd(&pucch_Config->pucch_PowerControl->p0_Set->list,p00);

  pucch_Config->pucch_PowerControl->pathlossReferenceRSs = calloc(1,sizeof(*pucch_Config->pucch_PowerControl->pathlossReferenceRSs));
  struct NR_PUCCH_PathlossReferenceRS *PL_ref_RS = calloc(1,sizeof(*PL_ref_RS));
  PL_ref_RS->pucch_PathlossReferenceRS_Id = 0;
  // TODO include CSI as reference signal when BWPs are handled properly
  PL_ref_RS->referenceSignal.present = NR_PUCCH_PathlossReferenceRS__referenceSignal_PR_ssb_Index;
  PL_ref_RS->referenceSignal.choice.ssb_Index = 0;
  asn1cSeqAdd(&pucch_Config->pucch_PowerControl->pathlossReferenceRSs->list,PL_ref_RS);

  pucch_Config->pucch_PowerControl->deltaF_PUCCH_f0 = calloc(1,sizeof(*pucch_Config->pucch_PowerControl->deltaF_PUCCH_f0));
  *pucch_Config->pucch_PowerControl->deltaF_PUCCH_f0 = 0;
  pucch_Config->pucch_PowerControl->deltaF_PUCCH_f2 = calloc(1,sizeof(*pucch_Config->pucch_PowerControl->deltaF_PUCCH_f2));
  *pucch_Config->pucch_PowerControl->deltaF_PUCCH_f2 = 0;

  pucch_Config->spatialRelationInfoToAddModList = calloc(1,sizeof(*pucch_Config->spatialRelationInfoToAddModList));
  pucch_Config->spatialRelationInfoToReleaseList=NULL;
  NR_PUCCH_SpatialRelationInfo_t *pucchspatial = calloc(1,sizeof(*pucchspatial));
  pucchspatial->pucch_SpatialRelationInfoId = 1;
  pucchspatial->servingCellId = NULL;
  // TODO include CSI as reference signal when BWPs are handled properly
  pucchspatial->referenceSignal.present = NR_PUCCH_SpatialRelationInfo__referenceSignal_PR_ssb_Index;
  pucchspatial->referenceSignal.choice.ssb_Index = 0;

  pucchspatial->pucch_PathlossReferenceRS_Id = PL_ref_RS->pucch_PathlossReferenceRS_Id;
  pucchspatial->p0_PUCCH_Id = p00->p0_PUCCH_Id;
  pucchspatial->closedLoopIndex = NR_PUCCH_SpatialRelationInfo__closedLoopIndex_i0;
  asn1cSeqAdd(&pucch_Config->spatialRelationInfoToAddModList->list,pucchspatial);
}

static void set_SR_periodandoffset(NR_SchedulingRequestResourceConfig_t *schedulingRequestResourceConfig, int scs)
{
  const frame_structure_t *fs = &RC.nrmac[0]->frame_structure;
  int sr_slot = 1; // in FDD SR in slot 1
  if (fs->frame_type == TDD)
    sr_slot = get_first_ul_slot(fs, true);

  schedulingRequestResourceConfig->periodicityAndOffset = calloc(1,sizeof(*schedulingRequestResourceConfig->periodicityAndOffset));

  if(sr_slot < 10 && scs < NR_SubcarrierSpacing_kHz60){
    schedulingRequestResourceConfig->periodicityAndOffset->present = NR_SchedulingRequestResourceConfig__periodicityAndOffset_PR_sl10;
    schedulingRequestResourceConfig->periodicityAndOffset->choice.sl10 = sr_slot;
    return;
  }
  else if(sr_slot < 20 && scs < NR_SubcarrierSpacing_kHz120){
    schedulingRequestResourceConfig->periodicityAndOffset->present = NR_SchedulingRequestResourceConfig__periodicityAndOffset_PR_sl20;
    schedulingRequestResourceConfig->periodicityAndOffset->choice.sl20 = sr_slot;
    return;
  }
  else if(sr_slot < 40){
    schedulingRequestResourceConfig->periodicityAndOffset->present = NR_SchedulingRequestResourceConfig__periodicityAndOffset_PR_sl40;
    schedulingRequestResourceConfig->periodicityAndOffset->choice.sl40 = sr_slot;
    return;
  }
  else if(sr_slot < 80 || scs == NR_SubcarrierSpacing_kHz15){
    schedulingRequestResourceConfig->periodicityAndOffset->present = NR_SchedulingRequestResourceConfig__periodicityAndOffset_PR_sl80;
    schedulingRequestResourceConfig->periodicityAndOffset->choice.sl80 = sr_slot;
    return;
  }
  else if(sr_slot < 160 || scs == NR_SubcarrierSpacing_kHz30){
    schedulingRequestResourceConfig->periodicityAndOffset->present = NR_SchedulingRequestResourceConfig__periodicityAndOffset_PR_sl160;
    schedulingRequestResourceConfig->periodicityAndOffset->choice.sl160 = sr_slot;
    return;
  }
  else if(sr_slot < 320 || scs == NR_SubcarrierSpacing_kHz60){
    schedulingRequestResourceConfig->periodicityAndOffset->present = NR_SchedulingRequestResourceConfig__periodicityAndOffset_PR_sl320;
    schedulingRequestResourceConfig->periodicityAndOffset->choice.sl320 = sr_slot;
    return;
  }
  else {
    schedulingRequestResourceConfig->periodicityAndOffset->present = NR_SchedulingRequestResourceConfig__periodicityAndOffset_PR_sl640;
    schedulingRequestResourceConfig->periodicityAndOffset->choice.sl640 = sr_slot;
  }
}

static void scheduling_request_config(NR_PUCCH_Config_t *pucch_Config, int scs)
{
  // format with <=2 bits in pucch resource set 0
  NR_PUCCH_ResourceSet_t *pucchresset = pucch_Config->resourceSetToAddModList->list.array[0];
  // assigning the 1st pucch resource in the set to scheduling request
  NR_PUCCH_ResourceId_t *pucchressetid = pucchresset->resourceList.list.array[0];

  pucch_Config->schedulingRequestResourceToAddModList = calloc(1,sizeof(*pucch_Config->schedulingRequestResourceToAddModList));
  NR_SchedulingRequestResourceConfig_t *schedulingRequestResourceConfig = calloc(1,sizeof(*schedulingRequestResourceConfig));
  schedulingRequestResourceConfig->schedulingRequestResourceId = 1;
  schedulingRequestResourceConfig->schedulingRequestID = 0;

  set_SR_periodandoffset(schedulingRequestResourceConfig, scs);

  schedulingRequestResourceConfig->resource = calloc(1,sizeof(*schedulingRequestResourceConfig->resource));
  *schedulingRequestResourceConfig->resource = *pucchressetid;
  asn1cSeqAdd(&pucch_Config->schedulingRequestResourceToAddModList->list,schedulingRequestResourceConfig);
}

static void set_ul_mcs_table(const NR_UE_NR_Capability_t *cap,
                             const NR_ServingCellConfigCommon_t *scc,
                             NR_PUSCH_Config_t *pusch_Config)
{

  if (cap == NULL){
    pusch_Config->mcs_Table = NULL;
    return;
  }

  int band;
  if (scc->uplinkConfigCommon->frequencyInfoUL->frequencyBandList)
    band = *scc->uplinkConfigCommon->frequencyInfoUL->frequencyBandList->list.array[0];
  else
    band = *scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0];
  bool supported = false;
  for (int i=0;i<cap->rf_Parameters.supportedBandListNR.list.count;i++) {
    NR_BandNR_t *bandNRinfo = cap->rf_Parameters.supportedBandListNR.list.array[i];
    if(bandNRinfo->bandNR == band && bandNRinfo->pusch_256QAM) {
      supported = true;
      break;
    }
  }
  if (supported) {
    if(pusch_Config->transformPrecoder == NULL ||
       *pusch_Config->transformPrecoder == NR_PUSCH_Config__transformPrecoder_disabled) {
      if(pusch_Config->mcs_Table == NULL)
        pusch_Config->mcs_Table = calloc(1, sizeof(*pusch_Config->mcs_Table));
      *pusch_Config->mcs_Table = NR_PUSCH_Config__mcs_Table_qam256;
    }
    else {
      if(pusch_Config->mcs_TableTransformPrecoder == NULL)
        pusch_Config->mcs_TableTransformPrecoder = calloc(1, sizeof(*pusch_Config->mcs_TableTransformPrecoder));
      *pusch_Config->mcs_TableTransformPrecoder = NR_PUSCH_Config__mcs_TableTransformPrecoder_qam256;
    }
  }
  else {
    pusch_Config->mcs_Table = NULL;
    pusch_Config->mcs_TableTransformPrecoder = NULL;
  }
}

static void set_dl_mcs_table(int scs,
                             const NR_UE_NR_Capability_t *cap,
                             NR_BWP_DownlinkDedicated_t *bwp_Dedicated,
                             const NR_ServingCellConfigCommon_t *scc)
{

  if (cap == NULL){
    bwp_Dedicated->pdsch_Config->choice.setup->mcs_Table = NULL;
    return;
  }

  int band = *scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0];
  struct NR_FrequencyInfoDL__scs_SpecificCarrierList scs_list = scc->downlinkConfigCommon->frequencyInfoDL->scs_SpecificCarrierList;
  int bw_rb = -1;
  for(int i = 0; i < scs_list.list.count; i++){
    if(scs == scs_list.list.array[i]->subcarrierSpacing){
      bw_rb = scs_list.list.array[i]->carrierBandwidth;
      break;
    }
  }
  AssertFatal(bw_rb > 0,"Could not find scs-SpecificCarrierList element for scs %d", scs);

  bool supported = false;
  const frequency_range_t freq_range = get_freq_range_from_band(band);
  if (freq_range == FR2) {
    for (int i = 0; i < cap->rf_Parameters.supportedBandListNR.list.count; i++) {
      NR_BandNR_t *bandNRinfo = cap->rf_Parameters.supportedBandListNR.list.array[i];
      if(bandNRinfo->bandNR == band && bandNRinfo->pdsch_256QAM_FR2) {
        supported = true;
        break;
      }
    }
  }
  else if (cap->phy_Parameters.phy_ParametersFR1 && cap->phy_Parameters.phy_ParametersFR1->pdsch_256QAM_FR1)
    supported = true;

  if (supported) {
    if(bwp_Dedicated->pdsch_Config->choice.setup->mcs_Table == NULL)
      bwp_Dedicated->pdsch_Config->choice.setup->mcs_Table = calloc(1, sizeof(*bwp_Dedicated->pdsch_Config->choice.setup->mcs_Table));
    *bwp_Dedicated->pdsch_Config->choice.setup->mcs_Table = NR_PDSCH_Config__mcs_Table_qam256;
  }
  else
    bwp_Dedicated->pdsch_Config->choice.setup->mcs_Table = NULL;
}

static NR_PTRS_UplinkConfig_t *config_ulptrs(const nr_ptrs_config_t *ptrs)
{
  struct NR_PTRS_UplinkConfig__transformPrecoderDisabled *ptrs_config = calloc_or_fail(1, sizeof(*ptrs_config));
  AssertFatal(ptrs->ul_FreqDensity0_0 < 277, "Invalid ul_FreqDensity0_0 %d\n", ptrs->ul_FreqDensity0_0);
  if (ptrs->ul_FreqDensity0_0 > 0) {
    ptrs_config->frequencyDensity = calloc_or_fail(1, sizeof(*ptrs_config->frequencyDensity));
    asn1cSequenceAdd(ptrs_config->frequencyDensity->list, long, f0);
    *f0 = ptrs->ul_FreqDensity0_0;
    AssertFatal(ptrs->ul_FreqDensity1_0 < 277, "Invalid ul_FreqDensity1_0 %d\n", ptrs->ul_FreqDensity1_0);
    if (ptrs->ul_FreqDensity1_0 > 0) {
      asn1cSequenceAdd(ptrs_config->frequencyDensity->list, long, f1);
      *f1 = ptrs->ul_FreqDensity1_0;
    }
  }

  AssertFatal(ptrs->ul_TimeDensity0_0 < 30, "Invalid ul_TimeDensity0_0 %d\n", ptrs->ul_TimeDensity0_0);
  if (ptrs->ul_TimeDensity0_0 >= 0) {
    ptrs_config->timeDensity = calloc_or_fail(1, sizeof(*ptrs_config->timeDensity));
    asn1cSequenceAdd(ptrs_config->timeDensity->list, long, t0);
    *t0 = ptrs->ul_TimeDensity0_0;
    AssertFatal(ptrs->ul_TimeDensity1_0 < 30, "Invalid ul_TimeDensity1_0 %d\n", ptrs->ul_TimeDensity1_0);
    if (ptrs->ul_TimeDensity1_0 >= 0) {
      asn1cSequenceAdd(ptrs_config->timeDensity->list, long, t1);
      *t1 = ptrs->ul_TimeDensity1_0;
    }
    AssertFatal(ptrs->ul_TimeDensity2_0 < 30, "Invalid ul_TimeDensity2_0 %d\n", ptrs->ul_TimeDensity2_0);
    if (ptrs->ul_TimeDensity2_0 >= 0) {
      asn1cSequenceAdd(ptrs_config->timeDensity->list, long, t2);
      *t2 = ptrs->ul_TimeDensity2_0;
    }
  }

  AssertFatal(ptrs->ul_ReOffset_0 < 3, "Invalid ul_ReOffset_0 %d\n", ptrs->ul_ReOffset_0);
  if (ptrs->ul_ReOffset_0 >= 0) {
    ptrs_config->resourceElementOffset = calloc_or_fail(1, sizeof(*ptrs_config->resourceElementOffset));
    *ptrs_config->resourceElementOffset = ptrs->ul_ReOffset_0;
  }
  AssertFatal(ptrs->ul_MaxPorts_0 == 0 || ptrs->ul_MaxPorts_0 == 1, "Invalid ul_MaxPorts_0 %d\n", ptrs->ul_MaxPorts_0);
  ptrs_config->maxNrofPorts = ptrs->ul_MaxPorts_0;
  AssertFatal(ptrs->ul_Power_0 >= 0 || ptrs->ul_Power_0 < 4, "Invalid ul_Power_0 %d\n", ptrs->ul_Power_0);
  ptrs_config->ptrs_Power = ptrs->ul_Power_0;

  NR_PTRS_UplinkConfig_t *ulptrs = calloc_or_fail(1, sizeof(*ptrs_config));
  ulptrs->transformPrecoderDisabled = ptrs_config;
  return ulptrs;
}

long ue_supported_ul_layers(const NR_UE_NR_Capability_t *uecap)
{
  long ul_max_layers = 1;
  if (uecap
      && uecap->featureSets
      && uecap->featureSets->featureSetsUplinkPerCC
      && uecap->featureSets->featureSetsUplinkPerCC->list.count > 0) {
    NR_FeatureSetUplinkPerCC_t *ul_feature_setup_per_cc = uecap->featureSets->featureSetsUplinkPerCC->list.array[0];
    if (ul_feature_setup_per_cc->mimo_CB_PUSCH->maxNumberMIMO_LayersCB_PUSCH) {
      switch (*ul_feature_setup_per_cc->mimo_CB_PUSCH->maxNumberMIMO_LayersCB_PUSCH) {
        case NR_MIMO_LayersUL_twoLayers:
          ul_max_layers = 2;
          break;
        case NR_MIMO_LayersUL_fourLayers:
          ul_max_layers = 4;
          break;
        default:
          ul_max_layers = 1;
      }
    }
  }
  return ul_max_layers;
}

static long set_ul_max_layers(const nr_mac_config_t *configuration, const NR_UE_NR_Capability_t *uecap)
{
  return min(ue_supported_ul_layers(uecap), configuration->pusch_AntennaPorts);
}

static NR_SetupRelease_PUSCH_Config_t *config_pusch(const nr_mac_config_t *configuration,
                                                    const NR_ServingCellConfigCommon_t *scc,
                                                    const NR_UE_NR_Capability_t *uecap)
{
  NR_SetupRelease_PUSCH_Config_t *setup_puschconfig = calloc(1, sizeof(*setup_puschconfig));
  setup_puschconfig->present = NR_SetupRelease_PUSCH_Config_PR_setup;
  NR_PUSCH_Config_t *pusch_Config = calloc(1, sizeof(*pusch_Config));
  setup_puschconfig->choice.setup = pusch_Config;

  pusch_Config->dataScramblingIdentityPUSCH = NULL;
  if (!pusch_Config->txConfig)
    pusch_Config->txConfig = calloc(1, sizeof(*pusch_Config->txConfig));
  *pusch_Config->txConfig = NR_PUSCH_Config__txConfig_codebook;
  pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA = NULL;
  if (!pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB)
    pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB = calloc(1, sizeof(*pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB));
  pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->present = NR_SetupRelease_DMRS_UplinkConfig_PR_setup;
  if (!pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup)
    pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup = calloc(1, sizeof(*pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup));
  NR_DMRS_UplinkConfig_t *NR_DMRS_UplinkConfig = pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup;
  if (configuration->ptrs) {
    NR_PTRS_UplinkConfig_t *ptrs_config = config_ulptrs(configuration->ptrs);
    NR_SetupRelease_PTRS_UplinkConfig_t *phaseTrackingRS = calloc(1, sizeof(*phaseTrackingRS));
    phaseTrackingRS->present = NR_SetupRelease_PTRS_UplinkConfig_PR_setup;
    phaseTrackingRS->choice.setup = ptrs_config;
    NR_DMRS_UplinkConfig->phaseTrackingRS = phaseTrackingRS;
  }
  NR_DMRS_UplinkConfig->dmrs_Type = NULL;
  NR_DMRS_UplinkConfig->dmrs_AdditionalPosition = NULL;
  NR_DMRS_UplinkConfig->phaseTrackingRS = NULL;
  NR_DMRS_UplinkConfig->maxLength = NULL;
  if (!NR_DMRS_UplinkConfig->transformPrecodingDisabled)
    NR_DMRS_UplinkConfig->transformPrecodingDisabled = calloc(1, sizeof(*NR_DMRS_UplinkConfig->transformPrecodingDisabled));
  NR_DMRS_UplinkConfig->transformPrecodingDisabled->scramblingID0 = NULL;
  NR_DMRS_UplinkConfig->transformPrecodingDisabled->scramblingID1 = NULL;
  if (!NR_DMRS_UplinkConfig->transformPrecodingEnabled)
    NR_DMRS_UplinkConfig->transformPrecodingEnabled = calloc(1, sizeof(*NR_DMRS_UplinkConfig->transformPrecodingEnabled));
  NR_DMRS_UplinkConfig->transformPrecodingEnabled->nPUSCH_Identity = NULL;
  NR_DMRS_UplinkConfig->transformPrecodingEnabled->sequenceHopping = NULL;
  NR_DMRS_UplinkConfig->transformPrecodingEnabled->sequenceGroupHopping = NULL;
  if (!pusch_Config->pusch_PowerControl)
    pusch_Config->pusch_PowerControl = calloc(1, sizeof(*pusch_Config->pusch_PowerControl));
  pusch_Config->pusch_PowerControl->tpc_Accumulation = NULL;
  if (!pusch_Config->pusch_PowerControl->msg3_Alpha)
    pusch_Config->pusch_PowerControl->msg3_Alpha = calloc(1, sizeof(*pusch_Config->pusch_PowerControl->msg3_Alpha));
  *pusch_Config->pusch_PowerControl->msg3_Alpha = NR_Alpha_alpha1;
  pusch_Config->pusch_PowerControl->p0_NominalWithoutGrant = NULL;
  pusch_Config->pusch_PowerControl->p0_AlphaSets = calloc(1, sizeof(*pusch_Config->pusch_PowerControl->p0_AlphaSets));
  NR_P0_PUSCH_AlphaSet_t *aset = calloc(1, sizeof(*aset));
  aset->p0_PUSCH_AlphaSetId = 0;
  aset->p0 = calloc(1, sizeof(*aset->p0));
  *aset->p0 = 0;
  aset->alpha = calloc(1, sizeof(*aset->alpha));
  *aset->alpha = NR_Alpha_alpha1;
  asn1cSeqAdd(&pusch_Config->pusch_PowerControl->p0_AlphaSets->list, aset);
  pusch_Config->pusch_PowerControl->pathlossReferenceRSToAddModList = calloc(1, sizeof(*pusch_Config->pusch_PowerControl->pathlossReferenceRSToAddModList));
  NR_PUSCH_PathlossReferenceRS_t *plrefRS = calloc(1, sizeof(*plrefRS));
  plrefRS->pusch_PathlossReferenceRS_Id = 0;
  plrefRS->referenceSignal.present = NR_PUSCH_PathlossReferenceRS__referenceSignal_PR_ssb_Index;
  plrefRS->referenceSignal.choice.ssb_Index = 0;
  asn1cSeqAdd(&pusch_Config->pusch_PowerControl->pathlossReferenceRSToAddModList->list, plrefRS);
  pusch_Config->pusch_PowerControl->pathlossReferenceRSToReleaseList = NULL;
  pusch_Config->pusch_PowerControl->twoPUSCH_PC_AdjustmentStates = NULL;
  if (configuration->use_deltaMCS) {
    if (!pusch_Config->pusch_PowerControl->deltaMCS)
      pusch_Config->pusch_PowerControl->deltaMCS = calloc(1, sizeof(*pusch_Config->pusch_PowerControl->deltaMCS));
    *pusch_Config->pusch_PowerControl->deltaMCS = NR_PUSCH_PowerControl__deltaMCS_enabled;
  }
  else free(pusch_Config->pusch_PowerControl->deltaMCS);
  pusch_Config->pusch_PowerControl->sri_PUSCH_MappingToAddModList = NULL;
  pusch_Config->pusch_PowerControl->sri_PUSCH_MappingToReleaseList = NULL;
  pusch_Config->frequencyHopping = NULL;
  pusch_Config->frequencyHoppingOffsetLists = NULL;
  pusch_Config->resourceAllocation = NR_PUSCH_Config__resourceAllocation_resourceAllocationType1;
  pusch_Config->pusch_TimeDomainAllocationList = NULL;
  pusch_Config->pusch_AggregationFactor = NULL;
  set_ul_mcs_table(configuration->force_UL256qam_off ? NULL : uecap, scc, pusch_Config);
  pusch_Config->transformPrecoder = NULL;
  if (!pusch_Config->codebookSubset)
    pusch_Config->codebookSubset = calloc(1, sizeof(*pusch_Config->codebookSubset));
  *pusch_Config->codebookSubset = NR_PUSCH_Config__codebookSubset_nonCoherent;
  if (!pusch_Config->maxRank)
    pusch_Config->maxRank = calloc(1, sizeof(*pusch_Config->maxRank));
  *pusch_Config->maxRank = set_ul_max_layers(configuration, uecap);
  pusch_Config->rbg_Size = NULL;
  pusch_Config->uci_OnPUSCH = NULL;
  pusch_Config->tp_pi2BPSK = NULL;

  return setup_puschconfig;
}

static NR_PTRS_DownlinkConfig_t *config_dlptrs(const nr_ptrs_config_t *ptrs)
{
  NR_PTRS_DownlinkConfig_t *ptrs_config = calloc_or_fail(1, sizeof(*ptrs_config));
  AssertFatal(ptrs->dl_FreqDensity0_0 < 277, "Invalid dl_FreqDensity0_0 %d\n", ptrs->dl_FreqDensity0_0);
  if (ptrs->dl_FreqDensity0_0 > 0) {
    ptrs_config->frequencyDensity = calloc_or_fail(1, sizeof(*ptrs_config->frequencyDensity));
    asn1cSequenceAdd(ptrs_config->frequencyDensity->list, long, f0);
    *f0 = ptrs->dl_FreqDensity0_0;
    AssertFatal(ptrs->dl_FreqDensity1_0 < 277, "Invalid dl_FreqDensity1_0 %d\n", ptrs->dl_FreqDensity1_0);
    if (ptrs->dl_FreqDensity1_0 > 0) {
      asn1cSequenceAdd(ptrs_config->frequencyDensity->list, long, f1);
      *f1 = ptrs->dl_FreqDensity1_0;
    }
  }

  AssertFatal(ptrs->dl_TimeDensity0_0 < 30, "Invalid dl_TimeDensity0_0 %d\n", ptrs->dl_TimeDensity0_0);
  if (ptrs->dl_TimeDensity0_0 >= 0) {
    ptrs_config->timeDensity = calloc_or_fail(1, sizeof(*ptrs_config->timeDensity));
    asn1cSequenceAdd(ptrs_config->timeDensity->list, long, t0);
    *t0 = ptrs->dl_TimeDensity0_0;
    AssertFatal(ptrs->dl_TimeDensity1_0 < 30, "Invalid dl_TimeDensity1_0 %d\n", ptrs->dl_TimeDensity1_0);
    if (ptrs->dl_TimeDensity1_0 >= 0) {
      asn1cSequenceAdd(ptrs_config->timeDensity->list, long, t1);
      *t1 = ptrs->dl_TimeDensity1_0;
    }
    AssertFatal(ptrs->dl_TimeDensity2_0 < 30, "Invalid dl_TimeDensity2_0 %d\n", ptrs->dl_TimeDensity2_0);
    if (ptrs->dl_TimeDensity2_0 >= 0) {
      asn1cSequenceAdd(ptrs_config->timeDensity->list, long, t2);
      *t2 = ptrs->dl_TimeDensity2_0;
    }
  }

  AssertFatal(ptrs->dl_EpreRatio_0 < 4, "Invalid dl_EpreRatio_0 %d\n", ptrs->dl_EpreRatio_0);
  if (ptrs->dl_EpreRatio_0 >= 0) {
    ptrs_config->epre_Ratio = calloc_or_fail(1, sizeof(*ptrs_config->epre_Ratio));
    *ptrs_config->epre_Ratio = ptrs->dl_EpreRatio_0;
  }
  AssertFatal(ptrs->dl_ReOffset_0 < 3, "Invalid dl_ReOffset_0 %d\n", ptrs->dl_ReOffset_0);
  if (ptrs->dl_ReOffset_0 >= 0) {
    ptrs_config->resourceElementOffset = calloc_or_fail(1, sizeof(*ptrs_config->resourceElementOffset));
    *ptrs_config->resourceElementOffset = ptrs->dl_ReOffset_0;
  }
  return ptrs_config;
}

static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap, int bwp_Id, bool do_TCI, nr_ptrs_config_t *ptrs)
{
  NR_SetupRelease_PDSCH_Config_t *setup_pdsch_Config = calloc(1,sizeof(*setup_pdsch_Config));
  setup_pdsch_Config->present = NR_SetupRelease_PDSCH_Config_PR_setup;
  NR_PDSCH_Config_t *pdsch_Config = calloc(1, sizeof(*pdsch_Config));
  setup_pdsch_Config->choice.setup = pdsch_Config;
  pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA = calloc(1, sizeof(*pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA));
  pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->present = NR_SetupRelease_DMRS_DownlinkConfig_PR_setup;
  pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup = calloc(1, sizeof(*pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup));
  NR_DMRS_DownlinkConfig_t *dmrs_DownlinkForPDSCH_MappingTypeA = pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
  if (ptrs) {
    NR_PTRS_DownlinkConfig_t *ptrs_config = config_dlptrs(ptrs);
    NR_SetupRelease_PTRS_DownlinkConfig_t *phaseTrackingRS = calloc(1, sizeof(*phaseTrackingRS));
    phaseTrackingRS->present = NR_SetupRelease_PTRS_DownlinkConfig_PR_setup;
    phaseTrackingRS->choice.setup = ptrs_config;
    dmrs_DownlinkForPDSCH_MappingTypeA->phaseTrackingRS = phaseTrackingRS;
  }
  dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_Type = NULL;
  dmrs_DownlinkForPDSCH_MappingTypeA->maxLength = NULL;
  dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID0 = NULL;
  dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID1 = NULL;
  dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = calloc(1, sizeof(*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition));
  // TODO possible improvement is to select based on some input additional position
  *dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = NR_DMRS_DownlinkConfig__dmrs_AdditionalPosition_pos1;

  pdsch_Config->dataScramblingIdentityPDSCH = NULL;
  pdsch_Config->resourceAllocation = NR_PDSCH_Config__resourceAllocation_resourceAllocationType1;

  pdsch_Config->prb_BundlingType.present = NR_PDSCH_Config__prb_BundlingType_PR_staticBundling;
  pdsch_Config->prb_BundlingType.choice.staticBundling = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling));
  pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize));
  *pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = NR_PDSCH_Config__prb_BundlingType__staticBundling__bundleSize_wideband;

  if (do_TCI) {
    int n_ssb = 0;
    if (!pdsch_Config->tci_StatesToAddModList)
      pdsch_Config->tci_StatesToAddModList=calloc(1,sizeof(*pdsch_Config->tci_StatesToAddModList));
    for (int i = 0; i < 64; i++) {
      if (((ssb_bitmap >> (63 - i)) & 0x01) == 0)
        continue;
      asn1cSequenceAdd(pdsch_Config->tci_StatesToAddModList->list, NR_TCI_State_t, tcid);
      tcid->tci_StateId = n_ssb++;
      tcid->qcl_Type1.cell = NULL;
      asn1cCallocOne(tcid->qcl_Type1.bwp_Id, bwp_Id);
      tcid->qcl_Type1.referenceSignal.present = NR_QCL_Info__referenceSignal_PR_ssb;
      tcid->qcl_Type1.referenceSignal.choice.ssb = i;
      tcid->qcl_Type1.qcl_Type = NR_QCL_Info__qcl_Type_typeC;
    }
  }
  return setup_pdsch_Config;
}

static NR_BWP_Downlink_t *config_downlinkBWP(const NR_ServingCellConfigCommon_t *scc,
                                             const NR_UE_NR_Capability_t *uecap,
                                             bool force_256qam_off,
                                             bool is_SA,
                                             const nr_mac_config_t *configuration)
{
  NR_BWP_Downlink_t *bwp = calloc_or_fail(1, sizeof(*bwp));
  bwp->bwp_Id = 1;
  bwp->bwp_Common = calloc(1,sizeof(*bwp->bwp_Common));
  if(configuration->num_additional_bwps > 0 && configuration->first_active_bwp > 0) {
    const nr_bwp_config_t *bwp_config = &configuration->bwp_config[configuration->first_active_bwp - 1];
    bwp->bwp_Common->genericParameters.locationAndBandwidth = bwp_config->location_and_bw;
    bwp->bwp_Common->genericParameters.subcarrierSpacing = bwp_config->scs;
    bwp->bwp_Common->genericParameters.cyclicPrefix = NULL;
  } else {
    bwp->bwp_Common->genericParameters.locationAndBandwidth = PRBalloc_to_locationandbandwidth(scc->downlinkConfigCommon->frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth,0);
    bwp->bwp_Common->genericParameters.subcarrierSpacing = scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.subcarrierSpacing;
    bwp->bwp_Common->genericParameters.cyclicPrefix = scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.cyclicPrefix;
  }

  bwp->bwp_Common->pdcch_ConfigCommon=calloc(1,sizeof(*bwp->bwp_Common->pdcch_ConfigCommon));
  bwp->bwp_Common->pdcch_ConfigCommon->present = NR_SetupRelease_PDCCH_ConfigCommon_PR_setup;
  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup = calloc(1,sizeof(*bwp->bwp_Common->pdcch_ConfigCommon->choice.setup));
  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->controlResourceSetZero = NULL;

  int bwp_size = NRRIV2BW(bwp->bwp_Common->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  int bwp_start = NRRIV2PRBOFFSET(bwp->bwp_Common->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  uint64_t ssb_bitmap = get_ssb_bitmap(scc);
  NR_ControlResourceSet_t *coreset = get_coreset_config(bwp->bwp_Id, 0, 0, bwp_start, bwp_size, ssb_bitmap, configuration->do_TCI);
  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->commonControlResourceSet = coreset;

  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->searchSpaceZero=NULL;
  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList=NULL;
  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList=calloc(1,sizeof(*bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList));

  int searchspaceid = 5 + bwp->bwp_Id;
  int agg_level_candidates[NUM_PDCCH_AGG_LEVELS];
  NR_SearchSpace_t *ss = NULL;
  if (!is_SA) {
    int num_cces = get_coreset_num_cces(coreset->frequencyDomainResources.buf, coreset->duration);
    verify_agg_levels(num_cces, configuration->num_agg_level_candidates, coreset->controlResourceSetId, agg_level_candidates);
    ss = rrc_searchspace_config(true, searchspaceid, coreset->controlResourceSetId, agg_level_candidates);
  } else {
    agg_level_candidates[PDCCH_AGG_LEVEL1] = NR_SearchSpace__nrofCandidates__aggregationLevel1_n0;
    agg_level_candidates[PDCCH_AGG_LEVEL2] = NR_SearchSpace__nrofCandidates__aggregationLevel2_n0;
    agg_level_candidates[PDCCH_AGG_LEVEL4] = NR_SearchSpace__nrofCandidates__aggregationLevel4_n1;
    agg_level_candidates[PDCCH_AGG_LEVEL8] = NR_SearchSpace__nrofCandidates__aggregationLevel8_n0;
    agg_level_candidates[PDCCH_AGG_LEVEL16] = NR_SearchSpace__nrofCandidates__aggregationLevel16_n0;
    ss = rrc_searchspace_config(true, searchspaceid, coreset->controlResourceSetId, agg_level_candidates);
  }
  asn1cSeqAdd(&bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list, ss);

  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->searchSpaceSIB1=NULL;
  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->searchSpaceOtherSystemInformation=NULL;
  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->pagingSearchSpace=NULL;
  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->ra_SearchSpace=NULL;
  if(!is_SA) {
    bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->ra_SearchSpace=calloc(1,sizeof(*bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->ra_SearchSpace));
    *bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->ra_SearchSpace=ss->searchSpaceId;
  }
  bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->ext1=NULL;
  bwp->bwp_Common->pdsch_ConfigCommon=calloc(1,sizeof(*bwp->bwp_Common->pdsch_ConfigCommon));
  bwp->bwp_Common->pdsch_ConfigCommon->present = NR_SetupRelease_PDSCH_ConfigCommon_PR_setup;
  bwp->bwp_Common->pdsch_ConfigCommon->choice.setup = calloc(1,sizeof(*bwp->bwp_Common->pdsch_ConfigCommon->choice.setup));
  bwp->bwp_Common->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList = calloc(1,sizeof(*bwp->bwp_Common->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList));

  nr_rrc_config_dl_tda(bwp->bwp_Common->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList,
                       get_frame_type((int)*scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0], *scc->ssbSubcarrierSpacing),
                       scc->tdd_UL_DL_ConfigurationCommon,
                       bwp_size);

  if (!bwp->bwp_Dedicated) {
    bwp->bwp_Dedicated=calloc(1,sizeof(*bwp->bwp_Dedicated));
  }
  bwp->bwp_Dedicated->pdcch_Config=calloc(1,sizeof(*bwp->bwp_Dedicated->pdcch_Config));
  bwp->bwp_Dedicated->pdcch_Config->present = NR_SetupRelease_PDCCH_Config_PR_setup;
  bwp->bwp_Dedicated->pdcch_Config->choice.setup = calloc(1,sizeof(*bwp->bwp_Dedicated->pdcch_Config->choice.setup));
  bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList = calloc(1,sizeof(*bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList));
  bwp->bwp_Dedicated->pdcch_Config->choice.setup->controlResourceSetToAddModList = calloc(1,sizeof(*bwp->bwp_Dedicated->pdcch_Config->choice.setup->controlResourceSetToAddModList));

  // coreset2 is identical to coreset above, but reallocated to prevent double frees
  NR_ControlResourceSet_t *coreset2 = get_coreset_config(bwp->bwp_Id, 0, 0, bwp_start, bwp_size, ssb_bitmap, configuration->do_TCI);
  asn1cSeqAdd(&bwp->bwp_Dedicated->pdcch_Config->choice.setup->controlResourceSetToAddModList->list, coreset2);
  int rrc_num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS];
  int num_cces = get_coreset_num_cces(coreset2->frequencyDomainResources.buf, coreset2->duration);
  verify_agg_levels(num_cces, configuration->num_agg_level_candidates, coreset2->controlResourceSetId, rrc_num_agg_level_candidates);

  searchspaceid = 10 + bwp->bwp_Id;
  NR_SearchSpace_t *ss2 = rrc_searchspace_config(true, searchspaceid, coreset2->controlResourceSetId, agg_level_candidates);
  asn1cSeqAdd(&bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList->list, ss2);

  searchspaceid = 20 + bwp->bwp_Id;
  NR_SearchSpace_t *ss3 =
      rrc_searchspace_config(false, searchspaceid, coreset2->controlResourceSetId, rrc_num_agg_level_candidates);
  asn1cSeqAdd(&bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList->list, ss3);

  bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToReleaseList = NULL;
  bwp->bwp_Dedicated->pdsch_Config = config_pdsch(ssb_bitmap, bwp->bwp_Id, configuration->do_TCI, configuration->ptrs);

  set_dl_mcs_table(bwp->bwp_Common->genericParameters.subcarrierSpacing,
                   force_256qam_off ? NULL : uecap,
                   bwp->bwp_Dedicated,
                   scc);
  return bwp;
}

static NR_BWP_Uplink_t *config_uplinkBWP(bool is_SA,
                                         int uid,
                                         int maxMIMO_Layers,
                                         const nr_mac_config_t *configuration,
                                         const NR_ServingCellConfigCommon_t *scc,
                                         const NR_UE_NR_Capability_t *uecap)
{
  NR_BWP_Uplink_t *ubwp = calloc_or_fail(1, sizeof(*ubwp));
  ubwp->bwp_Id = 1;
  ubwp->bwp_Common = calloc(1, sizeof(*ubwp->bwp_Common));
  if(configuration->num_additional_bwps > 0 && configuration->first_active_bwp > 0) {
    int bwp_idx = configuration->first_active_bwp - 1;
    ubwp->bwp_Common->genericParameters.locationAndBandwidth = configuration->bwp_config[bwp_idx].location_and_bw;
    ubwp->bwp_Common->genericParameters.subcarrierSpacing = configuration->bwp_config[bwp_idx].scs;
    ubwp->bwp_Common->genericParameters.cyclicPrefix = NULL;
  } else {
    ubwp->bwp_Common->genericParameters.locationAndBandwidth = PRBalloc_to_locationandbandwidth(scc->uplinkConfigCommon->frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth,0);
    ubwp->bwp_Common->genericParameters.subcarrierSpacing = scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.subcarrierSpacing;
    ubwp->bwp_Common->genericParameters.cyclicPrefix = scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.cyclicPrefix;
  }

  int curr_bwp = NRRIV2BW(ubwp->bwp_Common->genericParameters.locationAndBandwidth,MAX_BWP_SIZE);
  ubwp->bwp_Common->rach_ConfigCommon  = is_SA ? NULL : clone_rach_configcommon(scc->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon);
  ubwp->bwp_Common->pusch_ConfigCommon = clone_pusch_configcommon(scc->uplinkConfigCommon->initialUplinkBWP->pusch_ConfigCommon);
  ubwp->bwp_Common->pucch_ConfigCommon = CALLOC(1,sizeof(struct NR_SetupRelease_PUCCH_ConfigCommon));
  ubwp->bwp_Common->pucch_ConfigCommon->present= NR_SetupRelease_PUCCH_ConfigCommon_PR_setup;
  ubwp->bwp_Common->pucch_ConfigCommon->choice.setup = CALLOC(1,sizeof(struct NR_PUCCH_ConfigCommon));
  struct NR_PUCCH_ConfigCommon *pucch_ConfigCommon = ubwp->bwp_Common->pucch_ConfigCommon->choice.setup;
  pucch_ConfigCommon->pucch_ResourceCommon = NULL; // for BWP != 0 as per 38.213 section 9.2.1
  pucch_ConfigCommon->pucch_GroupHopping = scc->uplinkConfigCommon->initialUplinkBWP->pucch_ConfigCommon->choice.setup->pucch_GroupHopping;
  asn1cCallocOne(pucch_ConfigCommon->hoppingId, *scc->uplinkConfigCommon->initialUplinkBWP->pucch_ConfigCommon->choice.setup->hoppingId);
  asn1cCallocOne(pucch_ConfigCommon->p0_nominal, *scc->uplinkConfigCommon->initialUplinkBWP->pucch_ConfigCommon->choice.setup->p0_nominal);

  if (!ubwp->bwp_Dedicated) {
    ubwp->bwp_Dedicated = calloc(1,sizeof(*ubwp->bwp_Dedicated));
  }

  ubwp->bwp_Dedicated->pucch_Config = calloc(1,sizeof(*ubwp->bwp_Dedicated->pucch_Config));
  ubwp->bwp_Dedicated->pucch_Config->present = NR_SetupRelease_PUCCH_Config_PR_setup;
  NR_PUCCH_Config_t *pucch_Config = calloc(1,sizeof(*pucch_Config));
  ubwp->bwp_Dedicated->pucch_Config->choice.setup = pucch_Config;
  pucch_Config->resourceSetToAddModList = calloc(1,sizeof(*pucch_Config->resourceSetToAddModList));
  pucch_Config->resourceSetToReleaseList = NULL;
  pucch_Config->resourceToAddModList = calloc(1,sizeof(*pucch_Config->resourceToAddModList));
  pucch_Config->resourceToReleaseList = NULL;
  config_pucch_resset0(scc, pucch_Config, uid, curr_bwp, uecap, &configuration->pdsch_AntennaPorts);
  config_pucch_resset1(scc, pucch_Config, uid, curr_bwp, uecap, &configuration->pdsch_AntennaPorts);
  set_pucch_power_config(pucch_Config);
  scheduling_request_config(pucch_Config, ubwp->bwp_Common->genericParameters.subcarrierSpacing);
  set_dl_DataToUL_ACK(pucch_Config, configuration->minRXTXTIME);

  ubwp->bwp_Dedicated->pusch_Config = config_pusch(configuration, scc, uecap);

  ubwp->bwp_Dedicated->srs_Config = get_config_srs(NULL,
                                                   curr_bwp,
                                                   uid,
                                                   ubwp->bwp_Id,
                                                   maxMIMO_Layers,
                                                   configuration->minRXTXTIME,
                                                   configuration->do_SRS);

  ubwp->bwp_Dedicated->configuredGrantConfig = NULL;
  ubwp->bwp_Dedicated->beamFailureRecoveryConfig = NULL;
  return ubwp;
}

static void set_phr_config(NR_MAC_CellGroupConfig_t *mac_CellGroupConfig)
{
  mac_CellGroupConfig->phr_Config                                         = calloc(1, sizeof(*mac_CellGroupConfig->phr_Config));
  mac_CellGroupConfig->phr_Config->present                                = NR_SetupRelease_PHR_Config_PR_setup;
  mac_CellGroupConfig->phr_Config->choice.setup                           = calloc(1, sizeof(*mac_CellGroupConfig->phr_Config->choice.setup));
  mac_CellGroupConfig->phr_Config->choice.setup->phr_PeriodicTimer        = NR_PHR_Config__phr_PeriodicTimer_sf10;
  mac_CellGroupConfig->phr_Config->choice.setup->phr_ProhibitTimer        = NR_PHR_Config__phr_ProhibitTimer_sf10;
  mac_CellGroupConfig->phr_Config->choice.setup->phr_Tx_PowerFactorChange = NR_PHR_Config__phr_Tx_PowerFactorChange_dB1;
}

static void set_csi_meas_periodicity(const NR_ServingCellConfigCommon_t *scc,
                                     NR_CSI_ReportConfig_t *csirep,
                                     int uid,
                                     int curr_bwp,
                                     const nr_pdsch_AntennaPorts_t *antennaports,
                                     bool is_rsrp)
{
  const int ideal_period = set_ideal_period(true);
  const int num_pucch2 = get_nb_pucch2_per_slot(scc, curr_bwp, antennaports);
  const int idx = (uid * 2 / num_pucch2) + is_rsrp;
  frame_structure_t *fs = &RC.nrmac[0]->frame_structure;
  int offset = get_ul_slot_offset(fs, idx, true);
  LOG_D(NR_MAC, "set_csi_meas_periodicity: uid = %d, offset = %d, ideal_period = %d", uid, offset, ideal_period);
  // checked for validity in verify_radio_configuration
  AssertFatal(offset < 320, "Not enough UL slots to accomodate all possible UEs. Need to rework the implementation\n");
  if (check_periodicity(4, ideal_period, fs)) {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots4;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots4 = offset;
  } else if (check_periodicity(5, ideal_period, fs)) {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots5;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots5 = offset;
  } else if (check_periodicity(8, ideal_period, fs)) {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots8;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots8 = offset;
  } else if (check_periodicity(10, ideal_period, fs)) {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots10;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots10 = offset;
  } else if (check_periodicity(16, ideal_period, fs)) {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots16;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots16 = offset;
  } else if (check_periodicity(20, ideal_period, fs)) {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots20;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots20 = offset;
  } else if (check_periodicity(40, ideal_period, fs)) {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots40;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots40 = offset;
  } else if (check_periodicity(80, ideal_period, fs)) {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots80;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots80 = offset;
  } else if (check_periodicity(160, ideal_period, fs)) {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots160;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots160 = offset;
  } else {
    csirep->reportConfigType.choice.periodic->reportSlotConfig.present = NR_CSI_ReportPeriodicityAndOffset_PR_slots320;
    csirep->reportConfigType.choice.periodic->reportSlotConfig.choice.slots320 = offset;
  }
}

static void config_csi_codebook(const nr_pdsch_AntennaPorts_t *antennaports,
                                const int max_layers,
                                struct NR_CodebookConfig *codebookConfig)
{
  const int num_ant_ports = antennaports->N1 * antennaports->N2 * antennaports->XP;
  codebookConfig->codebookType.present = NR_CodebookConfig__codebookType_PR_type1;
  if(!codebookConfig->codebookType.choice.type1)
    codebookConfig->codebookType.choice.type1 = calloc(1, sizeof(*codebookConfig->codebookType.choice.type1));
  // Single panel configuration
  codebookConfig->codebookType.choice.type1->subType.present = NR_CodebookConfig__codebookType__type1__subType_PR_typeI_SinglePanel;
  if(!codebookConfig->codebookType.choice.type1->subType.choice.typeI_SinglePanel)
    codebookConfig->codebookType.choice.type1->subType.choice.typeI_SinglePanel = calloc(1, sizeof(*codebookConfig->codebookType.choice.type1->subType.choice.typeI_SinglePanel));
  struct NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel *singlePanelConfig = codebookConfig->codebookType.choice.type1->subType.choice.typeI_SinglePanel;
  singlePanelConfig->typeI_SinglePanel_ri_Restriction.size = 1;
  singlePanelConfig->typeI_SinglePanel_ri_Restriction.bits_unused = 0;
  singlePanelConfig->typeI_SinglePanel_ri_Restriction.buf = malloc(1);
  singlePanelConfig->typeI_SinglePanel_ri_Restriction.buf[0] = (1 << max_layers) - 1; // max_layers bit set to 1
  if (num_ant_ports == 2) {
    singlePanelConfig->nrOfAntennaPorts.present = NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts_PR_two;
    if(!singlePanelConfig->nrOfAntennaPorts.choice.two) {
      asn1cCalloc(singlePanelConfig->nrOfAntennaPorts.choice.two, two);
      two->twoTX_CodebookSubsetRestriction.size = 1;
      two->twoTX_CodebookSubsetRestriction.bits_unused = 2;
      asn1cCallocOne(two->twoTX_CodebookSubsetRestriction.buf, 0xfc); // no restriction (all 6 bits enabled)
    }
  } else {
    singlePanelConfig->nrOfAntennaPorts.present = NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts_PR_moreThanTwo;
    if(!singlePanelConfig->nrOfAntennaPorts.choice.moreThanTwo) {
      singlePanelConfig->nrOfAntennaPorts.choice.moreThanTwo = calloc(1, sizeof(*singlePanelConfig->nrOfAntennaPorts.choice.moreThanTwo));
      struct NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts__moreThanTwo *moreThanTwo = singlePanelConfig->nrOfAntennaPorts.choice.moreThanTwo;
      switch (num_ant_ports) {
        case 4:
          moreThanTwo->n1_n2.present = NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts__moreThanTwo__n1_n2_PR_two_one_TypeI_SinglePanel_Restriction;
          moreThanTwo->n1_n2.choice.two_one_TypeI_SinglePanel_Restriction.size = 1;
          moreThanTwo->n1_n2.choice.two_one_TypeI_SinglePanel_Restriction.bits_unused = 0;
          // TODO verify the meaning of this parameter
          asn1cCallocOne(moreThanTwo->n1_n2.choice.two_one_TypeI_SinglePanel_Restriction.buf, 0xff);
          break;
        case 8:
          if (antennaports->N1 == 2) {
            AssertFatal(antennaports->N2 == 2, "N1 and N2 not in accordace with the specifications\n");
            moreThanTwo->n1_n2.present = NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts__moreThanTwo__n1_n2_PR_two_two_TypeI_SinglePanel_Restriction;
            moreThanTwo->n1_n2.choice.two_two_TypeI_SinglePanel_Restriction.size = 8;
            moreThanTwo->n1_n2.choice.two_two_TypeI_SinglePanel_Restriction.bits_unused = 0;
            moreThanTwo->n1_n2.choice.two_two_TypeI_SinglePanel_Restriction.buf = calloc(8, sizeof(uint8_t));
            for (int i = 0; i < 8; i++)
              moreThanTwo->n1_n2.choice.two_two_TypeI_SinglePanel_Restriction.buf[i] = 0xff; // TODO verify the meaning of this parameter
          } else if (antennaports->N1 == 4) {
            AssertFatal(antennaports->N2 == 1, "N1 and N2 not in accordace with the specifications\n");
            moreThanTwo->n1_n2.present = NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts__moreThanTwo__n1_n2_PR_four_one_TypeI_SinglePanel_Restriction;
            moreThanTwo->n1_n2.choice.four_one_TypeI_SinglePanel_Restriction.size = 2;
            moreThanTwo->n1_n2.choice.four_one_TypeI_SinglePanel_Restriction.bits_unused = 0;
            moreThanTwo->n1_n2.choice.four_one_TypeI_SinglePanel_Restriction.buf = calloc(2, sizeof(uint8_t));
            for (int i = 0; i < 2; i++)
              moreThanTwo->n1_n2.choice.four_one_TypeI_SinglePanel_Restriction.buf[i] = 0xff; // TODO verify the meaning of this parameter
          } else
            AssertFatal(1 == 0, "N1 %d and N2 %d not supported for %d antenna ports\n", antennaports->N1, antennaports->N2, num_ant_ports);
          break;
        case 12:
          if (antennaports->N1 == 3) {
            AssertFatal(antennaports->N2 == 2, "N1 and N2 not in accordace with the specifications\n");
            moreThanTwo->n1_n2.present = NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts__moreThanTwo__n1_n2_PR_three_two_TypeI_SinglePanel_Restriction;
            moreThanTwo->n1_n2.choice.three_two_TypeI_SinglePanel_Restriction.size = 12;
            moreThanTwo->n1_n2.choice.three_two_TypeI_SinglePanel_Restriction.bits_unused = 0;
            moreThanTwo->n1_n2.choice.three_two_TypeI_SinglePanel_Restriction.buf = calloc(12, sizeof(uint8_t));
            for (int i = 0; i < 12; i++)
              moreThanTwo->n1_n2.choice.three_two_TypeI_SinglePanel_Restriction.buf[i] = 0xff; // TODO verify the meaning of this parameter
          } else if (antennaports->N1 == 6) {
            AssertFatal(antennaports->N2 == 1, "N1 and N2 not in accordace with the specifications\n");
            moreThanTwo->n1_n2.present = NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts__moreThanTwo__n1_n2_PR_six_one_TypeI_SinglePanel_Restriction;
            moreThanTwo->n1_n2.choice.six_one_TypeI_SinglePanel_Restriction.size = 3;
            moreThanTwo->n1_n2.choice.six_one_TypeI_SinglePanel_Restriction.bits_unused = 0;
            moreThanTwo->n1_n2.choice.six_one_TypeI_SinglePanel_Restriction.buf = calloc(3, sizeof(uint8_t));
            for (int i = 0; i < 3; i++)
             moreThanTwo->n1_n2.choice.six_one_TypeI_SinglePanel_Restriction.buf[i] = 0xff; // TODO verify the meaning of this parameter
          } else
            AssertFatal(1 == 0, "N1 %d and N2 %d not supported for %d antenna ports\n", antennaports->N1, antennaports->N2, num_ant_ports);
          break;
        case 16:
          if (antennaports->N1 == 4) {
            AssertFatal(antennaports->N2 == 2, "N1 and N2 not in accordace with the specifications\n");
            moreThanTwo->n1_n2.present = NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts__moreThanTwo__n1_n2_PR_four_two_TypeI_SinglePanel_Restriction;
            moreThanTwo->n1_n2.choice.four_two_TypeI_SinglePanel_Restriction.size = 16;
            moreThanTwo->n1_n2.choice.four_two_TypeI_SinglePanel_Restriction.bits_unused = 0;
            moreThanTwo->n1_n2.choice.four_two_TypeI_SinglePanel_Restriction.buf = calloc(16, sizeof(uint8_t));
            for (int i = 0; i < 16; i++)
              moreThanTwo->n1_n2.choice.four_two_TypeI_SinglePanel_Restriction.buf[i] = 0xff; // TODO verify the meaning of this parameter
          } else if (antennaports->N1 == 8) {
            AssertFatal(antennaports->N2 == 1, "N1 and N2 not in accordace with the specifications\n");
            moreThanTwo->n1_n2.present = NR_CodebookConfig__codebookType__type1__subType__typeI_SinglePanel__nrOfAntennaPorts__moreThanTwo__n1_n2_PR_eight_one_TypeI_SinglePanel_Restriction;
            moreThanTwo->n1_n2.choice.eight_one_TypeI_SinglePanel_Restriction.size = 4;
            moreThanTwo->n1_n2.choice.eight_one_TypeI_SinglePanel_Restriction.bits_unused = 0;
            moreThanTwo->n1_n2.choice.eight_one_TypeI_SinglePanel_Restriction.buf = calloc(4, sizeof(uint8_t));
            for (int i = 0; i < 4; i++)
              moreThanTwo->n1_n2.choice.eight_one_TypeI_SinglePanel_Restriction.buf[i] = 0xff; // TODO verify the meaning of this parameter
          } else
            AssertFatal(1 == 0, "N1 %d and N2 %d not supported for %d antenna ports\n", antennaports->N1, antennaports->N2, num_ant_ports);
          break;
        default:
          AssertFatal(1 == 0, "%d antenna ports not supported\n", num_ant_ports);
      }
    }
  }
  codebookConfig->codebookType.choice.type1->codebookMode = 1;
}

static void config_csi_meas_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
                                   const NR_ServingCellConfigCommon_t *servingcellconfigcommon,
                                   NR_PUCCH_CSI_Resource_t *pucchcsires,
                                   struct NR_SetupRelease_PDSCH_Config *pdsch_Config,
                                   const nr_pdsch_AntennaPorts_t *antennaports,
                                   const int max_layers,
                                   int rep_id,
                                   int uid,
                                   int curr_bwp)
{
  int resource_id = -1;
  int im_id = -1;
  for (int csi_list = 0; csi_list < csi_MeasConfig->csi_ResourceConfigToAddModList->list.count; csi_list++) {
    NR_CSI_ResourceConfig_t *csires = csi_MeasConfig->csi_ResourceConfigToAddModList->list.array[csi_list];
    if (csires->csi_RS_ResourceSetList.present == NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB) {
      if (csires->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList) {
        resource_id = csires->csi_ResourceConfigId;
      }
    }
    if (csires->csi_RS_ResourceSetList.present == NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_csi_IM_ResourceSetList) {
      if (csires->csi_RS_ResourceSetList.choice.csi_IM_ResourceSetList) {
        im_id = csires->csi_ResourceConfigId;
      }
    }
  }
  // if there are no associated resources, do not configure
  if (resource_id < 0 || im_id < 0)
    return;
  NR_CSI_ReportConfig_t *csirep = calloc(1, sizeof(*csirep));
  csirep->reportConfigId = rep_id;
  csirep->carrier = NULL;
  csirep->resourcesForChannelMeasurement = resource_id;
  csirep->csi_IM_ResourcesForInterference = calloc(1, sizeof(*csirep->csi_IM_ResourcesForInterference));
  *csirep->csi_IM_ResourcesForInterference = im_id;
  csirep->nzp_CSI_RS_ResourcesForInterference = NULL;
  csirep->reportConfigType.present = NR_CSI_ReportConfig__reportConfigType_PR_periodic;
  csirep->reportConfigType.choice.periodic = calloc(1, sizeof(*csirep->reportConfigType.choice.periodic));
  set_csi_meas_periodicity(servingcellconfigcommon, csirep, uid, curr_bwp, antennaports, false);
  asn1cSeqAdd(&csirep->reportConfigType.choice.periodic->pucch_CSI_ResourceList.list, pucchcsires);
  csirep->reportQuantity.present = NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_PMI_CQI;
  csirep->reportQuantity.choice.cri_RI_PMI_CQI = (NULL_t)0;
  csirep->reportFreqConfiguration = calloc(1, sizeof(*csirep->reportFreqConfiguration));
  // Wideband configuration
  csirep->reportFreqConfiguration->cqi_FormatIndicator = calloc(1, sizeof(*csirep->reportFreqConfiguration->cqi_FormatIndicator));
  *csirep->reportFreqConfiguration->cqi_FormatIndicator = NR_CSI_ReportConfig__reportFreqConfiguration__cqi_FormatIndicator_widebandCQI;
  csirep->reportFreqConfiguration->pmi_FormatIndicator = calloc(1, sizeof(*csirep->reportFreqConfiguration->pmi_FormatIndicator));
  *csirep->reportFreqConfiguration->pmi_FormatIndicator = NR_CSI_ReportConfig__reportFreqConfiguration__pmi_FormatIndicator_widebandPMI;
  csirep->reportFreqConfiguration->csi_ReportingBand = NULL;
  csirep->timeRestrictionForChannelMeasurements = NR_CSI_ReportConfig__timeRestrictionForChannelMeasurements_notConfigured;
  csirep->timeRestrictionForInterferenceMeasurements = NR_CSI_ReportConfig__timeRestrictionForInterferenceMeasurements_notConfigured;
  csirep->codebookConfig = calloc(1, sizeof(*csirep->codebookConfig));
  config_csi_codebook(antennaports, max_layers, csirep->codebookConfig);
  csirep->dummy = NULL;
  csirep->groupBasedBeamReporting.present = NR_CSI_ReportConfig__groupBasedBeamReporting_PR_disabled;
  csirep->groupBasedBeamReporting.choice.disabled = calloc(1, sizeof(*csirep->groupBasedBeamReporting.choice.disabled));
  csirep->cqi_Table = calloc(1, sizeof(*csirep->cqi_Table));
  if (pdsch_Config->choice.setup->mcs_Table != NULL)
    *csirep->cqi_Table = NR_CSI_ReportConfig__cqi_Table_table2;
  else
    *csirep->cqi_Table = NR_CSI_ReportConfig__cqi_Table_table1;
  csirep->subbandSize = NR_CSI_ReportConfig__subbandSize_value2;
  csirep->non_PMI_PortIndication = NULL;
  csirep->ext1 = NULL;
  asn1cSeqAdd(&csi_MeasConfig->csi_ReportConfigToAddModList->list, csirep);
}

static long config_nrofReportedRS(const NR_UE_NR_Capability_t *uecap,
                                  uint64_t ssb_bitmap,
                                  const NR_ServingCellConfigCommon_t *scc,
                                  int max_num_reported_rs)
{
  long nb_band = *scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0];
  int num_supported_rs = 0;
  if (uecap) {
    for (int i = 0; i < uecap->rf_Parameters.supportedBandListNR.list.count; i++) {
      NR_BandNR_t *band = uecap->rf_Parameters.supportedBandListNR.list.array[i];
      if (band->bandNR == nb_band) {
        if (band->mimo_ParametersPerBand && band->mimo_ParametersPerBand->maxNumberNonGroupBeamReporting) {
          int maxNumberNonGroupBeamReporting = 1 << *band->mimo_ParametersPerBand->maxNumberNonGroupBeamReporting;
          if (num_supported_rs == 0 || num_supported_rs > maxNumberNonGroupBeamReporting)
            num_supported_rs = maxNumberNonGroupBeamReporting;
        }
      }
    }
  }
  int configured_rs = max_num_reported_rs > 0 ? min(num_supported_rs, max_num_reported_rs) : num_supported_rs;
  uint32_t num_ssb = count_bits64(ssb_bitmap);
  if (num_ssb == 1 || configured_rs < 2)
    return NR_CSI_ReportConfig__groupBasedBeamReporting__disabled__nrofReportedRS_n1;
  if (num_ssb == 2 || configured_rs == 2)
    return NR_CSI_ReportConfig__groupBasedBeamReporting__disabled__nrofReportedRS_n2;
  if (num_ssb == 3 || configured_rs == 3)
    return NR_CSI_ReportConfig__groupBasedBeamReporting__disabled__nrofReportedRS_n3;
  return NR_CSI_ReportConfig__groupBasedBeamReporting__disabled__nrofReportedRS_n4;
}

static void config_rsrp_meas_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
                                    const NR_UE_NR_Capability_t *uecap,
                                    const NR_ServingCellConfigCommon_t *servingcellconfigcommon,
                                    NR_PUCCH_CSI_Resource_t *pucchcsires,
                                    const nr_mac_config_t *configuration,
                                    int rep_id,
                                    int uid,
                                    int curr_bwp,
                                    uint64_t ssb_bitmap)
{
  int resource_id = -1;
  const nr_pdsch_AntennaPorts_t *pdschap = &configuration->pdsch_AntennaPorts;
  const int num_antenna_ports = pdschap->N1 * pdschap->N2 * pdschap->XP;
  for (int csi_list = 0; csi_list < csi_MeasConfig->csi_ResourceConfigToAddModList->list.count; csi_list++) {
    NR_CSI_ResourceConfig_t *csires = csi_MeasConfig->csi_ResourceConfigToAddModList->list.array[csi_list];
    if (csires->csi_RS_ResourceSetList.present == NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB) {
      if (configuration->report_type == CRI_RSRP && configuration->do_CSIRS && num_antenna_ports < 4) {
        if (csires->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList)
          resource_id = csires->csi_ResourceConfigId;
      } else {
        if (csires->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->csi_SSB_ResourceSetList)
          resource_id = csires->csi_ResourceConfigId;
      }
    }
  }
  // if there are no associated resources, do not configure
  if (resource_id < 0)
    return;
  NR_CSI_ReportConfig_t *csirep = calloc(1, sizeof(*csirep));
  csirep->reportConfigId = rep_id;
  csirep->carrier = NULL;
  csirep->resourcesForChannelMeasurement = resource_id;
  csirep->csi_IM_ResourcesForInterference = NULL;
  csirep->nzp_CSI_RS_ResourcesForInterference = NULL;
  csirep->reportConfigType.present = NR_CSI_ReportConfig__reportConfigType_PR_periodic;
  csirep->reportConfigType.choice.periodic = calloc(1, sizeof(*csirep->reportConfigType.choice.periodic));
  set_csi_meas_periodicity(servingcellconfigcommon, csirep, uid, curr_bwp, pdschap, true);
  asn1cSeqAdd(&csirep->reportConfigType.choice.periodic->pucch_CSI_ResourceList.list, pucchcsires);
  if (configuration->report_type == SSB_SINR) {
    csirep->reportQuantity.present = NR_CSI_ReportConfig__reportQuantity_PR_none;
    csirep->reportQuantity.choice.none = (NULL_t)0;
    csirep->ext2 = calloc(1, sizeof(*csirep->ext2));
    csirep->ext2->reportQuantity_r16 = calloc(1, sizeof(*csirep->ext2->reportQuantity_r16));
    csirep->ext2->reportQuantity_r16->present = NR_CSI_ReportConfig__ext2__reportQuantity_r16_PR_ssb_Index_SINR_r16;
    csirep->ext2->reportQuantity_r16->choice.ssb_Index_SINR_r16 = (NULL_t)0;
  } else if (configuration->report_type == CRI_RSRP && configuration->do_CSIRS && num_antenna_ports < 4) {
    csirep->reportQuantity.present = NR_CSI_ReportConfig__reportQuantity_PR_cri_RSRP;
    csirep->reportQuantity.choice.cri_RSRP = (NULL_t)0;
  } else {
    csirep->reportQuantity.present = NR_CSI_ReportConfig__reportQuantity_PR_ssb_Index_RSRP;
    csirep->reportQuantity.choice.ssb_Index_RSRP = (NULL_t)0;
  }
  csirep->groupBasedBeamReporting.present = NR_CSI_ReportConfig__groupBasedBeamReporting_PR_disabled;
  csirep->groupBasedBeamReporting.choice.disabled = calloc(1, sizeof(*csirep->groupBasedBeamReporting.choice.disabled));
  csirep->groupBasedBeamReporting.choice.disabled->nrofReportedRS = calloc(1, sizeof(*csirep->groupBasedBeamReporting.choice.disabled->nrofReportedRS));
  *csirep->groupBasedBeamReporting.choice.disabled->nrofReportedRS = config_nrofReportedRS(uecap,
                                                                                           ssb_bitmap,
                                                                                           servingcellconfigcommon,
                                                                                           configuration->max_num_rsrp);
  asn1cSeqAdd(&csi_MeasConfig->csi_ReportConfigToAddModList->list, csirep);
}

static void update_cqitables(struct NR_SetupRelease_PDSCH_Config *pdsch_Config, NR_CSI_MeasConfig_t *csi_MeasConfig)
{
  int nb_csi = csi_MeasConfig->csi_ReportConfigToAddModList->list.count;
  for (int i = 0; i < nb_csi; i++) {
    NR_CSI_ReportConfig_t *csirep = csi_MeasConfig->csi_ReportConfigToAddModList->list.array[i];
    if(csirep->cqi_Table) {
      if(pdsch_Config->choice.setup->mcs_Table!=NULL)
        *csirep->cqi_Table = NR_CSI_ReportConfig__cqi_Table_table2;
      else
        *csirep->cqi_Table = NR_CSI_ReportConfig__cqi_Table_table1;
    }
  }
}

NR_BCCH_BCH_Message_t *get_new_MIB_NR(const NR_ServingCellConfigCommon_t *scc)
{
  NR_BCCH_BCH_Message_t *mib = calloc(1, sizeof(*mib));
  if (mib == NULL)
    abort();
  mib->message.present = NR_BCCH_BCH_MessageType_PR_mib;
  mib->message.choice.mib = calloc(1, sizeof(struct NR_MIB));
  if (mib->message.choice.mib == NULL)
    abort();

  // 36.331 SFN BIT STRING (SIZE (8)  , 38.331 SFN BIT STRING (SIZE (6))
  uint8_t sfn_msb = 0; // encoding will update with the correct frame number
  mib->message.choice.mib->systemFrameNumber.buf = CALLOC(1, sizeof(uint8_t));
  mib->message.choice.mib->systemFrameNumber.buf[0] = sfn_msb << 2;
  mib->message.choice.mib->systemFrameNumber.size = 1;
  mib->message.choice.mib->systemFrameNumber.bits_unused = 2;

  // 38.331 spare BIT STRING (SIZE (1))
  uint16_t *spare = CALLOC(1, sizeof(uint16_t));
  if (spare == NULL)
    abort();
  mib->message.choice.mib->spare.buf = (uint8_t *)spare;
  mib->message.choice.mib->spare.size = 1;
  mib->message.choice.mib->spare.bits_unused = 7; // This makes a spare of 1 bits

  AssertFatal(scc->ssbSubcarrierSpacing != NULL, "scc->ssbSubcarrierSpacing is null\n");
  int ssb_subcarrier_offset = 31; // default value for NSA
  if (IS_SA_MODE(get_softmodem_params())) {
    ssb_subcarrier_offset = get_ssb_subcarrier_offset(*scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB,
                                                      scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencyPointA,
                                                      *scc->ssbSubcarrierSpacing);
  }
  mib->message.choice.mib->ssb_SubcarrierOffset = ssb_subcarrier_offset & 15;

  /*
   * The SIB1 will be sent in this allocation (Type0-PDCCH) : 38.213, 13-4 Table and 38.213 13-11 to 13-14 tables
   * the reverse allocation is in nr_ue_decode_mib()
   */
  const NR_PDCCH_ConfigCommon_t *pdcch_cc = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup;
  long cset0 = pdcch_cc->controlResourceSetZero ? *pdcch_cc->controlResourceSetZero : 0;
  mib->message.choice.mib->pdcch_ConfigSIB1.controlResourceSetZero = cset0;
  long ss0 = pdcch_cc->searchSpaceZero ? *pdcch_cc->searchSpaceZero : 0;
  mib->message.choice.mib->pdcch_ConfigSIB1.searchSpaceZero = ss0;

  switch (*scc->ssbSubcarrierSpacing) {
    case NR_SubcarrierSpacing_kHz15:
      mib->message.choice.mib->subCarrierSpacingCommon = NR_MIB__subCarrierSpacingCommon_scs15or60;
      break;

    case NR_SubcarrierSpacing_kHz30:
      mib->message.choice.mib->subCarrierSpacingCommon = NR_MIB__subCarrierSpacingCommon_scs30or120;
      break;

    case NR_SubcarrierSpacing_kHz60:
      mib->message.choice.mib->subCarrierSpacingCommon = NR_MIB__subCarrierSpacingCommon_scs15or60;
      break;

    case NR_SubcarrierSpacing_kHz120:
      mib->message.choice.mib->subCarrierSpacingCommon = NR_MIB__subCarrierSpacingCommon_scs30or120;
      break;

    case NR_SubcarrierSpacing_kHz240:
      AssertFatal(1 == 0, "Unknown subCarrierSpacingCommon %d\n", (int)*scc->ssbSubcarrierSpacing);
      break;

    default:
      AssertFatal(1 == 0, "Unknown subCarrierSpacingCommon %d\n", (int)*scc->ssbSubcarrierSpacing);
  }

  switch (scc->dmrs_TypeA_Position) {
    case NR_ServingCellConfigCommon__dmrs_TypeA_Position_pos2:
      mib->message.choice.mib->dmrs_TypeA_Position = NR_MIB__dmrs_TypeA_Position_pos2;
      break;

    case NR_ServingCellConfigCommon__dmrs_TypeA_Position_pos3:
      mib->message.choice.mib->dmrs_TypeA_Position = NR_MIB__dmrs_TypeA_Position_pos3;
      break;

    default:
      AssertFatal(1 == 0, "Unknown dmrs_TypeA_Position %d\n", (int)scc->dmrs_TypeA_Position);
  }

  mib->message.choice.mib->cellBarred = NR_MIB__cellBarred_notBarred;
  mib->message.choice.mib->intraFreqReselection = NR_MIB__intraFreqReselection_notAllowed;
  return mib;
}

void free_MIB_NR(NR_BCCH_BCH_Message_t *mib)
{
  ASN_STRUCT_FREE(asn_DEF_NR_BCCH_BCH_Message, mib);
}

int encode_MIB_NR(NR_BCCH_BCH_Message_t *mib, int frame, uint8_t *buf, int buf_size)
{
  DevAssert(mib != NULL && mib->message.choice.mib->systemFrameNumber.buf != NULL);
  uint8_t sfn_msb = (uint8_t)((frame >> 4) & 0x3f);
  *mib->message.choice.mib->systemFrameNumber.buf = sfn_msb << 2;

  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_BCCH_BCH_Message, NULL, mib, buf, buf_size);
  AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded);
  LOG_D(NR_RRC, "Encoded MIB for frame %d sfn_msb %d, bits %lu\n", frame, sfn_msb, enc_rval.encoded);
  return (enc_rval.encoded + 7) / 8;
}

int encode_MIB_NR_setup(NR_MIB_t *mib, int frame, uint8_t *buf, int buf_size)
{
  DevAssert(mib != NULL);
  uint8_t sfn_msb = (uint8_t)((frame >> 4) & 0x3f);
  *mib->systemFrameNumber.buf = sfn_msb << 2;

  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_MIB, NULL, mib, buf, buf_size);
  AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded);
  LOG_D(NR_RRC, "Encoded MIB for frame %d sfn_msb %d, bits %lu\n", frame, sfn_msb, enc_rval.encoded);
  return (enc_rval.encoded + 7) / 8;
}

static struct NR_SSB_MTC__periodicityAndOffset get_SSB_MTC_periodicityAndOffset(long ssb_periodicityServingCell)
{
  struct NR_SSB_MTC__periodicityAndOffset po = {0};
  switch (ssb_periodicityServingCell) {
    case NR_ServingCellConfigCommon__ssb_periodicityServingCell_ms5:
      po.present = NR_SSB_MTC__periodicityAndOffset_PR_sf5;
      po.choice.sf5 = 0;
      break;
    case NR_ServingCellConfigCommon__ssb_periodicityServingCell_ms10:
      po.present = NR_SSB_MTC__periodicityAndOffset_PR_sf10;
      po.choice.sf10 = 0;
      break;
    case NR_ServingCellConfigCommon__ssb_periodicityServingCell_ms20:
      po.present = NR_SSB_MTC__periodicityAndOffset_PR_sf20;
      po.choice.sf20 = 0;
      break;
    case NR_ServingCellConfigCommon__ssb_periodicityServingCell_ms40:
      po.present = NR_SSB_MTC__periodicityAndOffset_PR_sf40;
      po.choice.sf40 = 0;
      break;
    case NR_ServingCellConfigCommon__ssb_periodicityServingCell_ms80:
      po.present = NR_SSB_MTC__periodicityAndOffset_PR_sf80;
      po.choice.sf80 = 0;
      break;
    case NR_ServingCellConfigCommon__ssb_periodicityServingCell_ms160:
      po.present = NR_SSB_MTC__periodicityAndOffset_PR_sf160;
      po.choice.sf160 = 0;
      break;
    default:
      AssertFatal(false, "illegal ssb_periodicityServingCell %ld\n", ssb_periodicityServingCell);
      break;
  }
  return po;
}

NR_MeasurementTimingConfiguration_t *get_new_MeasurementTimingConfiguration(const NR_ServingCellConfigCommon_t *scc)
{
  NR_MeasurementTimingConfiguration_t *mtc = calloc(1, sizeof(*mtc));
  AssertFatal(mtc != NULL, "out of memory\n");
  mtc->criticalExtensions.present = NR_MeasurementTimingConfiguration__criticalExtensions_PR_c1;
  mtc->criticalExtensions.choice.c1 = calloc(1, sizeof(*mtc->criticalExtensions.choice.c1));
  AssertFatal(mtc->criticalExtensions.choice.c1 != NULL, "out of memory\n");
  mtc->criticalExtensions.choice.c1->present = NR_MeasurementTimingConfiguration__criticalExtensions__c1_PR_measTimingConf;
  NR_MeasurementTimingConfiguration_IEs_t *mtc_ie = calloc(1, sizeof(*mtc_ie));
  AssertFatal(mtc_ie != NULL, "out of memory\n");
  mtc->criticalExtensions.choice.c1->choice.measTimingConf = mtc_ie;
  mtc_ie->measTiming = calloc(1, sizeof(*mtc_ie->measTiming));
  AssertFatal(mtc_ie->measTiming != NULL, "out of memory\n");

  asn1cSequenceAdd(mtc_ie->measTiming->list, NR_MeasTiming_t, mt);
  AssertFatal(mt != NULL, "out of memory\n");
  mt->frequencyAndTiming = calloc(1, sizeof(*mt->frequencyAndTiming));
  AssertFatal(mt->frequencyAndTiming != NULL, "out of memory\n");
  mt->frequencyAndTiming->carrierFreq = *scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB;
  mt->frequencyAndTiming->ssbSubcarrierSpacing = *scc->ssbSubcarrierSpacing;

  NR_SSB_MTC_t *ssb_mtc = &mt->frequencyAndTiming->ssb_MeasurementTimingConfiguration;
  ssb_mtc->duration = NR_SSB_MTC__duration_sf1;
  ssb_mtc->periodicityAndOffset = get_SSB_MTC_periodicityAndOffset(*scc->ssb_periodicityServingCell);

  return mtc;
}

int encode_MeasurementTimingConfiguration(const struct NR_MeasurementTimingConfiguration *mtc, uint8_t *buf, int buf_len)
{
  DevAssert(mtc != NULL);
  if (LOG_DEBUGFLAG(DEBUG_ASN1))
    xer_fprint(stdout, &asn_DEF_NR_MeasurementTimingConfiguration, mtc);
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_MeasurementTimingConfiguration, NULL, mtc, buf, buf_len);
  AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded);
  return (enc_rval.encoded + 7) / 8;
}

void free_MeasurementTimingConfiguration(NR_MeasurementTimingConfiguration_t *mtc)
{
  ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc);
}

static long get_NR_UE_TimersAndConstants_t300(const nr_mac_timers_t *timer_config)
{
  switch (timer_config->t300) {
    case 100:
      return NR_UE_TimersAndConstants__t300_ms100;
    case 200:
      return NR_UE_TimersAndConstants__t300_ms200;
    case 300:
      return NR_UE_TimersAndConstants__t300_ms300;
    case 400:
      return NR_UE_TimersAndConstants__t300_ms400;
    case 600:
      return NR_UE_TimersAndConstants__t300_ms600;
    case 1000:
      return NR_UE_TimersAndConstants__t300_ms1000;
    case 1500:
      return NR_UE_TimersAndConstants__t300_ms1500;
    case 2000:
      return NR_UE_TimersAndConstants__t300_ms2000;
    default:
      AssertFatal(1 == 0, "Invalid value configured for t300!\n");
  }
}

static long get_NR_UE_TimersAndConstants_t301(const nr_mac_timers_t *timer_config)
{
  switch (timer_config->t301) {
    case 100:
      return NR_UE_TimersAndConstants__t301_ms100;
    case 200:
      return NR_UE_TimersAndConstants__t301_ms200;
    case 300:
      return NR_UE_TimersAndConstants__t301_ms300;
    case 400:
      return NR_UE_TimersAndConstants__t301_ms400;
    case 600:
      return NR_UE_TimersAndConstants__t301_ms600;
    case 1000:
      return NR_UE_TimersAndConstants__t301_ms1000;
    case 1500:
      return NR_UE_TimersAndConstants__t301_ms1500;
    case 2000:
      return NR_UE_TimersAndConstants__t301_ms2000;
    default:
      AssertFatal(1 == 0, "Invalid value configured for t301!\n");
  }
}

static long get_NR_UE_TimersAndConstants_t310(const nr_mac_timers_t *timer_config)
{
  switch (timer_config->t310) {
    case 0:
      return NR_UE_TimersAndConstants__t310_ms0;
    case 50:
      return NR_UE_TimersAndConstants__t310_ms50;
    case 100:
      return NR_UE_TimersAndConstants__t310_ms100;
    case 200:
      return NR_UE_TimersAndConstants__t310_ms200;
    case 500:
      return NR_UE_TimersAndConstants__t310_ms500;
    case 1000:
      return NR_UE_TimersAndConstants__t310_ms1000;
    case 2000:
      return NR_UE_TimersAndConstants__t310_ms2000;
    default:
      AssertFatal(1 == 0, "Invalid value configured for t310!\n");
  }
}

static long get_NR_UE_TimersAndConstants_n310(const nr_mac_timers_t *timer_config)
{
  switch (timer_config->n310) {
    case 1:
      return NR_UE_TimersAndConstants__n310_n1;
    case 2:
      return NR_UE_TimersAndConstants__n310_n2;
    case 3:
      return NR_UE_TimersAndConstants__n310_n3;
    case 4:
      return NR_UE_TimersAndConstants__n310_n4;
    case 6:
      return NR_UE_TimersAndConstants__n310_n6;
    case 8:
      return NR_UE_TimersAndConstants__n310_n8;
    case 10:
      return NR_UE_TimersAndConstants__n310_n10;
    case 20:
      return NR_UE_TimersAndConstants__n310_n20;
    default:
      AssertFatal(1 == 0, "Invalid value configured for n310!\n");
  }
}

static long get_NR_UE_TimersAndConstants_t311(const nr_mac_timers_t *timer_config)
{
  switch (timer_config->t311) {
    case 1000:
      return NR_UE_TimersAndConstants__t311_ms1000;
    case 3000:
      return NR_UE_TimersAndConstants__t311_ms3000;
    case 5000:
      return NR_UE_TimersAndConstants__t311_ms5000;
    case 10000:
      return NR_UE_TimersAndConstants__t311_ms10000;
    case 15000:
      return NR_UE_TimersAndConstants__t311_ms15000;
    case 20000:
      return NR_UE_TimersAndConstants__t311_ms20000;
    case 30000:
      return NR_UE_TimersAndConstants__t311_ms30000;
    default:
      AssertFatal(1 == 0, "Invalid value configured for t311!\n");
  }
}

static long get_NR_UE_TimersAndConstants_n311(const nr_mac_timers_t *timer_config)
{
  switch (timer_config->n311) {
    case 1:
      return NR_UE_TimersAndConstants__n311_n1;
    case 2:
      return NR_UE_TimersAndConstants__n311_n2;
    case 3:
      return NR_UE_TimersAndConstants__n311_n3;
    case 4:
      return NR_UE_TimersAndConstants__n311_n4;
    case 5:
      return NR_UE_TimersAndConstants__n311_n5;
    case 6:
      return NR_UE_TimersAndConstants__n311_n6;
    case 8:
      return NR_UE_TimersAndConstants__n311_n8;
    case 10:
      return NR_UE_TimersAndConstants__n311_n10;
    default:
      AssertFatal(1 == 0, "Invalid value configured for n311!\n");
  }
}

static long get_NR_UE_TimersAndConstants_t319(const nr_mac_timers_t *timer_config)
{
  switch (timer_config->t319) {
    case 100:
      return NR_UE_TimersAndConstants__t319_ms100;
    case 200:
      return NR_UE_TimersAndConstants__t319_ms200;
    case 300:
      return NR_UE_TimersAndConstants__t319_ms300;
    case 400:
      return NR_UE_TimersAndConstants__t319_ms400;
    case 600:
      return NR_UE_TimersAndConstants__t319_ms600;
    case 1000:
      return NR_UE_TimersAndConstants__t319_ms1000;
    case 1500:
      return NR_UE_TimersAndConstants__t319_ms1500;
    case 2000:
      return NR_UE_TimersAndConstants__t319_ms2000;
    default:
      AssertFatal(1 == 0, "Invalid value configured for t319!\n");
  }
}

void add_sib_to_systeminformation(NR_SystemInformation_IEs_t *si, struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type)
{
  asn1cSeqAdd(&si->sib_TypeAndInfo.list, type);
}

void update_SIB1_NR_SI(NR_BCCH_DL_SCH_Message_t *sib1_bcch, int num_sibs, int sibs[num_sibs])
{
  NR_SIB1_t *sib1 = sib1_bcch->message.choice.c1->choice.systemInformationBlockType1;
  //si-SchedulingInfo
  NR_SI_SchedulingInfo_t *info = calloc(1, sizeof(*info));
  // TODO need to compute optimal SI-windowlength automatically
  //      based on the number of SSBs, TDD configuration and SS configuration
  info->si_WindowLength = NR_SI_SchedulingInfo__si_WindowLength_s10;
  NR_SchedulingInfo_t *schedulingInfo = calloc(1, sizeof(*schedulingInfo));
  schedulingInfo->si_BroadcastStatus = NR_SchedulingInfo__si_BroadcastStatus_broadcasting;
  schedulingInfo->si_Periodicity = NR_SchedulingInfo__si_Periodicity_rf16;
  bool reg_sib = false;
  NR_SchedulingInfo2_r17_t *schedulingInfo2_r17 = NULL;
  for (int i = 0; i < num_sibs; i++) {
    int sib_num = sibs[i];
    AssertFatal(sib_num > 1 && sib_num < 21, "other SIB invalid type\n");
    if (sib_num < 15) {
      reg_sib = true;
      NR_SIB_TypeInfo_t *mapping = calloc(1, sizeof(*mapping));
      // NR_SIB_TypeInfo__type_sibType2 = 0
      // NR_SIB_TypeInfo__type_sibType3 = 1
      // and so on
      mapping->type = sib_num - 2;
      // The field is mandatory present if the type is different from SIB6, SIB7 or SIB8. For SIB6, SIB7 and SIB8 it is absent.
      if (sib_num != 6 && sib_num != 7 && sib_num != 8) {
        mapping->valueTag = calloc(1, sizeof(*mapping->valueTag));
        *mapping->valueTag = 0;
      }
      asn1cSeqAdd(&schedulingInfo->sib_MappingInfo.list, mapping);
    } else {
      if (schedulingInfo2_r17 == NULL) {
        schedulingInfo2_r17 = calloc(1, sizeof(*schedulingInfo2_r17));
        schedulingInfo2_r17->si_BroadcastStatus_r17 = NR_SchedulingInfo2_r17__si_BroadcastStatus_r17_broadcasting;
        schedulingInfo2_r17->si_Periodicity_r17 = NR_SchedulingInfo2_r17__si_Periodicity_r17_rf16;
        schedulingInfo2_r17->si_WindowPosition_r17 = 2;
      }
      NR_SIB_TypeInfo_v1700_t *mapping17 = calloc(1, sizeof(*mapping17));
      mapping17->sibType_r17.present = NR_SIB_TypeInfo_v1700__sibType_r17_PR_type1_r17;
      // NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType15 = 0
      // and so on
      mapping17->sibType_r17.choice.type1_r17 = sib_num - 15;
      mapping17->valueTag_r17 = calloc(1, sizeof(*mapping17->valueTag_r17));
      *mapping17->valueTag_r17 = 0;
      asn1cSeqAdd(&schedulingInfo2_r17->sib_MappingInfo_r17.list, mapping17);
    }
  }
  AssertFatal(reg_sib, "At least 1 SIB from SchedulingInfo (SIB2 to SIB14 included) needs to be present\n");
  asn1cSeqAdd(&info->schedulingInfoList.list, schedulingInfo);
  sib1->si_SchedulingInfo = info;
  if (schedulingInfo2_r17) {
    NR_SI_SchedulingInfo_v1700_t *si_schedulingInfo_v17 = calloc_or_fail(1, sizeof(*si_schedulingInfo_v17));
    asn1cSeqAdd(&si_schedulingInfo_v17->schedulingInfoList2_r17.list, schedulingInfo2_r17);
    NR_SIB1_v1610_IEs_t *sib1_v1610 = NULL;
    if (sib1->nonCriticalExtension)
      sib1_v1610 = sib1->nonCriticalExtension;
    else {
      sib1_v1610 = calloc_or_fail(1, sizeof(*sib1_v1610));
      sib1->nonCriticalExtension = sib1_v1610;
    }
    NR_SIB1_v1630_IEs_t *sib1_v1630 = NULL;
    if (sib1_v1610->nonCriticalExtension)
      sib1_v1630 = sib1_v1610->nonCriticalExtension;
    else {
      sib1_v1630 = calloc_or_fail(1, sizeof(*sib1_v1630));
      sib1_v1610->nonCriticalExtension = sib1_v1630;
    }
    NR_SIB1_v1700_IEs_t *sib1_v17 = NULL;
    if (sib1_v1630->nonCriticalExtension)
      sib1_v17 = sib1_v1630->nonCriticalExtension;
    else {
      sib1_v17 = calloc_or_fail(1, sizeof(*sib1_v17));
      sib1_v1630->nonCriticalExtension = sib1_v17;
    }
    sib1_v17->si_SchedulingInfo_v1700 = si_schedulingInfo_v17;
  }
  if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
    xer_fprint(stdout, &asn_DEF_NR_BCCH_DL_SCH_Message, sib1_bcch);
  }
}

int encode_sysinfo_ie(NR_SystemInformation_IEs_t *sysInfo, uint8_t *buf, int len)
{
  if (sysInfo->sib_TypeAndInfo.list.count == 0)
    return 0;
  NR_SystemInformation_t si = {.criticalExtensions.present = NR_SystemInformation__criticalExtensions_PR_systemInformation};
  si.criticalExtensions.choice.systemInformation = sysInfo;
  struct NR_BCCH_DL_SCH_MessageType__c1 c1 = {.present = NR_BCCH_DL_SCH_MessageType__c1_PR_systemInformation};
  c1.choice.systemInformation = &si;
  NR_BCCH_DL_SCH_MessageType_t message_type = {.present = NR_BCCH_DL_SCH_MessageType_PR_c1};
  message_type.choice.c1 = &c1;
  NR_BCCH_DL_SCH_Message_t sib_message = {.message = message_type};
  return encode_SIB_NR(&sib_message, buf, len);
}

static bool is_ntn_band(int band)
{
  // TS 3GPP 38.101-5 V1807 Section 5.2.2
  if (band >= 254 && band <= 256) // FR1 NTN
    return true;

  // TS 3GPP 38.101-5 V1807 Section 5.2.3
  if (band >= 510 && band <= 512) // FR2 NTN
    return true;

  return false;
}

static BIT_STRING_t bit_string_clone(const BIT_STRING_t *orig)
{
  BIT_STRING_t bs = {.size = orig->size, .bits_unused = orig->bits_unused};
  bs.buf = malloc_or_fail(bs.size * sizeof(*bs.buf));
  memcpy(bs.buf, orig->buf, bs.size);
  return bs;
}

void configure_coreset_for_mux23(const NR_ServingCellConfigCommon_t *scc,
                                 int offset,
                                 int limit,
                                 int bwp_start,
                                 int bwp_size,
                                 bool do_TCI)
{
  NR_ControlResourceSet_t *coreset = get_coreset_config(5, offset, limit, bwp_start, bwp_size, get_ssb_bitmap(scc), do_TCI);
  NR_PDCCH_ConfigCommon_t *pdcch_common = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup;
  pdcch_common->commonControlResourceSet = coreset;
  for (int i = 0; i < pdcch_common->commonSearchSpaceList->list.count; i++) {
    *pdcch_common->commonSearchSpaceList->list.array[i]->controlResourceSetId = coreset->controlResourceSetId;
  }
}

NR_BCCH_DL_SCH_Message_t *get_SIB1_NR(const NR_ServingCellConfigCommon_t *scc,
                                      const plmn_id_t *plmn,
                                      uint64_t cellID,
                                      int tac,
                                      const nr_mac_config_t *mac_config)
{
  AssertFatal(cellID < (1l << 36), "cellID must fit within 36 bits, but is %lu\n", cellID);

  NR_BCCH_DL_SCH_Message_t *sib1_message = CALLOC(1,sizeof(NR_BCCH_DL_SCH_Message_t));
  AssertFatal(sib1_message != NULL, "out of memory\n");
  sib1_message->message.present = NR_BCCH_DL_SCH_MessageType_PR_c1;
  sib1_message->message.choice.c1 = CALLOC(1,sizeof(struct NR_BCCH_DL_SCH_MessageType__c1));
  AssertFatal(sib1_message->message.choice.c1 != NULL, "out of memory\n");
  sib1_message->message.choice.c1->present = NR_BCCH_DL_SCH_MessageType__c1_PR_systemInformationBlockType1;
  sib1_message->message.choice.c1->choice.systemInformationBlockType1 = CALLOC(1,sizeof(struct NR_SIB1));
  AssertFatal(sib1_message->message.choice.c1->choice.systemInformationBlockType1 != NULL, "out of memory\n");
  NR_SIB1_t *sib1 = sib1_message->message.choice.c1->choice.systemInformationBlockType1;

  // cellSelectionInfo
  sib1->cellSelectionInfo = CALLOC(1,sizeof(*sib1->cellSelectionInfo));
  AssertFatal(sib1->cellSelectionInfo != NULL, "out of memory\n");
  // Fixme: should be in config file
  //The IE Q-RxLevMin is used to indicate for cell selection/ re-selection the required minimum received RSRP level in the (NR) cell.
  //Corresponds to parameter Qrxlevmin in TS38.304.
  //Actual value Qrxlevmin = field value * 2 [dBm].
  sib1->cellSelectionInfo->q_RxLevMin = -65;

  // cellAccessRelatedInfo
  // TODO : Add support for more than one PLMN
  int num_plmn = 1; // int num_plmn = configuration->num_plmn;
  asn1cSequenceAdd(sib1->cellAccessRelatedInfo.plmn_IdentityInfoList.list, struct NR_PLMN_IdentityInfo, nr_plmn_info);
  for (int i = 0; i < num_plmn; ++i) {
    asn1cSequenceAdd(nr_plmn_info->plmn_IdentityList.list, struct NR_PLMN_Identity, nr_plmn);
    asn1cCalloc(nr_plmn->mcc, mcc);
    int confMcc = plmn->mcc;
    asn1cSequenceAdd(mcc->list, NR_MCC_MNC_Digit_t, mcc0);
    *mcc0 = (confMcc / 100) % 10;
    asn1cSequenceAdd(mcc->list, NR_MCC_MNC_Digit_t, mcc1);
    *mcc1 = (confMcc / 10) % 10;
    asn1cSequenceAdd(mcc->list, NR_MCC_MNC_Digit_t, mcc2);
    *mcc2 = confMcc % 10;
    int mnc = plmn->mnc;
    if (plmn->mnc_digit_length == 3) {
      asn1cSequenceAdd(nr_plmn->mnc.list, NR_MCC_MNC_Digit_t, mnc0);
      *mnc0 = (mnc / 100) % 10;
    }
    asn1cSequenceAdd(nr_plmn->mnc.list, NR_MCC_MNC_Digit_t, mnc1);
    *mnc1 = (mnc / 10) % 10;
    asn1cSequenceAdd(nr_plmn->mnc.list, NR_MCC_MNC_Digit_t, mnc2);
    *mnc2 = (mnc) % 10;
  }

  NR_CELL_ID_TO_BIT_STRING(cellID, &nr_plmn_info->cellIdentity);
  nr_plmn_info->cellReservedForOperatorUse = NR_PLMN_IdentityInfo__cellReservedForOperatorUse_notReserved;

  nr_plmn_info->trackingAreaCode = CALLOC(1, sizeof(NR_TrackingAreaCode_t));
  AssertFatal(nr_plmn_info->trackingAreaCode != NULL, "out of memory\n");
  uint32_t tmp2 = htobe32(tac);
  nr_plmn_info->trackingAreaCode->buf = CALLOC(1, 3);
  AssertFatal(nr_plmn_info->trackingAreaCode->buf != NULL, "out of memory\n");
  memcpy(nr_plmn_info->trackingAreaCode->buf, ((char *)&tmp2) + 1, 3);
  nr_plmn_info->trackingAreaCode->size = 3;
  nr_plmn_info->trackingAreaCode->bits_unused = 0;

  // connEstFailureControl
  // TODO: add connEstFailureControl

  // servingCellConfigCommon
  asn1cCalloc(sib1->servingCellConfigCommon, ServCellCom);
  NR_BWP_DownlinkCommon_t *initialDownlinkBWP = &ServCellCom->downlinkConfigCommon.initialDownlinkBWP;
  initialDownlinkBWP->genericParameters = clone_generic_parameters(&scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters);

  const NR_FrequencyInfoDL_t *frequencyInfoDL = scc->downlinkConfigCommon->frequencyInfoDL;
  for (int i = 0; i < frequencyInfoDL->frequencyBandList.list.count; i++) {
    asn1cSequenceAdd(ServCellCom->downlinkConfigCommon.frequencyInfoDL.frequencyBandList.list,
                     struct NR_NR_MultiBandInfo,
                     nrMultiBandInfo);
    asn1cCallocOne(nrMultiBandInfo->freqBandIndicatorNR, *frequencyInfoDL->frequencyBandList.list.array[i]);
  }

  const NR_FreqBandIndicatorNR_t band = *frequencyInfoDL->frequencyBandList.list.array[0];
  frequency_range_t frequency_range = get_freq_range_from_band(band);
  sib1->servingCellConfigCommon->downlinkConfigCommon.frequencyInfoDL.offsetToPointA = get_ssb_offset_to_pointA(*scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB,
                               scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencyPointA,
                               scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.subcarrierSpacing,
                               frequency_range);

  LOG_I(NR_RRC,
	"SIB1 freq: offsetToPointA %d\n",
        (int)sib1->servingCellConfigCommon->downlinkConfigCommon.frequencyInfoDL.offsetToPointA);
  
  for (int i = 0; i < frequencyInfoDL->scs_SpecificCarrierList.list.count; i++) {
    const NR_SCS_SpecificCarrier_t *orig = frequencyInfoDL->scs_SpecificCarrierList.list.array[i];
    NR_SCS_SpecificCarrier_t *new = NULL;
    const int copy_result = asn_copy(&asn_DEF_NR_SCS_SpecificCarrier, (void **)&new, orig);
    AssertFatal(copy_result == 0, "unable to copy NR_SCS_SpecificCarrier from scc to SIB1 structure\n");
    asn1cSeqAdd(&ServCellCom->downlinkConfigCommon.frequencyInfoDL.scs_SpecificCarrierList.list, new);
  }

  initialDownlinkBWP->pdcch_ConfigCommon = clone_pdcch_configcommon(scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon);
  AssertFatal(initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList != NULL,
              "expected commonSearchSpaceList to be populated through SCC\n");
  AssertFatal(initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->searchSpaceSIB1 != NULL,
              "expected searchSpaceSIB1 to be populated through SCC\n");
  AssertFatal(initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->ra_SearchSpace != NULL,
              "expected ra_SearchSpace to be populated through SCC\n");
  AssertFatal(initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->pagingSearchSpace != NULL,
              "expected pagingSearchSpace to be populated through SCC\n");
  AssertFatal(initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->searchSpaceOtherSystemInformation != NULL,
              "expected searchSpaceOtherSystemInformation to be populated through SCC\n");

  initialDownlinkBWP->pdsch_ConfigCommon = clone_pdsch_configcommon(scc->downlinkConfigCommon->initialDownlinkBWP->pdsch_ConfigCommon);
  ServCellCom->downlinkConfigCommon.bcch_Config.modificationPeriodCoeff = NR_BCCH_Config__modificationPeriodCoeff_n2;
  ServCellCom->downlinkConfigCommon.pcch_Config.defaultPagingCycle = NR_PagingCycle_rf256;
  ServCellCom->downlinkConfigCommon.pcch_Config.nAndPagingFrameOffset.present = NR_PCCH_Config__nAndPagingFrameOffset_PR_quarterT;
  ServCellCom->downlinkConfigCommon.pcch_Config.nAndPagingFrameOffset.choice.quarterT = 1;
  ServCellCom->downlinkConfigCommon.pcch_Config.ns = NR_PCCH_Config__ns_one;

  asn1cCalloc(ServCellCom->downlinkConfigCommon.pcch_Config.firstPDCCH_MonitoringOccasionOfPO, P0);
  P0->present = NR_PCCH_Config__firstPDCCH_MonitoringOccasionOfPO_PR_sCS120KHZoneT_SCS60KHZhalfT_SCS30KHZquarterT_SCS15KHZoneEighthT;

  asn1cCalloc(P0->choice.sCS120KHZoneT_SCS60KHZhalfT_SCS30KHZquarterT_SCS15KHZoneEighthT, Z8);
  asn1cSequenceAdd(Z8->list, long, ZoneEight);
  *ZoneEight = 0;

  asn1cCalloc(ServCellCom->uplinkConfigCommon, UL);
  asn_set_empty(&UL->frequencyInfoUL.scs_SpecificCarrierList.list);
  const NR_FrequencyInfoUL_t *frequencyInfoUL = scc->uplinkConfigCommon->frequencyInfoUL;
  for (int i = 0; i < frequencyInfoUL->scs_SpecificCarrierList.list.count; i++) {
    const NR_SCS_SpecificCarrier_t *orig = frequencyInfoUL->scs_SpecificCarrierList.list.array[i];
    NR_SCS_SpecificCarrier_t *new = NULL;
    const int copy_result = asn_copy(&asn_DEF_NR_SCS_SpecificCarrier, (void **)&new, orig);
    AssertFatal(copy_result == 0, "unable to copy NR_SCS_SpecificCarrier from scc to SIB1 structure\n");
    asn1cSeqAdd(&UL->frequencyInfoUL.scs_SpecificCarrierList.list, new);
  }

  asn1cCallocOne(UL->frequencyInfoUL.p_Max, *frequencyInfoUL->p_Max);

  frame_type_t frame_type =
      get_frame_type((int)*scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0],
                     *scc->ssbSubcarrierSpacing);

  if (frame_type == FDD) {
    UL->frequencyInfoUL.absoluteFrequencyPointA = malloc(sizeof(*UL->frequencyInfoUL.absoluteFrequencyPointA));
    AssertFatal(UL->frequencyInfoUL.absoluteFrequencyPointA != NULL, "out of memory\n");
    *UL->frequencyInfoUL.absoluteFrequencyPointA =
        *scc->uplinkConfigCommon->frequencyInfoUL->absoluteFrequencyPointA;
    UL->frequencyInfoUL.frequencyBandList = calloc(1, sizeof(*UL->frequencyInfoUL.frequencyBandList));
    AssertFatal(UL->frequencyInfoUL.frequencyBandList != NULL, "out of memory\n");
    for (int i = 0; i < frequencyInfoUL->frequencyBandList->list.count; i++) {
      asn1cSequenceAdd(UL->frequencyInfoUL.frequencyBandList->list, struct NR_NR_MultiBandInfo, nrMultiBandInfo);
      asn1cCallocOne(nrMultiBandInfo->freqBandIndicatorNR, *frequencyInfoUL->frequencyBandList->list.array[i]);
    }
  }

  UL->initialUplinkBWP.genericParameters = clone_generic_parameters(&scc->uplinkConfigCommon->initialUplinkBWP->genericParameters);
  UL->initialUplinkBWP.rach_ConfigCommon = clone_rach_configcommon(scc->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon);

  if (scc->uplinkConfigCommon->initialUplinkBWP->ext1) {
    NR_SetupRelease_MsgA_ConfigCommon_r16_t *msgA_configcommon =
        clone_msga_configcommon(scc->uplinkConfigCommon->initialUplinkBWP->ext1->msgA_ConfigCommon_r16);
    if (msgA_configcommon) {
      // Add the struct ext1
      UL->initialUplinkBWP.ext1 = calloc(1, sizeof(*UL->initialUplinkBWP.ext1));
      UL->initialUplinkBWP.ext1->msgA_ConfigCommon_r16 = msgA_configcommon;
    }
  }

  UL->initialUplinkBWP.pusch_ConfigCommon = clone_pusch_configcommon(scc->uplinkConfigCommon->initialUplinkBWP->pusch_ConfigCommon);
  free(UL->initialUplinkBWP.pusch_ConfigCommon->choice.setup->groupHoppingEnabledTransformPrecoding);
  UL->initialUplinkBWP.pusch_ConfigCommon->choice.setup->groupHoppingEnabledTransformPrecoding = NULL;

  UL->initialUplinkBWP.pucch_ConfigCommon = clone_pucch_configcommon(scc->uplinkConfigCommon->initialUplinkBWP->pucch_ConfigCommon);

  UL->timeAlignmentTimerCommon = NR_TimeAlignmentTimer_infinity;

  ServCellCom->n_TimingAdvanceOffset = scc->n_TimingAdvanceOffset;

  uint8_t bitmap8,temp_bitmap=0;
  switch (scc->ssb_PositionsInBurst->present) {
    case NR_ServingCellConfigCommon__ssb_PositionsInBurst_PR_shortBitmap:
      ServCellCom->ssb_PositionsInBurst.inOneGroup = bit_string_clone(&scc->ssb_PositionsInBurst->choice.shortBitmap);
      break;
    case NR_ServingCellConfigCommon__ssb_PositionsInBurst_PR_mediumBitmap:
      ServCellCom->ssb_PositionsInBurst.inOneGroup = bit_string_clone(&scc->ssb_PositionsInBurst->choice.mediumBitmap);
      break;
    /*
     * groupPresence: This field is present when maximum number of SS/PBCH blocks per half frame equals to 64 as defined in
     * TS 38.213 [13], clause 4.1. The first/leftmost bit corresponds to the SS/PBCH index 0-7, the second bit corresponds to
     * SS/PBCH block 8-15, and so on. Value 0 in the bitmap indicates that the SSBs according to inOneGroup are absent. Value 1
     * indicates that the SS/PBCH blocks are transmitted in accordance with inOneGroup. inOneGroup: When maximum number of SS/PBCH
     * blocks per half frame equals to 64 as defined in TS 38.213 [13], clause 4.1, all 8 bit are valid; The first/ leftmost bit
     * corresponds to the first SS/PBCH block index in the group (i.e., to SSB index 0, 8, and so on); the second bit corresponds to
     * the second SS/PBCH block index in the group (i.e., to SSB index 1, 9, and so on), and so on. Value 0 in the bitmap indicates
     * that the corresponding SS/PBCH block is not transmitted while value 1 indicates that the corresponding SS/PBCH block is
     * transmitted.
     */
    case NR_ServingCellConfigCommon__ssb_PositionsInBurst_PR_longBitmap:
      ServCellCom->ssb_PositionsInBurst.inOneGroup.buf = calloc_or_fail(1, sizeof(uint8_t));
      ServCellCom->ssb_PositionsInBurst.inOneGroup.size = 1;
      ServCellCom->ssb_PositionsInBurst.inOneGroup.bits_unused = 0;
      ServCellCom->ssb_PositionsInBurst.groupPresence = calloc(1, sizeof(BIT_STRING_t));
      AssertFatal(ServCellCom->ssb_PositionsInBurst.groupPresence != NULL, "out of memory\n");
      ServCellCom->ssb_PositionsInBurst.groupPresence->size = 1;
      ServCellCom->ssb_PositionsInBurst.groupPresence->bits_unused = 0;
      ServCellCom->ssb_PositionsInBurst.groupPresence->buf = calloc(1, sizeof(uint8_t));
      AssertFatal(ServCellCom->ssb_PositionsInBurst.groupPresence->buf != NULL, "out of memory\n");
      ServCellCom->ssb_PositionsInBurst.groupPresence->buf[0] = 0;
      for (int i = 0; i < 8; i++) {
        bitmap8 = scc->ssb_PositionsInBurst->choice.longBitmap.buf[i];
        if (bitmap8 != 0) {
          if (temp_bitmap == 0)
            temp_bitmap = bitmap8;
          else
            AssertFatal(temp_bitmap == bitmap8,
                        "For longBitmap the groups of 8 SSBs containing at least 1 transmitted SSB should be all the same\n");

          ServCellCom->ssb_PositionsInBurst.inOneGroup.buf[0] = bitmap8;
          ServCellCom->ssb_PositionsInBurst.groupPresence->buf[0] |= 1<<(7-i);
        }
      }
      break;
    default:
      AssertFatal(false, "ssb_PositionsInBurst not present\n");
      break;
  }

  ServCellCom->ssb_PeriodicityServingCell = *scc->ssb_periodicityServingCell;
  if (scc->tdd_UL_DL_ConfigurationCommon) {
    int copy_result = asn_copy(&asn_DEF_NR_TDD_UL_DL_ConfigCommon, (void **)&ServCellCom->tdd_UL_DL_ConfigurationCommon, scc->tdd_UL_DL_ConfigurationCommon);
    AssertFatal(copy_result == 0, "Was unable to copy tdd_UL_DL_ConfigurationCommon from scc to SIB19 structure\n");
  }
  ServCellCom->ss_PBCH_BlockPower = scc->ss_PBCH_BlockPower;

  // ims-EmergencySupport
  // TODO: add ims-EmergencySupport

  // eCallOverIMS-Support
  // TODO: add eCallOverIMS-Support

  // ue-TimersAndConstants
  sib1->ue_TimersAndConstants = CALLOC(1,sizeof(struct NR_UE_TimersAndConstants));
  AssertFatal(sib1->ue_TimersAndConstants != NULL, "out of memory\n");
  const nr_mac_timers_t *timer_config = &mac_config->timer_config;
  sib1->ue_TimersAndConstants->t300 = get_NR_UE_TimersAndConstants_t300(timer_config);
  sib1->ue_TimersAndConstants->t301 = get_NR_UE_TimersAndConstants_t301(timer_config);
  sib1->ue_TimersAndConstants->t310 = get_NR_UE_TimersAndConstants_t310(timer_config);
  sib1->ue_TimersAndConstants->n310 = get_NR_UE_TimersAndConstants_n310(timer_config);
  sib1->ue_TimersAndConstants->t311 = get_NR_UE_TimersAndConstants_t311(timer_config);
  sib1->ue_TimersAndConstants->n311 = get_NR_UE_TimersAndConstants_n311(timer_config);
  sib1->ue_TimersAndConstants->t319 = get_NR_UE_TimersAndConstants_t319(timer_config);

  // uac-BarringInfo
  /*sib1->uac_BarringInfo = CALLOC(1, sizeof(struct NR_SIB1__uac_BarringInfo));
  NR_UAC_BarringInfoSet_t *nr_uac_BarringInfoSet = CALLOC(1, sizeof(NR_UAC_BarringInfoSet_t));
  asn_set_empty(&sib1->uac_BarringInfo->uac_BarringInfoSetList);
  nr_uac_BarringInfoSet->uac_BarringFactor = NR_UAC_BarringInfoSet__uac_BarringFactor_p95;
  nr_uac_BarringInfoSet->uac_BarringTime = NR_UAC_BarringInfoSet__uac_BarringTime_s4;
  nr_uac_BarringInfoSet->uac_BarringForAccessIdentity.buf = CALLOC(1, 1);
  nr_uac_BarringInfoSet->uac_BarringForAccessIdentity.size = 1;
  nr_uac_BarringInfoSet->uac_BarringForAccessIdentity.bits_unused = 1;
  asn1cSeqAdd(&sib1->uac_BarringInfo->uac_BarringInfoSetList, nr_uac_BarringInfoSet);*/

  // useFullResumeID
  // TODO: add useFullResumeID

  // lateNonCriticalExtension
  // TODO: add lateNonCriticalExtension

  // nonCriticalExtension
  if (mac_config->redcap || (scc->ext2 && scc->ext2->ntn_Config_r17)) {
    sib1->nonCriticalExtension = calloc_or_fail(1, sizeof(*sib1->nonCriticalExtension));
    NR_SIB1_v1610_IEs_t *sib1_1610 = sib1->nonCriticalExtension;
    sib1_1610->nonCriticalExtension = calloc_or_fail(1, sizeof(*sib1_1610->nonCriticalExtension));
    NR_SIB1_v1630_IEs_t *sib1_1630 = sib1_1610->nonCriticalExtension;
    sib1_1630->nonCriticalExtension = calloc_or_fail(1, sizeof(*sib1_1630->nonCriticalExtension));
    NR_SIB1_v1700_IEs_t *sib1_1700 = sib1_1630->nonCriticalExtension;

    if (mac_config->redcap) {
      sib1_1700->redCap_ConfigCommon_r17 = calloc_or_fail(1, sizeof(*sib1_1700->redCap_ConfigCommon_r17));
      sib1_1700->redCap_ConfigCommon_r17->cellBarredRedCap_r17 =
          calloc_or_fail(1, sizeof(*sib1_1700->redCap_ConfigCommon_r17->cellBarredRedCap_r17));

      const nr_redcap_config_t *redcap_config = mac_config->redcap;
      struct NR_RedCap_ConfigCommonSIB_r17__cellBarredRedCap_r17 *CellBarredRedCap_r17 =
          sib1_1700->redCap_ConfigCommon_r17->cellBarredRedCap_r17;
      CellBarredRedCap_r17->cellBarredRedCap1Rx_r17 = redcap_config->cellBarredRedCap1Rx_r17;
      CellBarredRedCap_r17->cellBarredRedCap2Rx_r17 = redcap_config->cellBarredRedCap2Rx_r17;
      sib1_1700->intraFreqReselectionRedCap_r17 = calloc_or_fail(1, sizeof(*sib1_1700->intraFreqReselectionRedCap_r17));
      *sib1_1700->intraFreqReselectionRedCap_r17 = redcap_config->intraFreqReselectionRedCap_r17;
    }

    if (is_ntn_band(band)) {
      // If cell provides NTN access, set cellBarredNTN to notBarred.
      asn1cCallocOne(sib1_1700->cellBarredNTN_r17, NR_SIB1_v1700_IEs__cellBarredNTN_r17_notBarred);
    }
  }

  if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
    xer_fprint(stdout, &asn_DEF_NR_BCCH_DL_SCH_Message, sib1_message);
  }

  return sib1_message;
}

void free_SIB1_NR(NR_BCCH_DL_SCH_Message_t *sib1)
{
  ASN_STRUCT_FREE(asn_DEF_NR_BCCH_DL_SCH_Message, sib1);
}

int encode_SIB_NR(NR_BCCH_DL_SCH_Message_t *sib, uint8_t *buffer, int max_buffer_size)
{
  AssertFatal(max_buffer_size <= NR_MAX_SIB_LENGTH / 8,
              "Maximum buffer size too large: 3GPP TS 38.331 section 5.2.1 - The physical layer imposes a limit to the "
              "maximum size a SIB can take. The maximum SIB1 or SI message size is 2976 bits.\n");
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_BCCH_DL_SCH_Message, NULL, sib, buffer, max_buffer_size);
  AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded <= max_buffer_size * 8, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded);
  return (enc_rval.encoded + 7) / 8;
}

NR_SIB19_r17_t *get_SIB19_NR(const NR_ServingCellConfigCommon_t *scc)
{
  NR_SIB19_r17_t *sib19 = calloc(1, sizeof(*sib19));
  // use ntn-config from NR_ServingCellConfigCommon_t
  const int copy_result = asn_copy(&asn_DEF_NR_NTN_Config_r17, (void **) &sib19->ntn_Config_r17, scc->ext2->ntn_Config_r17);
  AssertFatal(copy_result == 0, "Was unable to copy ntn_Config_r17 from scc to SIB19 structure\n");
  return sib19;
}

void free_SIB19_NR(NR_BCCH_DL_SCH_Message_t *sib19)
{
  ASN_STRUCT_FREE(asn_DEF_NR_BCCH_DL_SCH_Message, sib19);
}

static NR_PhysicalCellGroupConfig_t *configure_phy_cellgroup(void)
{
  NR_PhysicalCellGroupConfig_t *physicalCellGroupConfig = calloc(1, sizeof(*physicalCellGroupConfig));
  AssertFatal(physicalCellGroupConfig != NULL, "Couldn't allocate physicalCellGroupConfig. Out of memory!\n");
  physicalCellGroupConfig->pdsch_HARQ_ACK_Codebook = NR_PhysicalCellGroupConfig__pdsch_HARQ_ACK_Codebook_dynamic;
  return physicalCellGroupConfig;
}

static long *get_sr_ProhibitTimer(const nr_mac_timers_t *timer_config)
{
  if (timer_config->sr_ProhibitTimer == 0)
    return NULL;

  long *ret = calloc(1, sizeof(*ret));
  switch (timer_config->sr_ProhibitTimer) {
    case 1:
      *ret = NR_SchedulingRequestToAddMod__sr_ProhibitTimer_ms1;
      break;
    case 2:
      *ret = NR_SchedulingRequestToAddMod__sr_ProhibitTimer_ms2;
      break;
    case 4:
      *ret = NR_SchedulingRequestToAddMod__sr_ProhibitTimer_ms4;
      break;
    case 8:
      *ret = NR_SchedulingRequestToAddMod__sr_ProhibitTimer_ms8;
      break;
    case 16:
      *ret = NR_SchedulingRequestToAddMod__sr_ProhibitTimer_ms16;
      break;
    case 32:
      *ret = NR_SchedulingRequestToAddMod__sr_ProhibitTimer_ms32;
      break;
    case 64:
      *ret = NR_SchedulingRequestToAddMod__sr_ProhibitTimer_ms64;
      break;
    case 128:
      *ret = NR_SchedulingRequestToAddMod__sr_ProhibitTimer_ms128;
      break;
    default:
      AssertFatal(1 == 0, "Invalid value configured for sr_ProhibitTimer!\n");
  }
  return ret;
}

static long get_sr_TransMax(const nr_mac_timers_t *timer_config)
{
  switch (timer_config->sr_TransMax) {
    case 4:
      return NR_SchedulingRequestToAddMod__sr_TransMax_n4;
    case 8:
      return NR_SchedulingRequestToAddMod__sr_TransMax_n8;
    case 16:
      return NR_SchedulingRequestToAddMod__sr_TransMax_n16;
    case 32:
      return NR_SchedulingRequestToAddMod__sr_TransMax_n32;
    case 64:
      return NR_SchedulingRequestToAddMod__sr_TransMax_n64;
    default:
      AssertFatal(1 == 0, "Invalid value configured for sr_TransMax!\n");
  }
}

static long *get_sr_ProhibitTimer_v1700(const nr_mac_timers_t *timer_config)
{
  if (timer_config->sr_ProhibitTimer_v1700 == 0)
    return NULL;

  long *ret = calloc(1, sizeof(*ret));
  switch (timer_config->sr_ProhibitTimer_v1700) {
    case 192:
      *ret = NR_SchedulingRequestToAddModExt_v1700__sr_ProhibitTimer_v1700_ms192;
      break;
    case 256:
      *ret = NR_SchedulingRequestToAddModExt_v1700__sr_ProhibitTimer_v1700_ms256;
      break;
    case 320:
      *ret = NR_SchedulingRequestToAddModExt_v1700__sr_ProhibitTimer_v1700_ms320;
      break;
    case 384:
      *ret = NR_SchedulingRequestToAddModExt_v1700__sr_ProhibitTimer_v1700_ms384;
      break;
    case 448:
      *ret = NR_SchedulingRequestToAddModExt_v1700__sr_ProhibitTimer_v1700_ms448;
      break;
    case 512:
      *ret = NR_SchedulingRequestToAddModExt_v1700__sr_ProhibitTimer_v1700_ms512;
      break;
    case 576:
      *ret = NR_SchedulingRequestToAddModExt_v1700__sr_ProhibitTimer_v1700_ms576;
      break;
    case 640:
      *ret = NR_SchedulingRequestToAddModExt_v1700__sr_ProhibitTimer_v1700_ms640;
      break;
    case 1082:
      *ret = NR_SchedulingRequestToAddModExt_v1700__sr_ProhibitTimer_v1700_ms1082;
      break;
    default:
      AssertFatal(1 == 0, "Invalid value configured for sr_ProhibitTimer_v1700!\n");
  }
  return ret;
}

static NR_MAC_CellGroupConfig_t *configure_mac_cellgroup(const nr_mac_timers_t *timer_config)
{
  NR_MAC_CellGroupConfig_t * mac_CellGroupConfig = calloc(1, sizeof(*mac_CellGroupConfig));
  AssertFatal(mac_CellGroupConfig != NULL, "Couldn't allocate mac-CellGroupConfig. Out of memory!\n");
  mac_CellGroupConfig->bsr_Config = calloc(1, sizeof(*mac_CellGroupConfig->bsr_Config));
  mac_CellGroupConfig->bsr_Config->periodicBSR_Timer = NR_BSR_Config__periodicBSR_Timer_sf5;
  mac_CellGroupConfig->bsr_Config->retxBSR_Timer = NR_BSR_Config__retxBSR_Timer_sf80;
  mac_CellGroupConfig->tag_Config = calloc(1, sizeof(*mac_CellGroupConfig->tag_Config));
  mac_CellGroupConfig->tag_Config->tag_ToReleaseList = NULL;
  mac_CellGroupConfig->tag_Config->tag_ToAddModList = calloc(1,sizeof(*mac_CellGroupConfig->tag_Config->tag_ToAddModList));
  struct NR_TAG *tag=calloc(1,sizeof(*tag));
  tag->tag_Id = 0;
  tag->timeAlignmentTimer = NR_TimeAlignmentTimer_infinity;
  asn1cSeqAdd(&mac_CellGroupConfig->tag_Config->tag_ToAddModList->list,tag);

  set_phr_config(mac_CellGroupConfig);

  mac_CellGroupConfig->schedulingRequestConfig = calloc(1, sizeof(*mac_CellGroupConfig->schedulingRequestConfig));
  mac_CellGroupConfig->schedulingRequestConfig->schedulingRequestToAddModList = CALLOC(1,sizeof(*mac_CellGroupConfig->schedulingRequestConfig->schedulingRequestToAddModList));
  struct NR_SchedulingRequestToAddMod *schedulingrequestlist = CALLOC(1,sizeof(*schedulingrequestlist));
  schedulingrequestlist->schedulingRequestId = 0;
  schedulingrequestlist->sr_ProhibitTimer = get_sr_ProhibitTimer(timer_config);
  schedulingrequestlist->sr_TransMax = get_sr_TransMax(timer_config);
  asn1cSeqAdd(&(mac_CellGroupConfig->schedulingRequestConfig->schedulingRequestToAddModList->list),schedulingrequestlist);

  if (timer_config->sr_ProhibitTimer_v1700 != 0) {
    mac_CellGroupConfig->ext4 = calloc(1, sizeof(*mac_CellGroupConfig->ext4));
    mac_CellGroupConfig->ext4->schedulingRequestConfig_v1700 =
        calloc(1, sizeof(*mac_CellGroupConfig->ext4->schedulingRequestConfig_v1700));
    mac_CellGroupConfig->ext4->schedulingRequestConfig_v1700->schedulingRequestToAddModListExt_v1700 =
        calloc(1, sizeof(*mac_CellGroupConfig->ext4->schedulingRequestConfig_v1700->schedulingRequestToAddModListExt_v1700));
    struct NR_SchedulingRequestToAddModExt_v1700 *schedulingrequestlist_v1700 = calloc(1, sizeof(*schedulingrequestlist_v1700));
    schedulingrequestlist_v1700->sr_ProhibitTimer_v1700 = get_sr_ProhibitTimer_v1700(timer_config);
    asn1cSeqAdd(&(mac_CellGroupConfig->ext4->schedulingRequestConfig_v1700->schedulingRequestToAddModListExt_v1700->list),
                schedulingrequestlist_v1700);
  }

  mac_CellGroupConfig->skipUplinkTxDynamic=false;
  mac_CellGroupConfig->ext1 = NULL;
  return mac_CellGroupConfig;
}

static void allocate_32_harq(NR_PDSCH_Config_t *pdsch_Config)
{
  if (!pdsch_Config->ext3)
    pdsch_Config->ext3 = calloc(1, sizeof(*pdsch_Config->ext3));
  asn1cCallocOne(pdsch_Config->ext3->harq_ProcessNumberSizeDCI_1_1_r17, 5);
}

static void allocate_32_ul_harq(NR_PUSCH_Config_t *pusch_Config)
{
  if (!pusch_Config->ext2)
    pusch_Config->ext2 = calloc(1, sizeof(*pusch_Config->ext2));
  asn1cCallocOne(pusch_Config->ext2->harq_ProcessNumberSizeDCI_0_1_r17, 5);
}

// Set HARQ related IEs according to the number of DL, UL harqprocesses configured
static void fill_harq_IEs(NR_ServingCellConfig_t *scc, int num_dlharq, int num_ulharq, int bwp_id)
{
  AssertFatal(scc
              && scc->pdsch_ServingCellConfig
              && scc->pdsch_ServingCellConfig->present == NR_SetupRelease_PDSCH_ServingCellConfig_PR_setup,
              "PDSCH_Servingcellconfig IEs NOT present\n");
  NR_PDSCH_ServingCellConfig_t *pdsch_scc = scc->pdsch_ServingCellConfig->choice.setup;
  switch (num_dlharq) {
    case 2:
      *pdsch_scc->nrofHARQ_ProcessesForPDSCH = NR_PDSCH_ServingCellConfig__nrofHARQ_ProcessesForPDSCH_n2;
      break;
    case 4:
      *pdsch_scc->nrofHARQ_ProcessesForPDSCH = NR_PDSCH_ServingCellConfig__nrofHARQ_ProcessesForPDSCH_n4;
      break;
    case 6:
      *pdsch_scc->nrofHARQ_ProcessesForPDSCH = NR_PDSCH_ServingCellConfig__nrofHARQ_ProcessesForPDSCH_n6;
      break;
    case 8:
      // 8 if IEs nrofHARQ_ProcessesForPDSCH and nrofHARQ_ProcessesForPDSCH_v1700 are not present
      free_and_zero(pdsch_scc->nrofHARQ_ProcessesForPDSCH);
      break;
    case 10:
      *pdsch_scc->nrofHARQ_ProcessesForPDSCH = NR_PDSCH_ServingCellConfig__nrofHARQ_ProcessesForPDSCH_n10;
      break;
    case 12:
      *pdsch_scc->nrofHARQ_ProcessesForPDSCH = NR_PDSCH_ServingCellConfig__nrofHARQ_ProcessesForPDSCH_n12;
      break;
    case 16:
      *pdsch_scc->nrofHARQ_ProcessesForPDSCH = NR_PDSCH_ServingCellConfig__nrofHARQ_ProcessesForPDSCH_n16;
      break;
    case 32:
      if (!pdsch_scc->ext3)
        pdsch_scc->ext3 = calloc(1, sizeof(*pdsch_scc->ext3));
      asn1cCallocOne(pdsch_scc->ext3->nrofHARQ_ProcessesForPDSCH_v1700,
                     NR_PDSCH_ServingCellConfig__ext3__nrofHARQ_ProcessesForPDSCH_v1700_n32);
      NR_BWP_DownlinkDedicated_t *dlbwp = NULL;
      if (bwp_id == 0) {
        dlbwp = scc->initialDownlinkBWP;
        if (dlbwp && dlbwp->pdsch_Config && dlbwp->pdsch_Config->present == NR_SetupRelease_PDSCH_Config_PR_setup)
          allocate_32_harq(scc->initialDownlinkBWP->pdsch_Config->choice.setup);
      } else {
        for (int i = 0; i < scc->downlinkBWP_ToAddModList->list.count; i++) {
          if (scc->downlinkBWP_ToAddModList->list.array[i]->bwp_Id != bwp_id)
            continue;
          dlbwp = scc->downlinkBWP_ToAddModList->list.array[i]->bwp_Dedicated;
          if (dlbwp && dlbwp->pdsch_Config && dlbwp->pdsch_Config->present == NR_SetupRelease_PDSCH_Config_PR_setup)
            allocate_32_harq(dlbwp->pdsch_Config->choice.setup);
        }
      }
      break;
    default: // Already IE should have been set to 16 harq processes
      AssertFatal(false, "Invalid number of HARQ processes\n");
      break;
  }

  AssertFatal(scc->uplinkConfig, "uplinkConfig IE NOT present\n");

  if (num_ulharq == 32) {
    if (!scc->uplinkConfig->pusch_ServingCellConfig)
      scc->uplinkConfig->pusch_ServingCellConfig = calloc(1, sizeof(*scc->uplinkConfig->pusch_ServingCellConfig));
    scc->uplinkConfig->pusch_ServingCellConfig->present = NR_SetupRelease_PUSCH_ServingCellConfig_PR_setup;
    if (!scc->uplinkConfig->pusch_ServingCellConfig->choice.setup)
      scc->uplinkConfig->pusch_ServingCellConfig->choice.setup = calloc(1, sizeof(NR_PUSCH_ServingCellConfig_t));
    NR_PUSCH_ServingCellConfig_t *pusch_scc = scc->uplinkConfig->pusch_ServingCellConfig->choice.setup;
    if (!pusch_scc->ext3)
      pusch_scc->ext3 = calloc(1, sizeof(*pusch_scc->ext3));
    asn1cCallocOne(pusch_scc->ext3->nrofHARQ_ProcessesForPUSCH_r17,
                   NR_PUSCH_ServingCellConfig__ext3__nrofHARQ_ProcessesForPUSCH_r17_n32);
    NR_BWP_UplinkDedicated_t *ulbwp = NULL;
    if (bwp_id == 0) {
      ulbwp = scc->uplinkConfig->initialUplinkBWP;
      if (ulbwp && ulbwp->pusch_Config && ulbwp->pusch_Config->present == NR_SetupRelease_PUSCH_Config_PR_setup)
        allocate_32_ul_harq(ulbwp->pusch_Config->choice.setup);
    } else {
      for (int i = 0; i < scc->uplinkConfig->uplinkBWP_ToAddModList->list.count; i++) {
        if (bwp_id != scc->uplinkConfig->uplinkBWP_ToAddModList->list.array[i]->bwp_Id)
          continue;
        ulbwp = scc->uplinkConfig->uplinkBWP_ToAddModList->list.array[i]->bwp_Dedicated;
        if (ulbwp && ulbwp->pusch_Config && ulbwp->pusch_Config->present == NR_SetupRelease_PUSCH_Config_PR_setup)
          allocate_32_ul_harq(ulbwp->pusch_Config->choice.setup);
      }
    }
  }
}

static NR_BWP_UplinkDedicated_t *configure_initial_ul_bwp(const NR_ServingCellConfigCommon_t *scc,
                                                          const nr_mac_config_t *configuration,
                                                          int maxMIMO_Layers,
                                                          const NR_UE_NR_Capability_t *uecap,
                                                          int id)
{
  NR_BWP_UplinkDedicated_t *initialUplinkBWP = calloc(1, sizeof(*initialUplinkBWP));
  NR_BWP_t *genericParameters = &scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters;
  int curr_bwp = NRRIV2BW(genericParameters->locationAndBandwidth, MAX_BWP_SIZE);
  initialUplinkBWP->pucch_Config = calloc(1, sizeof(*initialUplinkBWP->pucch_Config));
  initialUplinkBWP->pucch_Config->present = NR_SetupRelease_PUCCH_Config_PR_setup;
  NR_PUCCH_Config_t *pucch_Config = calloc(1, sizeof(*pucch_Config));
  initialUplinkBWP->pucch_Config->choice.setup = pucch_Config;
  pucch_Config->resourceSetToAddModList = calloc(1, sizeof(*pucch_Config->resourceSetToAddModList));
  pucch_Config->resourceSetToReleaseList = NULL;
  pucch_Config->resourceToAddModList = calloc(1, sizeof(*pucch_Config->resourceToAddModList));
  pucch_Config->resourceToReleaseList = NULL;
  config_pucch_resset0(scc, pucch_Config, id, curr_bwp, uecap, &configuration->pdsch_AntennaPorts);
  config_pucch_resset1(scc, pucch_Config, id, curr_bwp, uecap, &configuration->pdsch_AntennaPorts);
  set_pucch_power_config(pucch_Config);

  initialUplinkBWP->pusch_Config = config_pusch(configuration, scc, uecap);

  // We are using do_srs = 0 here because the periodic SRS will only be enabled in update_cellGroupConfig() if do_srs == 1
  initialUplinkBWP->srs_Config = get_config_srs(uecap, curr_bwp, id, 0, maxMIMO_Layers, configuration->minRXTXTIME, 0);

  scheduling_request_config(pucch_Config, scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.subcarrierSpacing);
  set_dl_DataToUL_ACK(pucch_Config, configuration->minRXTXTIME);
  return initialUplinkBWP;
}

static NR_BWP_DownlinkDedicated_t *configure_initial_dl_bwp(const NR_ServingCellConfigCommon_t *scc,
                                                            const int pdsch_AntennaPorts,
                                                            uint64_t bitmap,
                                                            const NR_UE_NR_Capability_t *uecap,
                                                            const nr_mac_config_t *configuration)
{
  NR_BWP_DownlinkDedicated_t *bwp_Dedicated = calloc(1, sizeof(*bwp_Dedicated));
  bwp_Dedicated->pdcch_Config = calloc(1, sizeof(*bwp_Dedicated->pdcch_Config));
  bwp_Dedicated->pdcch_Config->present = NR_SetupRelease_PDCCH_Config_PR_setup;
  NR_PDCCH_Config_t *pdcch_Config = calloc(1, sizeof(*pdcch_Config));
  pdcch_Config->searchSpacesToAddModList = calloc(1, sizeof(*pdcch_Config->searchSpacesToAddModList));
  pdcch_Config->controlResourceSetToAddModList = calloc(1, sizeof(*pdcch_Config->controlResourceSetToAddModList));
  NR_BWP_t *genericParameters = &scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters;
  int bwp_size = NRRIV2BW(genericParameters->locationAndBandwidth, MAX_BWP_SIZE);
  int bwp_start = NRRIV2PRBOFFSET(genericParameters->locationAndBandwidth, MAX_BWP_SIZE);
  NR_ControlResourceSet_t *coreset = get_coreset_config(0, 0, 0, bwp_start, bwp_size, bitmap, configuration->do_TCI);
  asn1cSeqAdd(&pdcch_Config->controlResourceSetToAddModList->list, coreset);

  int css_num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS];
  css_num_agg_level_candidates[PDCCH_AGG_LEVEL1] = NR_SearchSpace__nrofCandidates__aggregationLevel1_n0;
  css_num_agg_level_candidates[PDCCH_AGG_LEVEL2] = NR_SearchSpace__nrofCandidates__aggregationLevel2_n0;
  css_num_agg_level_candidates[PDCCH_AGG_LEVEL4] = NR_SearchSpace__nrofCandidates__aggregationLevel4_n1;
  css_num_agg_level_candidates[PDCCH_AGG_LEVEL8] = NR_SearchSpace__nrofCandidates__aggregationLevel8_n0;
  css_num_agg_level_candidates[PDCCH_AGG_LEVEL16] = NR_SearchSpace__nrofCandidates__aggregationLevel16_n0;
  int searchspaceid = 4;
  NR_SearchSpace_t *ss = rrc_searchspace_config(true, searchspaceid, 0, css_num_agg_level_candidates);
  searchspaceid = 5;
  int rrc_num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS];
  int num_cces = get_coreset_num_cces(coreset->frequencyDomainResources.buf, coreset->duration);
  verify_agg_levels(num_cces, configuration->num_agg_level_candidates, coreset->controlResourceSetId, rrc_num_agg_level_candidates);
  NR_SearchSpace_t *ss2 = rrc_searchspace_config(false, searchspaceid, coreset->controlResourceSetId, rrc_num_agg_level_candidates);
  asn1cSeqAdd(&pdcch_Config->searchSpacesToAddModList->list, ss);
  asn1cSeqAdd(&pdcch_Config->searchSpacesToAddModList->list, ss2);
  bwp_Dedicated->pdcch_Config->choice.setup = pdcch_Config;

  bwp_Dedicated->pdsch_Config = config_pdsch(bitmap, 0, configuration->do_TCI, configuration->ptrs);
  // we might call configuration of initial BWP for BWP switch when we already have UE capabilities
  set_dl_mcs_table(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.subcarrierSpacing,
                   configuration->force_256qam_off ? NULL : uecap,
                   bwp_Dedicated,
                   scc);
  return bwp_Dedicated;
}

static NR_CSI_MeasConfig_t *get_csiMeasConfig(const NR_ServingCellConfig_t *configDedicated,
                                              const NR_UE_NR_Capability_t *uecap,
                                              const NR_ServingCellConfigCommon_t *scc,
                                              const nr_mac_config_t *configuration,
                                              int uid,
                                              int bwp_id,
                                              uint64_t bitmap,
                                              int ssb_index)
{
  NR_CSI_MeasConfig_t *csi_MeasConfig = calloc(1, sizeof(*csi_MeasConfig));
  csi_MeasConfig->csi_SSB_ResourceSetToAddModList = calloc(1, sizeof(*csi_MeasConfig->csi_SSB_ResourceSetToAddModList));
  csi_MeasConfig->csi_SSB_ResourceSetToReleaseList = NULL;
  csi_MeasConfig->csi_ResourceConfigToAddModList = calloc(1, sizeof(*csi_MeasConfig->csi_ResourceConfigToAddModList));
  csi_MeasConfig->csi_ResourceConfigToReleaseList = NULL;
  csi_MeasConfig->csi_ReportConfigToAddModList = calloc(1, sizeof(*csi_MeasConfig->csi_ReportConfigToAddModList));
  csi_MeasConfig->csi_ReportConfigToReleaseList = NULL;

  NR_CSI_SSB_ResourceSet_t *ssbresset0 = calloc(1, sizeof(*ssbresset0));
  ssbresset0->csi_SSB_ResourceSetId = 0;

  for (int i = 0; i < 64; i++) {
    if ((bitmap >> (63 - i)) & 0x01) {
      NR_SSB_Index_t *ssbres = NULL;
      asn1cCallocOne(ssbres, i);
      asn1cSeqAdd(&ssbresset0->csi_SSB_ResourceList.list, ssbres);
    }
  }
  asn1cSeqAdd(&csi_MeasConfig->csi_SSB_ResourceSetToAddModList->list, ssbresset0);

  int curr_bwp;
  NR_SetupRelease_PDSCH_Config_t *pdsch_Config;
  if (bwp_id == 0) {
    pdsch_Config = configDedicated->initialDownlinkBWP->pdsch_Config;
    curr_bwp = NRRIV2BW(scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  } else {
    NR_BWP_Downlink_t *bwp = NULL;
    for (int i = 0; i < configDedicated->downlinkBWP_ToAddModList->list.count; i++) {
      if (bwp_id == configDedicated->downlinkBWP_ToAddModList->list.array[i]->bwp_Id)
        bwp = configDedicated->downlinkBWP_ToAddModList->list.array[i];
    }
    AssertFatal(bwp, "BWP ID doesn't match\n");
    pdsch_Config = bwp->bwp_Dedicated->pdsch_Config;
    curr_bwp = NRRIV2BW(bwp->bwp_Common->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  }

  const int pdsch_AntennaPorts =
      configuration->pdsch_AntennaPorts.N1 * configuration->pdsch_AntennaPorts.N2 * configuration->pdsch_AntennaPorts.XP;
  config_csirs(scc, csi_MeasConfig, pdsch_AntennaPorts, curr_bwp, configuration->do_CSIRS, ssb_index / 2);
  config_csiim(configuration->do_CSIRS, pdsch_AntennaPorts, curr_bwp, csi_MeasConfig, ssb_index / 2);

  NR_CSI_ResourceConfig_t *csires1 = calloc(1, sizeof(*csires1));
  csires1->csi_ResourceConfigId = bwp_id + 20;
  csires1->csi_RS_ResourceSetList.present = NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB;
  csires1->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB =
      calloc(1, sizeof(*csires1->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB));
  csires1->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->csi_SSB_ResourceSetList =
      calloc(1, sizeof(*csires1->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->csi_SSB_ResourceSetList));
  NR_CSI_SSB_ResourceSetId_t *ssbres00 = calloc(1, sizeof(*ssbres00));
  *ssbres00 = 0;
  asn1cSeqAdd(&csires1->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->csi_SSB_ResourceSetList->list, ssbres00);
  csires1->bwp_Id = bwp_id;
  csires1->resourceType = NR_CSI_ResourceConfig__resourceType_periodic;
  asn1cSeqAdd(&csi_MeasConfig->csi_ResourceConfigToAddModList->list, csires1);

  int pucch_Resource = 2;
  if (configuration->do_CSIRS) {
    NR_CSI_ResourceConfig_t *csires0 = calloc(1, sizeof(*csires0));
    csires0->csi_ResourceConfigId = bwp_id;
    csires0->csi_RS_ResourceSetList.present = NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB;
    csires0->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB =
        calloc(1, sizeof(*csires0->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB));
    csires0->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList =
        calloc(1, sizeof(*csires0->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList));
    NR_NZP_CSI_RS_ResourceSetId_t *nzp0 = calloc(1, sizeof(*nzp0));
    *nzp0 = ssb_index / 2;
    asn1cSeqAdd(&csires0->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList->list, nzp0);
    csires0->bwp_Id = bwp_id;
    csires0->resourceType = NR_CSI_ResourceConfig__resourceType_periodic;
    asn1cSeqAdd(&csi_MeasConfig->csi_ResourceConfigToAddModList->list, csires0);
  }

  if (configuration->do_CSIRS && pdsch_AntennaPorts > 1) {
    NR_CSI_ResourceConfig_t *csires2 = calloc(1, sizeof(*csires2));
    csires2->csi_ResourceConfigId = bwp_id + 10;
    csires2->csi_RS_ResourceSetList.present = NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_csi_IM_ResourceSetList;
    csires2->csi_RS_ResourceSetList.choice.csi_IM_ResourceSetList =
        calloc(1, sizeof(*csires2->csi_RS_ResourceSetList.choice.csi_IM_ResourceSetList));
    NR_CSI_IM_ResourceSetId_t *csiim00 = calloc(1, sizeof(*csiim00));
    *csiim00 = ssb_index / 2;
    asn1cSeqAdd(&csires2->csi_RS_ResourceSetList.choice.csi_IM_ResourceSetList->list, csiim00);
    csires2->bwp_Id = bwp_id;
    csires2->resourceType = NR_CSI_ResourceConfig__resourceType_periodic;
    asn1cSeqAdd(&csi_MeasConfig->csi_ResourceConfigToAddModList->list, csires2);

    NR_PUCCH_CSI_Resource_t *pucchcsi = calloc(1, sizeof(*pucchcsi));
    pucchcsi->uplinkBandwidthPartId = bwp_id;
    pucchcsi->pucch_Resource = pucch_Resource;
    config_csi_meas_report(csi_MeasConfig,
                           scc,
                           pucchcsi,
                           pdsch_Config,
                           &configuration->pdsch_AntennaPorts,
                           *configDedicated->pdsch_ServingCellConfig->choice.setup->ext1->maxMIMO_Layers,
                           bwp_id,
                           uid,
                           curr_bwp);
  }
  NR_PUCCH_CSI_Resource_t *pucchrsrp = calloc(1, sizeof(*pucchrsrp));
  pucchrsrp->uplinkBandwidthPartId = bwp_id;
  pucchrsrp->pucch_Resource = pucch_Resource;
  config_rsrp_meas_report(csi_MeasConfig,
                          uecap,
                          scc,
                          pucchrsrp,
                          configuration,
                          bwp_id + 10,
                          uid,
                          curr_bwp,
                          bitmap);
  return csi_MeasConfig;
}

static NR_SpCellConfig_t *get_initial_SpCellConfig(int uid,
                                                   const NR_ServingCellConfigCommon_t *scc,
                                                   const nr_mac_config_t *configuration,
                                                   int ssb_index)
{
  const int pdsch_AntennaPorts =
      configuration->pdsch_AntennaPorts.N1 * configuration->pdsch_AntennaPorts.N2 * configuration->pdsch_AntennaPorts.XP;
  NR_SpCellConfig_t *SpCellConfig = calloc(1, sizeof(*SpCellConfig));
  SpCellConfig->servCellIndex = NULL;
  SpCellConfig->reconfigurationWithSync = NULL;
  SpCellConfig->rlmInSyncOutOfSyncThreshold = NULL;
  SpCellConfig->rlf_TimersAndConstants = NULL;

  NR_ServingCellConfig_t *configDedicated = calloc(1, sizeof(*configDedicated));
  configDedicated->uplinkConfig = calloc(1, sizeof(*configDedicated->uplinkConfig));
  NR_UplinkConfig_t *uplinkConfig = configDedicated->uplinkConfig;
  long maxMIMO_Layers = uplinkConfig
                        && uplinkConfig->pusch_ServingCellConfig
                        && uplinkConfig->pusch_ServingCellConfig->choice.setup->ext1
                        && uplinkConfig->pusch_ServingCellConfig->choice.setup->ext1->maxMIMO_Layers
                        ? *uplinkConfig->pusch_ServingCellConfig->choice.setup->ext1->maxMIMO_Layers
                        : 1;

  configDedicated->tag_Id = 0;
  configDedicated->pdsch_ServingCellConfig = calloc(1, sizeof(*configDedicated->pdsch_ServingCellConfig));
  NR_PDSCH_ServingCellConfig_t *pdsch_servingcellconfig = calloc(1, sizeof(*pdsch_servingcellconfig));
  configDedicated->pdsch_ServingCellConfig->present = NR_SetupRelease_PDSCH_ServingCellConfig_PR_setup;
  configDedicated->pdsch_ServingCellConfig->choice.setup = pdsch_servingcellconfig;

  pdsch_servingcellconfig->codeBlockGroupTransmission = NULL;
  pdsch_servingcellconfig->xOverhead = NULL;
  asn1cCallocOne(pdsch_servingcellconfig->nrofHARQ_ProcessesForPDSCH, NR_PDSCH_ServingCellConfig__nrofHARQ_ProcessesForPDSCH_n16);
  pdsch_servingcellconfig->pucch_Cell = NULL;
  set_dl_maxmimolayers(pdsch_servingcellconfig, scc, NULL, configuration->maxMIMO_layers);

  uint64_t bitmap = get_ssb_bitmap(scc);
  int first_active_bwp = 0;
  if (configuration->num_additional_bwps > 0)
    first_active_bwp = configuration->first_active_bwp > 0 ? 1 : 0;

  asn1cCallocOne(configDedicated->firstActiveDownlinkBWP_Id, first_active_bwp);
  asn1cCallocOne(uplinkConfig->firstActiveUplinkBWP_Id, first_active_bwp);
  if (first_active_bwp == 0) {
    uplinkConfig->initialUplinkBWP = configure_initial_ul_bwp(scc, configuration, maxMIMO_Layers, NULL, uid);
    configDedicated->initialDownlinkBWP = configure_initial_dl_bwp(scc, pdsch_AntennaPorts, bitmap, NULL, configuration);
  } else {
    configDedicated->downlinkBWP_ToAddModList = calloc(1, sizeof(*configDedicated->downlinkBWP_ToAddModList));
    NR_BWP_Downlink_t *bwp = config_downlinkBWP(scc, NULL, false, true, configuration);
    asn1cSeqAdd(&configDedicated->downlinkBWP_ToAddModList->list, bwp);
    uplinkConfig->uplinkBWP_ToAddModList = calloc(1, sizeof(*uplinkConfig->uplinkBWP_ToAddModList));
    NR_BWP_Uplink_t *ubwp = config_uplinkBWP(true, uid, maxMIMO_Layers, configuration, scc, NULL);
    asn1cSeqAdd(&uplinkConfig->uplinkBWP_ToAddModList->list, ubwp);
  }

  configDedicated->csi_MeasConfig = calloc(1, sizeof(*configDedicated->csi_MeasConfig));
  configDedicated->csi_MeasConfig->present = NR_SetupRelease_CSI_MeasConfig_PR_setup;
  configDedicated->csi_MeasConfig->choice.setup = get_csiMeasConfig(configDedicated,
                                                                    NULL,
                                                                    scc,
                                                                    configuration,
                                                                    uid,
                                                                    first_active_bwp,
                                                                    bitmap,
                                                                    ssb_index);

  fill_harq_IEs(configDedicated, configuration->num_dlharq, configuration->num_ulharq, first_active_bwp);
  SpCellConfig->spCellConfigDedicated = configDedicated;

  if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
    xer_fprint(stdout, &asn_DEF_NR_SpCellConfig, SpCellConfig);
  }
  return SpCellConfig;
}

struct NR_RLC_Config *nr_srb_config(const nr_rlc_configuration_t *default_rlc_config)
{
  NR_RLC_Config_t *rlc_Config = calloc(1, sizeof(NR_RLC_Config_t));
  rlc_Config->present = NR_RLC_Config_PR_am;
  rlc_Config->choice.am = calloc(1, sizeof(*rlc_Config->choice.am));
  rlc_Config->choice.am->dl_AM_RLC.sn_FieldLength = calloc(1, sizeof(NR_SN_FieldLengthAM_t));
  *(rlc_Config->choice.am->dl_AM_RLC.sn_FieldLength) = encode_sn_field_length_am(default_rlc_config->srb.sn_field_length);
  rlc_Config->choice.am->dl_AM_RLC.t_Reassembly = encode_t_reassembly(default_rlc_config->srb.t_reassembly);
  rlc_Config->choice.am->dl_AM_RLC.t_StatusProhibit = encode_t_status_prohibit(default_rlc_config->srb.t_status_prohibit);
  rlc_Config->choice.am->ul_AM_RLC.sn_FieldLength = calloc(1, sizeof(NR_SN_FieldLengthAM_t));
  *(rlc_Config->choice.am->ul_AM_RLC.sn_FieldLength) = encode_sn_field_length_am(default_rlc_config->srb.sn_field_length);
  rlc_Config->choice.am->ul_AM_RLC.t_PollRetransmit = encode_t_poll_retransmit(default_rlc_config->srb.t_poll_retransmit);
  rlc_Config->choice.am->ul_AM_RLC.pollPDU = encode_poll_pdu(default_rlc_config->srb.poll_pdu);
  rlc_Config->choice.am->ul_AM_RLC.pollByte = encode_poll_byte(default_rlc_config->srb.poll_byte);
  rlc_Config->choice.am->ul_AM_RLC.maxRetxThreshold = encode_max_retx_threshold(default_rlc_config->srb.max_retx_threshold);
  return rlc_Config;
}

NR_RLC_BearerConfig_t *get_SRB_RLC_BearerConfig(long channelId,
                                                long priority,
                                                long bucketSizeDuration,
                                                const nr_rlc_configuration_t *default_rlc_config)
{
  NR_RLC_BearerConfig_t *rlc_BearerConfig = NULL;
  rlc_BearerConfig                                                 = calloc(1, sizeof(NR_RLC_BearerConfig_t));
  rlc_BearerConfig->logicalChannelIdentity                         = channelId;
  rlc_BearerConfig->servedRadioBearer                              = calloc(1, sizeof(*rlc_BearerConfig->servedRadioBearer));
  rlc_BearerConfig->servedRadioBearer->present                     = NR_RLC_BearerConfig__servedRadioBearer_PR_srb_Identity;
  rlc_BearerConfig->servedRadioBearer->choice.srb_Identity         = channelId;
  rlc_BearerConfig->reestablishRLC                                 = NULL;

  rlc_BearerConfig->rlc_Config = nr_srb_config(default_rlc_config);

  NR_LogicalChannelConfig_t *logicalChannelConfig                  = calloc(1, sizeof(NR_LogicalChannelConfig_t));
  logicalChannelConfig->ul_SpecificParameters                      = calloc(1, sizeof(*logicalChannelConfig->ul_SpecificParameters));
  logicalChannelConfig->ul_SpecificParameters->priority            = priority;
  logicalChannelConfig->ul_SpecificParameters->prioritisedBitRate  = NR_LogicalChannelConfig__ul_SpecificParameters__prioritisedBitRate_infinity;
  logicalChannelConfig->ul_SpecificParameters->bucketSizeDuration  = bucketSizeDuration;

  long *logicalChannelGroup                                        = CALLOC(1, sizeof(long));
  *logicalChannelGroup                                             = 0;
  logicalChannelConfig->ul_SpecificParameters->logicalChannelGroup = logicalChannelGroup;
  logicalChannelConfig->ul_SpecificParameters->schedulingRequestID = CALLOC(1, sizeof(*logicalChannelConfig->ul_SpecificParameters->schedulingRequestID));
  *logicalChannelConfig->ul_SpecificParameters->schedulingRequestID = 0;
  logicalChannelConfig->ul_SpecificParameters->logicalChannelSR_Mask = 0;
  logicalChannelConfig->ul_SpecificParameters->logicalChannelSR_DelayTimerApplied = 0;
  rlc_BearerConfig->mac_LogicalChannelConfig                       = logicalChannelConfig;

  return rlc_BearerConfig;
}

struct NR_RLC_Config *nr_drb_config(NR_RLC_Config_PR rlc_config_pr, const nr_rlc_configuration_t *default_rlc_config)
{
  NR_RLC_Config_t *rlc_Config = calloc(1, sizeof(NR_RLC_Config_t));
  switch (rlc_config_pr) {
    case NR_RLC_Config_PR_um_Bi_Directional:
      // RLC UM Bi-directional Bearer configuration
      LOG_I(RLC, "RLC UM Bi-directional Bearer configuration selected \n");
      rlc_Config->choice.um_Bi_Directional = calloc(1, sizeof(*rlc_Config->choice.um_Bi_Directional));
      rlc_Config->choice.um_Bi_Directional->ul_UM_RLC.sn_FieldLength =
          calloc(1, sizeof(*rlc_Config->choice.um_Bi_Directional->ul_UM_RLC.sn_FieldLength));
      *rlc_Config->choice.um_Bi_Directional->ul_UM_RLC.sn_FieldLength = encode_sn_field_length_um(default_rlc_config->drb_um.sn_field_length);
      rlc_Config->choice.um_Bi_Directional->dl_UM_RLC.sn_FieldLength =
          calloc(1, sizeof(*rlc_Config->choice.um_Bi_Directional->dl_UM_RLC.sn_FieldLength));
      *rlc_Config->choice.um_Bi_Directional->dl_UM_RLC.sn_FieldLength = encode_sn_field_length_um(default_rlc_config->drb_um.sn_field_length);
      rlc_Config->choice.um_Bi_Directional->dl_UM_RLC.t_Reassembly = encode_t_reassembly(default_rlc_config->drb_um.t_reassembly);
      break;
    case NR_RLC_Config_PR_am:
      // RLC AM Bearer configuration
      rlc_Config->choice.am = calloc(1, sizeof(*rlc_Config->choice.am));
      rlc_Config->choice.am->ul_AM_RLC.sn_FieldLength = calloc(1, sizeof(*rlc_Config->choice.am->ul_AM_RLC.sn_FieldLength));
      *rlc_Config->choice.am->ul_AM_RLC.sn_FieldLength = encode_sn_field_length_am(default_rlc_config->drb_am.sn_field_length);
      rlc_Config->choice.am->ul_AM_RLC.t_PollRetransmit = encode_t_poll_retransmit(default_rlc_config->drb_am.t_poll_retransmit);
      rlc_Config->choice.am->ul_AM_RLC.pollPDU = encode_poll_pdu(default_rlc_config->drb_am.poll_pdu);
      rlc_Config->choice.am->ul_AM_RLC.pollByte = encode_poll_byte(default_rlc_config->drb_am.poll_byte);
      rlc_Config->choice.am->ul_AM_RLC.maxRetxThreshold = encode_max_retx_threshold(default_rlc_config->drb_am.max_retx_threshold);
      rlc_Config->choice.am->dl_AM_RLC.sn_FieldLength = calloc(1, sizeof(*rlc_Config->choice.am->dl_AM_RLC.sn_FieldLength));
      *rlc_Config->choice.am->dl_AM_RLC.sn_FieldLength = encode_sn_field_length_am(default_rlc_config->drb_am.sn_field_length);
      rlc_Config->choice.am->dl_AM_RLC.t_Reassembly = encode_t_reassembly(default_rlc_config->drb_am.t_reassembly);
      rlc_Config->choice.am->dl_AM_RLC.t_StatusProhibit = encode_t_status_prohibit(default_rlc_config->drb_am.t_status_prohibit);
      break;
    default:
      AssertFatal(false, "RLC config type %d not handled\n", rlc_config_pr);
      break;
  }
  rlc_Config->present = rlc_config_pr;
  return rlc_Config;
}

NR_RLC_BearerConfig_t *get_DRB_RLC_BearerConfig(long lcChannelId,
                                                long drbId,
                                                NR_RLC_Config_PR rlc_conf,
                                                long priority,
                                                const nr_rlc_configuration_t *default_rlc_config)
{
  NR_RLC_BearerConfig_t *rlc_BearerConfig                  = calloc(1, sizeof(NR_RLC_BearerConfig_t));
  rlc_BearerConfig->logicalChannelIdentity                 = lcChannelId;
  rlc_BearerConfig->servedRadioBearer                      = calloc(1, sizeof(*rlc_BearerConfig->servedRadioBearer));
  rlc_BearerConfig->servedRadioBearer->present             = NR_RLC_BearerConfig__servedRadioBearer_PR_drb_Identity;
  rlc_BearerConfig->servedRadioBearer->choice.drb_Identity = drbId;
  rlc_BearerConfig->reestablishRLC                         = NULL;

  rlc_BearerConfig->rlc_Config = nr_drb_config(rlc_conf, default_rlc_config);

  NR_LogicalChannelConfig_t *logicalChannelConfig                 = calloc(1, sizeof(NR_LogicalChannelConfig_t));
  logicalChannelConfig->ul_SpecificParameters                     = calloc(1, sizeof(*logicalChannelConfig->ul_SpecificParameters));
  logicalChannelConfig->ul_SpecificParameters->priority           = priority;
  logicalChannelConfig->ul_SpecificParameters->prioritisedBitRate = NR_LogicalChannelConfig__ul_SpecificParameters__prioritisedBitRate_kBps8;
  logicalChannelConfig->ul_SpecificParameters->bucketSizeDuration = NR_LogicalChannelConfig__ul_SpecificParameters__bucketSizeDuration_ms100;

  long *logicalChannelGroup                                          = CALLOC(1, sizeof(long));
  *logicalChannelGroup                                               = 1;
  logicalChannelConfig->ul_SpecificParameters->logicalChannelGroup   = logicalChannelGroup;
  logicalChannelConfig->ul_SpecificParameters->schedulingRequestID   = CALLOC(1, sizeof(*logicalChannelConfig->ul_SpecificParameters->schedulingRequestID));
  *logicalChannelConfig->ul_SpecificParameters->schedulingRequestID  = 0;
  logicalChannelConfig->ul_SpecificParameters->logicalChannelSR_Mask = 0;
  logicalChannelConfig->ul_SpecificParameters->logicalChannelSR_DelayTimerApplied = 0;
  rlc_BearerConfig->mac_LogicalChannelConfig                         = logicalChannelConfig;

  return rlc_BearerConfig;
}

static bool verify_radio_configuration(int uid, const NR_ServingCellConfigCommon_t *scc, const nr_mac_config_t *configuration)
{
  frame_structure_t *fs = &RC.nrmac[0]->frame_structure;
  int srs_offset = get_ul_slot_offset(fs, uid, false);
  // see configure_periodic_srs
  if (srs_offset >= 2560) {
    LOG_E(NR_RRC, "UID %d, cannot allocate resources for SRS, rejecting UE\n", uid);
    return false;
  }

  int n_dl_bwp = 1 + configuration->num_additional_bwps; // initial + additional bwps
  int csi_offset = fs->numb_slots_period * n_dl_bwp;
  // see set_csirs_periodicity
  if (csi_offset / 320 >= get_full_dl_slots_per_period(fs)) {
    LOG_E(NR_RRC, "UID %d, cannot allocate resources for CSI-RS, rejecting UE\n", uid);
    return false; // cannot allocate resources for CSI-RS
  }

  const nr_pdsch_AntennaPorts_t *ap = &configuration->pdsch_AntennaPorts;
  int pucch2_size = get_pucch2_size(ap->N1 * ap->N2 * ap->XP);
  int curr_bwp = NRRIV2BW(scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  int num_pucch2 = get_nb_pucch2_per_slot(scc, curr_bwp, ap);
  int pucchres0_startingPRB = (pucch2_size * num_pucch2) + uid;
  // see config_pucch_resset0
  if (pucchres0_startingPRB >= curr_bwp) {
    LOG_E(NR_RRC, "UID %d, cannot allocate resources for PUCCH0, rejecting UE\n", uid);
    return false; // cannot allocate resources for PUCCH0
  }
  // see get_nb_pucch2_per_slot
  if ((num_pucch2 * pucch2_size) + MAX_MOBILES_PER_GNB > curr_bwp) {
    LOG_E(NR_RRC, "UID %d, cannot allocate resources for PUCCH2, rejecting UE\n", uid);
    return false; // cannot allocate resources for PUCCH2
  }
  const int idx = (uid * 2 / num_pucch2) + 1;
  int offset = get_ul_slot_offset(fs, idx, true);
  // see set_csi_meas_periodicity
  if (offset >= 320) {
    LOG_E(NR_RRC, "UID %d, cannot allocate resources for CSI reporting, rejecting UE\n", uid);
    return false; // cannot allocate resources for CSI report
  }

  return true;
}

NR_CellGroupConfig_t *get_initial_cellGroupConfig(int uid,
                                                  const NR_ServingCellConfigCommon_t *scc,
                                                  const nr_mac_config_t *configuration,
                                                  const nr_rlc_configuration_t *default_rlc_config,
                                                  int ssb_index)
{
  if (!verify_radio_configuration(uid, scc, configuration))
    return NULL;

  NR_SpCellConfig_t *spCellConfig = get_initial_SpCellConfig(uid, scc, configuration, ssb_index);
  NR_CellGroupConfig_t *cellGroupConfig = calloc(1, sizeof(*cellGroupConfig));
  cellGroupConfig->cellGroupId = 0;

  /* Rlc Bearer Config */
  /* TS38.331 9.2.1	Default SRB configurations */
  cellGroupConfig->rlc_BearerToAddModList = calloc(1, sizeof(*cellGroupConfig->rlc_BearerToAddModList));
  NR_RLC_BearerConfig_t *rlc_BearerConfig =
      get_SRB_RLC_BearerConfig(1, 1, NR_LogicalChannelConfig__ul_SpecificParameters__bucketSizeDuration_ms1000, default_rlc_config);
  asn1cSeqAdd(&cellGroupConfig->rlc_BearerToAddModList->list, rlc_BearerConfig);

  cellGroupConfig->rlc_BearerToReleaseList = NULL;

  /* mac CellGroup Config */
  cellGroupConfig->mac_CellGroupConfig = configure_mac_cellgroup(&configuration->timer_config);

  cellGroupConfig->physicalCellGroupConfig = configure_phy_cellgroup();

  cellGroupConfig->spCellConfig = spCellConfig;

  cellGroupConfig->sCellToAddModList = NULL;
  cellGroupConfig->sCellToReleaseList = NULL;
  return cellGroupConfig;
}

NR_CellGroupConfig_t *update_cellGroupConfig_for_BWP_switch(NR_CellGroupConfig_t *cellGroupConfig,
                                                            const nr_mac_config_t *configuration,
                                                            const NR_UE_NR_Capability_t *uecap,
                                                            const NR_ServingCellConfigCommon_t *scc,
                                                            int uid,
                                                            int old_bwp,
                                                            int new_bwp,
                                                            int ssb_index)
{
  NR_SpCellConfig_t *spCellConfig = cellGroupConfig->spCellConfig;
  NR_ServingCellConfig_t *configDedicated = spCellConfig->spCellConfigDedicated;
  *configDedicated->firstActiveDownlinkBWP_Id = new_bwp != 0;  // 1 for any BWP != 0
  NR_UplinkConfig_t *uplinkConfig = configDedicated->uplinkConfig;
  *uplinkConfig->firstActiveUplinkBWP_Id = new_bwp != 0;  // 1 for any BWP != 0
  nr_mac_config_t local_config = *configuration;
  long ul_maxMIMO_Layers = set_ul_max_layers(configuration, uecap);
  local_config.first_active_bwp = new_bwp;
  uint64_t bitmap = get_ssb_bitmap(scc);
  const int pdsch_AntennaPorts =
    configuration->pdsch_AntennaPorts.N1 * configuration->pdsch_AntennaPorts.N2 * configuration->pdsch_AntennaPorts.XP;
  // add new BWP
  if (new_bwp == 0) {
    if (!configDedicated->initialDownlinkBWP)
      configDedicated->initialDownlinkBWP = calloc_or_fail(1, sizeof(*configDedicated->initialDownlinkBWP));
    if (!uplinkConfig->initialUplinkBWP)
      uplinkConfig->initialUplinkBWP = calloc_or_fail(1, sizeof(*uplinkConfig->initialUplinkBWP));
    uplinkConfig->initialUplinkBWP = configure_initial_ul_bwp(scc, &local_config, ul_maxMIMO_Layers, uecap, uid);
    configDedicated->initialDownlinkBWP = configure_initial_dl_bwp(scc, pdsch_AntennaPorts, bitmap, uecap, &local_config);
  } else {
    if (!configDedicated->downlinkBWP_ToAddModList)
      configDedicated->downlinkBWP_ToAddModList = calloc_or_fail(1, sizeof(*configDedicated->downlinkBWP_ToAddModList));
    NR_BWP_Downlink_t *dl_bwp = config_downlinkBWP(scc, uecap, local_config.force_256qam_off, true, &local_config);
    asn1cSeqAdd(&configDedicated->downlinkBWP_ToAddModList->list, dl_bwp);

    if (!uplinkConfig->uplinkBWP_ToAddModList)
      uplinkConfig->uplinkBWP_ToAddModList = calloc_or_fail(1, sizeof(*uplinkConfig->uplinkBWP_ToAddModList));
    NR_BWP_Uplink_t *ul_bwp = config_uplinkBWP(true, uid, ul_maxMIMO_Layers, &local_config, scc, uecap);
    asn1cSeqAdd(&uplinkConfig->uplinkBWP_ToAddModList->list, ul_bwp);
  }

  ASN_STRUCT_FREE(asn_DEF_NR_CSI_MeasConfig, configDedicated->csi_MeasConfig->choice.setup);
  configDedicated->csi_MeasConfig->choice.setup = get_csiMeasConfig(configDedicated,
                                                                    uecap,
                                                                    scc,
                                                                    &local_config,
                                                                    uid,
                                                                    *uplinkConfig->firstActiveUplinkBWP_Id,
                                                                    bitmap,
                                                                    ssb_index);

  // we temporarily need to keep both the old and the new BWP in the CG used by the gNB
  // while removing the old from the CG sent to the UE
  NR_CellGroupConfig_t *clone_cg = NULL;
  const int copy_result = asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&clone_cg, cellGroupConfig);
  AssertFatal(copy_result == 0, "unable to copy NR_CellGroupConfig for cloning\n");
  if (old_bwp > 0)
    clean_bwp_structures(clone_cg->spCellConfig);
  return clone_cg;
}

NR_CellGroupConfig_t *update_cellGroupConfig_for_beam_switch(NR_CellGroupConfig_t *cellGroupConfig,
                                                            const nr_mac_config_t *configuration,
                                                            const NR_UE_NR_Capability_t *uecap,
                                                            const NR_ServingCellConfigCommon_t *scc,
                                                            int uid,
                                                            int bwp,
                                                            int ssb_index)
{
  NR_SpCellConfig_t *spCellConfig = cellGroupConfig->spCellConfig;
  NR_ServingCellConfig_t *configDedicated = spCellConfig->spCellConfigDedicated;

  uint64_t bitmap = get_ssb_bitmap(scc);
  ASN_STRUCT_FREE(asn_DEF_NR_CSI_MeasConfig, configDedicated->csi_MeasConfig->choice.setup);
  configDedicated->csi_MeasConfig->choice.setup = get_csiMeasConfig(configDedicated,
                                                                    uecap,
                                                                    scc,
                                                                    configuration,
                                                                    uid,
                                                                    bwp,
                                                                    bitmap,
                                                                    ssb_index);

  NR_CellGroupConfig_t *clone_cg = NULL;
  const int copy_result = asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&clone_cg, cellGroupConfig);
  AssertFatal(copy_result == 0, "unable to copy NR_CellGroupConfig for cloning\n");
  return clone_cg;
}

void update_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig,
                            const int uid,
                            const NR_UE_NR_Capability_t *uecap,
                            const nr_mac_config_t *configuration,
                            const NR_ServingCellConfigCommon_t *scc)
{
  DevAssert(cellGroupConfig != NULL);
  DevAssert(cellGroupConfig->spCellConfig != NULL);
  DevAssert(cellGroupConfig->spCellConfig->spCellConfigDedicated != NULL);
  DevAssert(configuration != NULL);
  DevAssert(scc != NULL);

  NR_SpCellConfig_t *SpCellConfig = cellGroupConfig->spCellConfig;
  NR_ServingCellConfig_t *spCellConfigDedicated = SpCellConfig->spCellConfigDedicated;
  NR_PDSCH_ServingCellConfig_t *pdsch_servingcellconfig = spCellConfigDedicated->pdsch_ServingCellConfig->choice.setup;
  set_dl_maxmimolayers(pdsch_servingcellconfig, scc, uecap, configuration->maxMIMO_layers);

  if (configuration->disable_harq) {
    if (!pdsch_servingcellconfig->ext3)
      pdsch_servingcellconfig->ext3 = calloc(1, sizeof(*pdsch_servingcellconfig->ext3));
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17 = calloc(1, sizeof(*pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17));
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->present = NR_SetupRelease_DownlinkHARQ_FeedbackDisabled_r17_PR_setup;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf = calloc(4, sizeof(uint8_t));
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.size = 4;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.bits_unused = 0;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[0] = 0xFF;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[1] = 0xFF;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[2] = 0xFF;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[3] = 0xFF;
  }

  NR_CSI_MeasConfig_t *csi_MeasConfig = spCellConfigDedicated->csi_MeasConfig->choice.setup;
  for (int report = 0; report < csi_MeasConfig->csi_ReportConfigToAddModList->list.count; report++) {
    NR_CSI_ReportConfig_t *csirep = csi_MeasConfig->csi_ReportConfigToAddModList->list.array[report];
    if (csirep->codebookConfig)
      config_csi_codebook(&configuration->pdsch_AntennaPorts, *pdsch_servingcellconfig->ext1->maxMIMO_Layers, csirep->codebookConfig);
    if (csirep->groupBasedBeamReporting.present == NR_CSI_ReportConfig__groupBasedBeamReporting_PR_disabled
        && csirep->groupBasedBeamReporting.choice.disabled
        && csirep->groupBasedBeamReporting.choice.disabled->nrofReportedRS)
      *csirep->groupBasedBeamReporting.choice.disabled->nrofReportedRS = config_nrofReportedRS(uecap,
                                                                                               get_ssb_bitmap(scc),
                                                                                               scc,
                                                                                               configuration->max_num_rsrp);
  }

  NR_UplinkConfig_t *uplinkConfig = spCellConfigDedicated->uplinkConfig;
  long maxMIMO_Layers = set_ul_max_layers(configuration, uecap);
  if (!uplinkConfig->pusch_ServingCellConfig)
    uplinkConfig->pusch_ServingCellConfig = calloc(1, sizeof(*uplinkConfig->pusch_ServingCellConfig));
  uplinkConfig->pusch_ServingCellConfig->present = NR_SetupRelease_PUSCH_ServingCellConfig_PR_setup;
  if (!uplinkConfig->pusch_ServingCellConfig->choice.setup)
    uplinkConfig->pusch_ServingCellConfig->choice.setup = calloc(1, sizeof(*uplinkConfig->pusch_ServingCellConfig->choice.setup));
  if (!uplinkConfig->pusch_ServingCellConfig->choice.setup->ext1)
    uplinkConfig->pusch_ServingCellConfig->choice.setup->ext1 =
        calloc(1, sizeof(*uplinkConfig->pusch_ServingCellConfig->choice.setup->ext1));
  asn1cCallocOne(uplinkConfig->pusch_ServingCellConfig->choice.setup->ext1->maxMIMO_Layers, maxMIMO_Layers);

  // Update UL BWP
  NR_BWP_UplinkDedicated_t *ul_bwp_Dedicated = NULL;
  int curr_bwp = 0;
  int bwp_id = 0;
  if (uplinkConfig && uplinkConfig->initialUplinkBWP) {
    ul_bwp_Dedicated = uplinkConfig->initialUplinkBWP;
    curr_bwp = NRRIV2BW(scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  } else if (uplinkConfig && uplinkConfig->uplinkBWP_ToAddModList) {
    struct NR_UplinkConfig__uplinkBWP_ToAddModList *UL_BWP_list = uplinkConfig->uplinkBWP_ToAddModList;
    AssertFatal(UL_BWP_list->list.count == 1, "We should only have 1 BWP configured at a given time\n");
    NR_BWP_Uplink_t *ul_bwp = UL_BWP_list->list.array[0];
    curr_bwp = NRRIV2BW(ul_bwp->bwp_Common->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
    ul_bwp_Dedicated = ul_bwp->bwp_Dedicated;
    bwp_id = ul_bwp->bwp_Id;
  }
  if (ul_bwp_Dedicated) {
    NR_PUSCH_Config_t *pusch_Config = ul_bwp_Dedicated->pusch_Config->choice.setup;
    if (!pusch_Config->maxRank)
      pusch_Config->maxRank = calloc(1, sizeof(*pusch_Config->maxRank));
    *pusch_Config->maxRank = maxMIMO_Layers;
    if (configuration->do_SRS) {
      ASN_STRUCT_FREE(asn_DEF_NR_SetupRelease_SRS_Config, ul_bwp_Dedicated->srs_Config);
      ul_bwp_Dedicated->srs_Config = get_config_srs(uecap,
                                                    curr_bwp,
                                                    uid,
                                                    bwp_id,
                                                    maxMIMO_Layers,
                                                    configuration->minRXTXTIME,
                                                    configuration->do_SRS);
    }
    set_ul_mcs_table(configuration->force_UL256qam_off ? NULL : uecap, scc, pusch_Config);
  }

  // Update DL BWP
  NR_BWP_DownlinkDedicated_t *bwp_Dedicated = NULL;
  int scs = -1;
  if (spCellConfigDedicated->initialDownlinkBWP) {
    bwp_Dedicated = spCellConfigDedicated->initialDownlinkBWP;
    scs = scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.subcarrierSpacing;
  } else if (spCellConfigDedicated->downlinkBWP_ToAddModList) {
    struct NR_ServingCellConfig__downlinkBWP_ToAddModList *DL_BWP_list = spCellConfigDedicated->downlinkBWP_ToAddModList;
    AssertFatal(DL_BWP_list->list.count == 1, "We should only have 1 BWP configured at a given time\n");
    NR_BWP_Downlink_t *bwp = DL_BWP_list->list.array[0];
    bwp_Dedicated = bwp->bwp_Dedicated;
    scs = bwp->bwp_Common->genericParameters.subcarrierSpacing;
  }
  if (bwp_Dedicated) {
    set_dl_mcs_table(scs, configuration->force_256qam_off ? NULL : uecap, bwp_Dedicated, scc);
    update_cqitables(bwp_Dedicated->pdsch_Config, csi_MeasConfig);
  }
}

int encode_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig, uint8_t *buffer, int max_buffer_size)
{
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, cellGroupConfig, buffer, max_buffer_size);
  AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded <= max_buffer_size * 8,
              "ASN1 message encoding failed (%s, %lu)!\n",
              enc_rval.failed_type->name,
              enc_rval.encoded);
  return (enc_rval.encoded + 7) / 8;
}

static NR_ServingCellConfigCommon_t *clone_ServingCellConfigCommon(const NR_ServingCellConfigCommon_t *scc)
{
  if (scc == NULL)
    return NULL;

  uint8_t buf[16384];
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_ServingCellConfigCommon, NULL, scc, buf, sizeof(buf));
  AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded < sizeof(buf), "could not clone NR_ServingCellConfigCommon: problem while encoding\n");
  NR_ServingCellConfigCommon_t *clone = NULL;
  asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_ServingCellConfigCommon, (void **)&clone, buf, enc_rval.encoded, 0, 0);
  AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed == enc_rval.encoded, "could not clone NR_ServingCellConfigCommon: problem while decoding\n");
  return clone;
}

NR_CellGroupConfig_t *get_default_secondaryCellGroup(const NR_ServingCellConfigCommon_t *servingcellconfigcommon,
                                                     const NR_UE_NR_Capability_t *uecap,
                                                     int scg_id,
                                                     int servCellIndex,
                                                     const nr_mac_config_t *configuration,
                                                     int uid,
                                                     int ssb_index)
{
  AssertFatal(servingcellconfigcommon, "servingcellconfigcommon is null\n");

  if (uecap == NULL)
    LOG_E(RRC, "No UE Capabilities available when programming default CellGroup in NSA\n");

  uint64_t bitmap = get_ssb_bitmap(servingcellconfigcommon);

  NR_CellGroupConfig_t *secondaryCellGroup = calloc(1, sizeof(*secondaryCellGroup));
  secondaryCellGroup->cellGroupId = scg_id;

  /* rlc_BearerToAddModList is handled outside */

  secondaryCellGroup->mac_CellGroupConfig = configure_mac_cellgroup(&configuration->timer_config);
  secondaryCellGroup->physicalCellGroupConfig = configure_phy_cellgroup();
  secondaryCellGroup->spCellConfig = calloc(1, sizeof(*secondaryCellGroup->spCellConfig));
  secondaryCellGroup->spCellConfig->servCellIndex = calloc(1, sizeof(*secondaryCellGroup->spCellConfig->servCellIndex));
  *secondaryCellGroup->spCellConfig->servCellIndex = servCellIndex;

  secondaryCellGroup->spCellConfig->rlf_TimersAndConstants =
      calloc(1, sizeof(*secondaryCellGroup->spCellConfig->rlf_TimersAndConstants));
  secondaryCellGroup->spCellConfig->rlf_TimersAndConstants->present = NR_SetupRelease_RLF_TimersAndConstants_PR_setup;
  secondaryCellGroup->spCellConfig->rlf_TimersAndConstants->choice.setup =
      calloc(1, sizeof(*secondaryCellGroup->spCellConfig->rlf_TimersAndConstants->choice.setup));
  secondaryCellGroup->spCellConfig->rlf_TimersAndConstants->choice.setup->t310 = NR_RLF_TimersAndConstants__t310_ms4000;
  secondaryCellGroup->spCellConfig->rlf_TimersAndConstants->choice.setup->n310 = NR_RLF_TimersAndConstants__n310_n20;
  secondaryCellGroup->spCellConfig->rlf_TimersAndConstants->choice.setup->n311 = NR_RLF_TimersAndConstants__n311_n1;
  secondaryCellGroup->spCellConfig->rlf_TimersAndConstants->choice.setup->ext1 =
      calloc(1, sizeof(*secondaryCellGroup->spCellConfig->rlf_TimersAndConstants->choice.setup->ext1));
  secondaryCellGroup->spCellConfig->rlf_TimersAndConstants->choice.setup->ext1->t311 =
      NR_RLF_TimersAndConstants__ext1__t311_ms30000;
  secondaryCellGroup->spCellConfig->rlmInSyncOutOfSyncThreshold = NULL;

  NR_ServingCellConfig_t *configDedicated = calloc(1, sizeof(*configDedicated));
  configDedicated->tdd_UL_DL_ConfigurationDedicated = NULL;

  /// initialDownlinkBWP

  configDedicated->initialDownlinkBWP = calloc(1, sizeof(*configDedicated->initialDownlinkBWP));
  configDedicated->initialDownlinkBWP->pdcch_Config = NULL;
  configDedicated->initialDownlinkBWP->pdsch_Config = config_pdsch(bitmap, 0, configuration->do_TCI, configuration->ptrs);
  configDedicated->initialDownlinkBWP->sps_Config = NULL; // calloc(1,sizeof(struct NR_SetupRelease_SPS_Config));

  configDedicated->initialDownlinkBWP->radioLinkMonitoringConfig = NULL;

  /// initialUplinkBWP
  if (!configDedicated->uplinkConfig) {
    configDedicated->uplinkConfig = calloc(1, sizeof(*configDedicated->uplinkConfig));
  }
  NR_UplinkConfig_t *ulConfig = configDedicated->uplinkConfig;

  NR_BWP_UplinkDedicated_t *initialUplinkBWP = calloc(1, sizeof(*initialUplinkBWP));
  ulConfig->initialUplinkBWP = initialUplinkBWP;
  initialUplinkBWP->pucch_Config = NULL;

  initialUplinkBWP->pusch_Config = config_pusch(configuration, servingcellconfigcommon, uecap);

  long maxMIMO_Layers = set_ul_max_layers(configuration, uecap);
  int curr_bwp = NRRIV2BW(servingcellconfigcommon->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth,
                          MAX_BWP_SIZE);
  initialUplinkBWP->srs_Config = get_config_srs(NULL,
                                                curr_bwp,
                                                uid,
                                                0,
                                                maxMIMO_Layers,
                                                configuration->minRXTXTIME,
                                                configuration->do_SRS);

  // Downlink BWPs
  int firstActiveDownlinkBWP_Id = 1;
  configDedicated->downlinkBWP_ToAddModList = calloc(1, sizeof(*configDedicated->downlinkBWP_ToAddModList));
  NR_BWP_Downlink_t *bwp = config_downlinkBWP(servingcellconfigcommon,
                                              uecap,
                                              configuration->force_256qam_off,
                                              false,
                                              configuration);
  asn1cSeqAdd(&configDedicated->downlinkBWP_ToAddModList->list, bwp);
  configDedicated->firstActiveDownlinkBWP_Id = calloc(1, sizeof(*configDedicated->firstActiveDownlinkBWP_Id));
  *configDedicated->firstActiveDownlinkBWP_Id = firstActiveDownlinkBWP_Id;
  configDedicated->defaultDownlinkBWP_Id = calloc(1, sizeof(*configDedicated->defaultDownlinkBWP_Id));
  *configDedicated->defaultDownlinkBWP_Id = 1;

  // Uplink BWPs
  int firstActiveUplinkBWP_Id = 1;
  ulConfig->uplinkBWP_ToAddModList = calloc(1, sizeof(*ulConfig->uplinkBWP_ToAddModList));
  NR_BWP_Uplink_t *ubwp = config_uplinkBWP(false, uid, maxMIMO_Layers, configuration, servingcellconfigcommon, uecap);
  asn1cSeqAdd(&ulConfig->uplinkBWP_ToAddModList->list, ubwp);
  ulConfig->firstActiveUplinkBWP_Id = calloc(1, sizeof(*ulConfig->firstActiveUplinkBWP_Id));
  *ulConfig->firstActiveUplinkBWP_Id = firstActiveUplinkBWP_Id;

  configDedicated->bwp_InactivityTimer = NULL;
  configDedicated->downlinkBWP_ToReleaseList = NULL;
  ulConfig->uplinkBWP_ToReleaseList = NULL;

  if (!ulConfig->pusch_ServingCellConfig)
    ulConfig->pusch_ServingCellConfig = calloc(1, sizeof(*ulConfig->pusch_ServingCellConfig));
  ulConfig->pusch_ServingCellConfig->present = NR_SetupRelease_PUSCH_ServingCellConfig_PR_setup;
  if (!ulConfig->pusch_ServingCellConfig->choice.setup)
    ulConfig->pusch_ServingCellConfig->choice.setup = calloc(1, sizeof(*ulConfig->pusch_ServingCellConfig->choice.setup));
  NR_PUSCH_ServingCellConfig_t *pusch_scc = ulConfig->pusch_ServingCellConfig->choice.setup;
  pusch_scc->codeBlockGroupTransmission = NULL;
  pusch_scc->rateMatching = NULL;
  pusch_scc->xOverhead = NULL;
  if (!pusch_scc->ext1)
    pusch_scc->ext1 = calloc(1, sizeof(*pusch_scc->ext1));
  asn1cCallocOne(pusch_scc->ext1->maxMIMO_Layers, maxMIMO_Layers);
  pusch_scc->ext1->processingType2Enabled = NULL;

  ulConfig->carrierSwitching = NULL;
  configDedicated->supplementaryUplink = NULL;
  configDedicated->pdcch_ServingCellConfig = NULL;

  configDedicated->pdsch_ServingCellConfig = calloc(1, sizeof(*configDedicated->pdsch_ServingCellConfig));
  NR_PDSCH_ServingCellConfig_t *pdsch_servingcellconfig = calloc(1, sizeof(*pdsch_servingcellconfig));
  configDedicated->pdsch_ServingCellConfig->present = NR_SetupRelease_PDSCH_ServingCellConfig_PR_setup;
  configDedicated->pdsch_ServingCellConfig->choice.setup = pdsch_servingcellconfig;
  pdsch_servingcellconfig->codeBlockGroupTransmission = NULL;
  pdsch_servingcellconfig->xOverhead = NULL;
  asn1cCallocOne(pdsch_servingcellconfig->nrofHARQ_ProcessesForPDSCH, NR_PDSCH_ServingCellConfig__nrofHARQ_ProcessesForPDSCH_n16);
  pdsch_servingcellconfig->pucch_Cell = NULL;
  set_dl_maxmimolayers(pdsch_servingcellconfig, servingcellconfigcommon, uecap, configuration->maxMIMO_layers);
  pdsch_servingcellconfig->ext1->processingType2Enabled = NULL;
  if (configuration->disable_harq) {
    if (!pdsch_servingcellconfig->ext3)
      pdsch_servingcellconfig->ext3 = calloc(1, sizeof(*pdsch_servingcellconfig->ext3));
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17 = calloc(1, sizeof(*pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17));
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->present = NR_SetupRelease_DownlinkHARQ_FeedbackDisabled_r17_PR_setup;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf = calloc(4, sizeof(uint8_t));
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.size = 4;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.bits_unused = 0;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[0] = 0xFF;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[1] = 0xFF;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[2] = 0xFF;
    pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[3] = 0xFF;
  }

  configDedicated->csi_MeasConfig = NULL;
  configDedicated->csi_MeasConfig = calloc(1, sizeof(*configDedicated->csi_MeasConfig));
  configDedicated->csi_MeasConfig->present = NR_SetupRelease_CSI_MeasConfig_PR_setup;
  configDedicated->csi_MeasConfig->choice.setup = get_csiMeasConfig(configDedicated,
                                                                    uecap,
                                                                    servingcellconfigcommon,
                                                                    configuration,
                                                                    uid,
                                                                    firstActiveUplinkBWP_Id,
                                                                    bitmap,
                                                                    ssb_index);

  configDedicated->sCellDeactivationTimer = NULL;
  configDedicated->crossCarrierSchedulingConfig = NULL;
  configDedicated->tag_Id = 0;
  configDedicated->dummy1 = NULL;
  configDedicated->pathlossReferenceLinking = NULL;
  configDedicated->servingCellMO = NULL;

  fill_harq_IEs(configDedicated, configuration->num_dlharq, configuration->num_ulharq, firstActiveDownlinkBWP_Id);
  secondaryCellGroup->spCellConfig->spCellConfigDedicated = configDedicated;

  if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
    xer_fprint(stdout, &asn_DEF_NR_SpCellConfig, (void *)secondaryCellGroup->spCellConfig);
  }
  return secondaryCellGroup;
}

NR_ReconfigurationWithSync_t *get_reconfiguration_with_sync(rnti_t rnti, uid_t uid, const NR_ServingCellConfigCommon_t *scc, int frame)
{
  NR_ReconfigurationWithSync_t *reconfigurationWithSync = calloc(1, sizeof(*reconfigurationWithSync));
  reconfigurationWithSync->newUE_Identity = rnti;
  reconfigurationWithSync->t304 = NR_ReconfigurationWithSync__t304_ms2000;
  reconfigurationWithSync->rach_ConfigDedicated = NULL;
  reconfigurationWithSync->ext1 = NULL;

  reconfigurationWithSync->spCellConfigCommon = clone_ServingCellConfigCommon(scc);

  // in case of ReconfigurationWithSync, the epochTime_r17 must be present if there is a ntn_Config_r17
  if (reconfigurationWithSync->spCellConfigCommon->ext2 && reconfigurationWithSync->spCellConfigCommon->ext2->ntn_Config_r17) {
    NR_NTN_Config_r17_t *ntncfg = reconfigurationWithSync->spCellConfigCommon->ext2->ntn_Config_r17;
    if (!ntncfg->epochTime_r17) {
      ntncfg->epochTime_r17 = calloc(1, sizeof(*ntncfg->epochTime_r17));
      // the epochTime_r17 is esp. relevant for the timer T430, which runs for multiples of 5 seconds.
      // here we don't have access to the current subFrame number, so we only set the SFN.
      // the error of up to 10 ms should be acceptable for this long running timer.
      ntncfg->epochTime_r17->sfn_r17 = frame;
      ntncfg->epochTime_r17->subFrameNR_r17 = 0;
    }
  }

  reconfigurationWithSync->rach_ConfigDedicated = calloc(1, sizeof(*reconfigurationWithSync->rach_ConfigDedicated));
  reconfigurationWithSync->rach_ConfigDedicated->present = NR_ReconfigurationWithSync__rach_ConfigDedicated_PR_uplink;
  NR_RACH_ConfigDedicated_t *uplink = calloc(1, sizeof(*uplink));
  reconfigurationWithSync->rach_ConfigDedicated->choice.uplink = uplink;
  uplink->ra_Prioritization = NULL;
  uplink->cfra = calloc(1, sizeof(struct NR_CFRA));
  uplink->cfra->ext1 = NULL;
  uplink->cfra->occasions = NULL;
  uplink->cfra->resources.present = NR_CFRA__resources_PR_ssb;
  uplink->cfra->resources.choice.ssb = calloc(1, sizeof(struct NR_CFRA__resources__ssb));
  uplink->cfra->resources.choice.ssb->ra_ssb_OccasionMaskIndex = 0;

  uint64_t bitmap = get_ssb_bitmap(scc);
  for (int i = 0; i < 64; i++) {
    if (((bitmap >> (63 - i)) & 0x01) == 0)
      continue;

    NR_CFRA_SSB_Resource_t *ssbElem = calloc(1, sizeof(*ssbElem));
    ssbElem->ssb = i;
    ssbElem->ra_PreambleIndex = 63 - (uid % 64);
    asn1cSeqAdd(&uplink->cfra->resources.choice.ssb->ssb_ResourceList.list, ssbElem);
  }

  return reconfigurationWithSync;
}


NR_MeasurementTimingConfiguration_t *get_nr_mtc(uint8_t *buf, uint32_t len)
{
  if (buf == NULL || len == 0)
    return NULL;

  NR_MeasurementTimingConfiguration_t *mtc = NULL;
  asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_MeasurementTimingConfiguration, (void **)&mtc, buf, len, 0, 0);
  if (dec_rval.code != RC_OK) {
    LOG_E(NR_MAC, "cannot decode NR MeasurementTimingConfiguration, ignoring\n");
    ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc);
    return NULL;
  }
  return mtc;
}

/** @brief return Measurement Gap Repetition Period from ASN.1 config */
static int get_mgrp(long mgrp)
{
  switch (mgrp) {
    case NR_GapConfig__mgrp_ms20:
      return 20;
    case NR_GapConfig__mgrp_ms40:
      return 40;
    case NR_GapConfig__mgrp_ms80:
      return 80;
    case NR_GapConfig__mgrp_ms160:
      return 160;
    default:
      LOG_E(NR_MAC, "Invalid MGRP %ld\n", mgrp);
    return -1;
  }
}

/** @brief Return Measurement Gap Length from ASN.1 config */
static float get_mgl(long mgl)
{
  switch (mgl) {
    case NR_GapConfig__mgl_ms1dot5:
      return 1.5;
    case NR_GapConfig__mgl_ms3:
      return 3.0;
    case NR_GapConfig__mgl_ms3dot5:
      return 3.5;
    case NR_GapConfig__mgl_ms4:
      return 4;
    case NR_GapConfig__mgl_ms5dot5:
      return 5.5;
    case NR_GapConfig__mgl_ms6:
      return 6;
    default:
      LOG_E(NR_MAC, "Invalid MGL %ld\n", mgl);
    return -1;
  }
}

/** @brief Return Measurement Gap Timing Advance, from ASN.1 config */
static float get_mgta(long mgta)
{
  switch (mgta) {
    case NR_GapConfig__mgta_ms0:
      return 0.0;
    case NR_GapConfig__mgta_ms0dot25:
      return 0.25;
    case NR_GapConfig__mgta_ms0dot5:
      return 0.5;
    default:
      LOG_E(NR_MAC, "Invalid MGTA %ld\n", mgta);
    return -1;
  }
}

/** @brief Extract gapOffset from SSB MTC periodicity */
static bool extract_gap_offset_from_smtc(const NR_SSB_MTC_t *ssb_mtc, long *gapOffset)
{
  if (!ssb_mtc)
    return false;

  // Extract gapOffset based on periodicity
  switch (ssb_mtc->periodicityAndOffset.present) {
    case NR_SSB_MTC__periodicityAndOffset_PR_sf20:
      *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf20;
      break;
    case NR_SSB_MTC__periodicityAndOffset_PR_sf40:
      *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf40;
      break;
    case NR_SSB_MTC__periodicityAndOffset_PR_sf80:
      *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf80;
      break;
    case NR_SSB_MTC__periodicityAndOffset_PR_sf160:
      *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf160;
      break;
    default:
      LOG_W(NR_RRC, "SMTC periodicity higher than MGRP\n");
      if (ssb_mtc->periodicityAndOffset.present == NR_SSB_MTC__periodicityAndOffset_PR_sf5) {
        *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf5;
      } else if (ssb_mtc->periodicityAndOffset.present == NR_SSB_MTC__periodicityAndOffset_PR_sf10) {
        *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf10;
      }
      break;
  }

  return true;
}

measgap_config_t create_measgap_config(const NR_MeasurementTimingConfiguration_t *mtc, int scs, int min_rxtxtime)
{
  measgap_config_t mgc = {0};
  const NR_MeasTimingList_t *mtlist = mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming;
  const NR_MeasTiming_t *mt = mtlist->list.array[0];
  DevAssert(mt != NULL && mt->frequencyAndTiming != NULL);
  const struct NR_MeasTiming__frequencyAndTiming *ft = mt->frequencyAndTiming;
  const NR_SSB_MTC_t *ssb_mtc = &ft->ssb_MeasurementTimingConfiguration;

  // Initialize with defaults
  long mgrp = DEFAULT_MGRP;
  long gapOffset = 0;
  long mgta = DEFAULT_MGTA;
  long mgl = DEFAULT_MGL;

  // Extract gapOffset from SSB MTC periodicity
  if (!extract_gap_offset_from_smtc(ssb_mtc, &gapOffset)) {
    return mgc;
  }

  mgc.mgrp_ms = get_mgrp(mgrp);
  DevAssert(mgc.mgrp_ms != -1);
  mgc.mgrp = mgrp;

  mgc.gapOffset = gapOffset;
  mgc.mgta = mgta;
  mgc.n_slots_mgta = ((int)(10 * get_mgta(mgta)) << scs) / 10;

  // We start the timer K2 slots earlier to avoid scheduling feedback PUCCHs inside measGap
  // or depending on the current min_rxtxtime earlier
  // TS 38.214 - Table 6.1.2.1.1.2
  const int max_k2 = max(3, min_rxtxtime);

  mgc.n_slots_advance = mgc.n_slots_mgta + max_k2;

  mgc.mgl_ms = get_mgl(mgl);
  DevAssert(mgc.mgl_ms != -1);
  mgc.mgl = mgl;
  mgc.mgl_slots = ((int)(10 * (mgc.mgl_ms + max_k2)) << scs) / 10;

  mgc.enable = true;

  return mgc;
}

int encode_measgap_config(const measgap_config_t *c, uint8_t *buf)
{
  NR_MeasGapConfig_t *measGapConfig = calloc_or_fail(1, sizeof(*measGapConfig));
  measGapConfig->ext1 = calloc_or_fail(1, sizeof(*measGapConfig->ext1));
  measGapConfig->ext1->gapUE = calloc_or_fail(1, sizeof(*measGapConfig->ext1->gapUE));
  measGapConfig->ext1->gapUE->present = NR_SetupRelease_GapConfig_PR_setup;
  NR_GapConfig_t *gap_config = calloc_or_fail(1, sizeof(*gap_config));
  measGapConfig->ext1->gapUE->choice.setup = gap_config;
  gap_config->mgta = c->mgta;
  gap_config->mgrp = c->mgrp;
  gap_config->gapOffset = c->gapOffset;
  gap_config->mgl = c->mgl;
  asn_enc_rval_t enc_rval_mgc = uper_encode_to_buffer(&asn_DEF_NR_MeasGapConfig, NULL, measGapConfig, buf, 1024);
  AssertFatal(enc_rval_mgc.encoded > 0, "Could not encode CellGroup, failed element %s\n", enc_rval_mgc.failed_type->name);
  ASN_STRUCT_FREE(asn_DEF_NR_MeasGapConfig, measGapConfig);
  return (int)((enc_rval_mgc.encoded + 7) >> 3);
}
