/**
 * Copyright 2013-2023 Software Radio Systems Limited
 *
 * This file is part of srsRAN.
 *
 * srsRAN is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * srsRAN is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * A copy of the GNU Affero General Public License can be found in
 * the LICENSE file in the top-level directory of this distribution
 * and at http://www.gnu.org/licenses/.
 *
 */

#include "srsran/phy/phch/dci_nr.h"
#include "srsran/phy/utils/bit.h"
#include "srsran/phy/utils/debug.h"
#include "srsran/phy/utils/vector.h"

/**
 * Defines minimum size according to TS 38.212 section 7.3.1 DCI Formats:
 *     If the number of information bits in a DCI format is less than 12 bits, zeros shall be appended to the DCI format
 *     until the payload size equals 12.
 */
#define DCI_NR_MIN_SIZE 12

static uint32_t dci_nr_freq_resource_size_type1(uint32_t N)
{
  if (N == 0) {
    return 0;
  }

  return (int)SRSRAN_CEIL_LOG2(N * (N + 1) / 2.0);
}

static uint32_t dci_nr_freq_resource_size(srsran_resource_alloc_t alloc_type, uint32_t N_RBG, uint32_t N_BWP_RB)
{
  // Frequency domain resource assignment
  switch (alloc_type) {
    case srsran_resource_alloc_type0:
      return N_RBG;
    case srsran_resource_alloc_type1:
      return dci_nr_freq_resource_size_type1(N_BWP_RB);
    case srsran_resource_alloc_dynamic:
      return SRSRAN_MAX(N_RBG, dci_nr_freq_resource_size_type1(N_BWP_RB)) + 1;
    default:
      ERROR("Unhandled case");
  }

  return 0;
}

static uint32_t dci_nr_bwp_id_size(uint32_t N_BWP_RRC)
{
  uint32_t N_BWP = N_BWP_RRC;
  if (N_BWP_RRC <= 3) {
    N_BWP = N_BWP_RRC + 1;
  }

  return (int)SRSRAN_CEIL_LOG2(N_BWP);
}

static uint32_t dci_nr_time_res_size(uint32_t nof_time_res)
{
  if (nof_time_res == 0) {
    // 4 bits are necessary for PUSCH default time resource assigment (TS 38.214 Table 6.1.2.1.1-2)
    nof_time_res = SRSRAN_MAX_NOF_TIME_RA;
  }
  return (uint32_t)SRSRAN_CEIL_LOG2(nof_time_res);
}

static uint32_t dci_nr_ptrs_size(const srsran_dci_cfg_nr_t* cfg)
{
  // 0 bit if PTRS-UplinkConfig is not configured and transform precoder is disabled, or if transform precoder is
  // enabled, or if maxRank=1;
  if ((!cfg->pusch_ptrs && !cfg->enable_transform_precoding) || cfg->enable_transform_precoding ||
      cfg->nof_ul_layers <= 1) {
    return 0;
  }

  // 2 bits otherwise, where Table 7.3.1.1.2-25 and 7.3.1.1.2-26 are used to indicate the association between
  // PTRS port(s) and DMRS port(s) for transmission of one PT-RS port and two PT-RS ports respectively, and
  // the DMRS ports are indicated by the Antenna ports field.
  return 2;
}

static uint32_t dci_nr_dl_ports_size(const srsran_dci_cfg_nr_t* cfg)
{
  uint32_t ret = 4;

  if (cfg->pdsch_dmrs_type == srsran_dmrs_sch_type_2) {
    ret++;
  }
  if (cfg->pdsch_dmrs_max_len == srsran_dmrs_sch_len_2) {
    ret++;
  }

  return ret;
}

static uint32_t dci_nr_ul_ports_size(const srsran_dci_cfg_nr_t* cfg)
{
  // 2 bits as defined by Tables 7.3.1.1.2-6, if transform precoder is enabled, dmrs-Type=1, and maxLength=1;
  if (cfg->enable_transform_precoding == true && cfg->pusch_dmrs_type == srsran_dmrs_sch_type_1 &&
      cfg->pusch_dmrs_max_len == srsran_dmrs_sch_len_1) {
    return 2;
  }

  // 4 bits as defined by Tables 7.3.1.1.2-7, if transform precoder is enabled, dmrs-Type=1, and maxLength=2;
  if (cfg->enable_transform_precoding == true && cfg->pusch_dmrs_type == srsran_dmrs_sch_type_1 &&
      cfg->pusch_dmrs_max_len == srsran_dmrs_sch_len_2) {
    return 4;
  }

  // 3 bits as defined by Tables 7.3.1.1.2-8/9/10/11, if transform precoder is disabled, dmrs-Type=1, and maxLength=1
  if (cfg->enable_transform_precoding == false && cfg->pusch_dmrs_type == srsran_dmrs_sch_type_1 &&
      cfg->pusch_dmrs_max_len == srsran_dmrs_sch_len_1) {
    return 3;
  }

  // 4 bits as defined by Tables 7.3.1.1.2-12/13/14/15, if transform precoder is disabled, dmrs-Type=1, and
  // maxLength=2
  if (cfg->enable_transform_precoding == false && cfg->pusch_dmrs_type == srsran_dmrs_sch_type_1 &&
      cfg->pusch_dmrs_max_len == srsran_dmrs_sch_len_2) {
    return 4;
  }

  // 4 bits as defined by Tables 7.3.1.1.2-16/17/18/19, if transform precoder is disabled, dmrs-Type=2, and
  // maxLength=1
  if (cfg->enable_transform_precoding == false && cfg->pusch_dmrs_type == srsran_dmrs_sch_type_2 &&
      cfg->pusch_dmrs_max_len == srsran_dmrs_sch_len_1) {
    return 4;
  }

  // 5 bits as defined by Tables 7.3.1.1.2-20/21/22/23, if transform precoder is disabled, dmrs-Type=2, and
  // maxLength=2
  if (cfg->enable_transform_precoding == false && cfg->pusch_dmrs_type == srsran_dmrs_sch_type_2 &&
      cfg->pusch_dmrs_max_len == srsran_dmrs_sch_len_2) {
    return 5;
  }

  ERROR("Unhandled configuration");
  return 0;
}

static uint32_t dci_nr_srs_id_size(const srsran_dci_cfg_nr_t* cfg)
{
  uint32_t N_srs = SRSRAN_MIN(1, cfg->nof_srs);
  if (cfg->pusch_tx_config_non_codebook) {
    uint32_t N = 0;
    for (uint32_t k = 1; k < SRSRAN_MIN(cfg->nof_ul_layers, cfg->nof_srs); k++) {
      N += cfg->nof_srs / k;
    }
    return (uint32_t)SRSRAN_CEIL_LOG2(N);
  }
  return (uint32_t)SRSRAN_CEIL_LOG2(N_srs);
}

// Determines DCI format 0_0 according to TS 38.212 clause 7.3.1.1.1
static uint32_t dci_nr_format_0_0_sizeof(uint32_t N_UL_BWP_RB, const srsran_dci_cfg_nr_t* cfg)
{
  uint32_t count = 0;

  // Identifier for DCI formats – 1 bits
  count++;

  // For PUSCH hopping with resource allocation type 1 N UL_hop MSB bits are used to indicate the frequency offset
  uint32_t N_UL_hop = (cfg->enable_hopping) ? ((N_UL_BWP_RB < 50) ? 1 : 2) : 0;
  count += N_UL_hop;

  // Frequency domain resource assignment
  uint32_t N = dci_nr_freq_resource_size_type1(N_UL_BWP_RB);
  if (N < N_UL_hop) {
    return 0;
  }
  count += N - N_UL_hop;

  // Time domain resource assignment – 4 bits
  count += 4;

  // Frequency hopping flag – 1 bit
  count += 1;

  // Modulation and coding scheme – 5 bits
  count += 5;

  // New data indicator – 1 bit
  count += 1;

  // Redundancy version – 2 bits
  count += 2;

  // HARQ process number – 4 bits
  count += 4;

  // TPC command for scheduled PUSCH – 2 bits
  count += 2;

  // UL/SUL indicator – 1 bit for UEs configured with supplementaryUplink in ServingCellConfig, otherwise 0
  if (cfg->enable_sul) {
    count++;
  }

  return count;
}

static int dci_nr_format_0_0_pack(const srsran_dci_nr_t* q, const srsran_dci_ul_nr_t* dci, srsran_dci_msg_nr_t* msg)
{
  bool               is_common_ss = SRSRAN_SEARCH_SPACE_IS_COMMON(msg->ctx.ss_type);
  uint32_t           trunc        = is_common_ss ? q->dci_0_0_common_trunc : 0; // hard-coded bit truncation
  uint32_t           padding      = is_common_ss ? q->dci_0_0_common_padd : q->dci_0_0_ue_padd; // Hard-coded padding
  uint8_t*           y            = msg->payload;
  srsran_rnti_type_t rnti_type    = msg->ctx.rnti_type;
  uint32_t           N_UL_BWP_RB  = is_common_ss ? q->cfg.bwp_ul_initial_bw : q->cfg.bwp_ul_active_bw;

  // 1st of all, copy DCI context
  msg->ctx = dci->ctx;

  // Check RNTI type
  if (rnti_type != srsran_rnti_type_c && rnti_type != srsran_rnti_type_cs && rnti_type != srsran_rnti_type_mcs_c &&
      rnti_type != srsran_rnti_type_tc) {
    return SRSRAN_ERROR;
  }

  // Identifier for DCI formats – 1 bits
  *y = 0;
  y++;

  // For PUSCH hopping with resource allocation type 1 N UL_hop MSB bits are used to indicate the frequency offset
  int N_UL_hop = (q->cfg.enable_hopping) ? (N_UL_BWP_RB ? 1 : 2) : 0;
  srsran_bit_unpack(dci->frequency_offset, &y, N_UL_hop);

  // Frequency domain resource assignment
  uint32_t N = dci_nr_freq_resource_size_type1(N_UL_BWP_RB);
  if (N <= N_UL_hop) {
    return SRSRAN_ERROR;
  }
  srsran_bit_unpack(dci->freq_domain_assigment, &y, (int)(N - N_UL_hop - trunc));

  // Time domain resource assignment – 4 bits
  srsran_bit_unpack(dci->time_domain_assigment, &y, 4);

  // Frequency hopping flag – 1 bit
  srsran_bit_unpack(dci->freq_hopping_flag, &y, 1);

  // Modulation and coding scheme – 5 bits
  srsran_bit_unpack(dci->mcs, &y, 5);

  // New data indicator – 1 bit
  srsran_bit_unpack(dci->ndi, &y, 1);

  // Redundancy version – 2 bits
  srsran_bit_unpack(dci->rv, &y, 2);

  // HARQ process number – 4 bits
  srsran_bit_unpack(dci->pid, &y, 4);

  // TPC command for scheduled PUSCH – 2 bits
  srsran_bit_unpack(dci->tpc, &y, 2);

  // Padding goes here
  for (uint32_t i = 0; i < padding; i++) {
    *(y++) = 0;
  }

  // UL/SUL indicator – 1 bit for UEs configured with supplementaryUplink in ServingCellConfig, otherwise 0
  if (q->cfg.enable_sul) {
    *(y++) = 0;
  }

  msg->nof_bits = srsran_dci_nr_size(q, msg->ctx.ss_type, srsran_dci_format_nr_0_0);
  if (msg->nof_bits != y - msg->payload) {
    ERROR("Unpacked bits read (%d) do NOT match payload size (%d)", msg->nof_bits, (int)(y - msg->payload));
    return SRSRAN_ERROR;
  }

  return SRSRAN_SUCCESS;
}

static int dci_nr_format_0_0_unpack(const srsran_dci_nr_t* q, srsran_dci_msg_nr_t* msg, srsran_dci_ul_nr_t* dci)
{
  uint32_t                   trim           = 0;     // hard-coded bit trimming
  bool                       enable_hopping = false; // hard-coded PUSCH hopping
  uint32_t                   padding        = 8;     // Hard-coded padding
  uint8_t*                   y              = msg->payload;
  srsran_rnti_type_t         rnti_type      = msg->ctx.rnti_type;
  srsran_search_space_type_t ss_type        = msg->ctx.ss_type;
  uint32_t N_UL_BWP_RB = SRSRAN_SEARCH_SPACE_IS_COMMON(ss_type) ? q->cfg.bwp_ul_initial_bw : q->cfg.bwp_ul_active_bw;

  // Check RNTI type
  if (rnti_type != srsran_rnti_type_c && rnti_type != srsran_rnti_type_cs && rnti_type != srsran_rnti_type_mcs_c &&
      rnti_type != srsran_rnti_type_tc) {
    ERROR("Unsupported %s", srsran_rnti_type_str(rnti_type));
    return SRSRAN_ERROR;
  }

  uint32_t nof_bits = srsran_dci_nr_size(q, ss_type, srsran_dci_format_nr_0_0);
  if (msg->nof_bits != nof_bits) {
    ERROR("Invalid number of bits %d, expected %d", msg->nof_bits, nof_bits);
    return SRSRAN_ERROR;
  }

  // Identifier for DCI formats – 1 bits
  if (*(y++) != 0) {
    ERROR("Wrond DCI format");
    return SRSRAN_ERROR;
  }

  // For PUSCH hopping with resource allocation type 1 N UL_hop MSB bits are used to indicate the frequency offset
  uint32_t N_UL_hop     = (enable_hopping) ? ((N_UL_BWP_RB < 50) ? 1 : 2) : 0;
  dci->frequency_offset = srsran_bit_pack(&y, N_UL_hop);

  // Frequency domain resource assignment
  uint32_t N = dci_nr_freq_resource_size_type1(N_UL_BWP_RB);

  dci->freq_domain_assigment = srsran_bit_pack(&y, N - N_UL_hop - trim);

  // Time domain resource assignment – 4 bits
  dci->time_domain_assigment = srsran_bit_pack(&y, 4);

  // Frequency hopping flag – 1 bit
  dci->freq_hopping_flag = srsran_bit_pack(&y, 1);

  // Modulation and coding scheme – 5 bits
  dci->mcs = srsran_bit_pack(&y, 5);

  // New data indicator – 1 bit
  dci->ndi = srsran_bit_pack(&y, 1);

  // Redundancy version – 2 bits
  dci->rv = srsran_bit_pack(&y, 2);

  // HARQ process number – 4 bits
  dci->pid = srsran_bit_pack(&y, 4);

  // TPC command for scheduled PUSCH – 2 bits
  dci->tpc = srsran_bit_pack(&y, 2);

  // Padding goes here
  for (uint32_t i = 0; i < padding; i++) {
    y++;
  }

  // UL/SUL indicator – 1 bit for UEs configured with supplementaryUplink in ServingCellConfig, otherwise 0
  if (q->cfg.enable_sul) {
    dci->sul = srsran_bit_pack(&y, 1);
  }

  return SRSRAN_SUCCESS;
}

static uint32_t dci_nr_format_0_0_to_str(const srsran_dci_ul_nr_t* dci, char* str, uint32_t str_len)
{
  uint32_t len = 0;

  // Frequency domain resource assignment
  len = srsran_print_check(str, str_len, len, "f_alloc=0x%x ", dci->freq_domain_assigment);

  // Time domain resource assignment – 4 bits
  len = srsran_print_check(str, str_len, len, "t_alloc=0x%x ", dci->time_domain_assigment);

  // Frequency hopping flag – 1 bit
  len = srsran_print_check(str, str_len, len, "hop=%c ", dci->freq_hopping_flag == 0 ? 'n' : 'y');

  // Modulation and coding scheme – 5 bits
  len = srsran_print_check(str, str_len, len, "mcs=%d ", dci->mcs);

  // New data indicator – 1 bit
  len = srsran_print_check(str, str_len, len, "ndi=%d ", dci->ndi);

  // Redundancy version – 2 bits
  len = srsran_print_check(str, str_len, len, "rv=%d ", dci->rv);

  // HARQ process number – 4 bits
  len = srsran_print_check(str, str_len, len, "harq_id=%d ", dci->pid);

  // TPC command for scheduled PUSCH – 2 bits
  len = srsran_print_check(str, str_len, len, "tpc=%d ", dci->tpc);

  return len;
}

static uint32_t dci_nr_format_0_1_sizeof(const srsran_dci_cfg_nr_t* cfg, srsran_rnti_type_t rnti_type)
{
  uint32_t count = 0;

  if (rnti_type != srsran_rnti_type_c && rnti_type != srsran_rnti_type_cs && rnti_type != srsran_rnti_type_sp_csi &&
      rnti_type != srsran_rnti_type_mcs_c) {
    ERROR("Invalid RNTI (%s) for format 0_1", srsran_rnti_type_str(rnti_type));
    return SRSRAN_ERROR;
  }

  // Identifier for DCI formats – 1 bit
  count += 1;

  // Carrier indicator – 0 or 3 bits
  count += cfg->carrier_indicator_size;

  // UL/SUL indicator – 0 bit for UEs not configured with supplementaryUplink ... otherwise, 1 bit
  count += cfg->enable_sul ? 1 : 0;

  // Bandwidth part indicator – 0, 1 or 2 bits
  count += dci_nr_bwp_id_size(cfg->nof_ul_bwp);

  // Frequency domain resource assignment
  count += dci_nr_freq_resource_size(cfg->pusch_alloc_type, cfg->nof_rb_groups, cfg->bwp_ul_active_bw);

  // Time domain resource assigment - 0, 1, 2, 3, or 4 bits
  count += dci_nr_time_res_size(cfg->nof_ul_time_res);

  // Frequency hopping flag - 0 or 1 bit:
  if (cfg->pusch_alloc_type == srsran_resource_alloc_type0 || !cfg->enable_hopping) {
    count += 0;
  } else {
    count += 1;
  }

  // Modulation and coding scheme – 5 bits
  count += 5;

  // New data indicator – 1 bit
  count += 1;

  // Redundancy version – 2 bits
  count += 2;

  // HARQ process number – 4 bits
  count += 4;

  // 1st DAI - 1 or 2 bits
  if (cfg->harq_ack_codebok == srsran_pdsch_harq_ack_codebook_semi_static) {
    count += 1;
  } else {
    count += 2;
  }

  // 2st DAI - 0 or 2 bits
  if (cfg->dynamic_dual_harq_ack_codebook) {
    count += 2;
  }

  // TPC command for scheduled PUSCH – 2 bits
  count += 2;

  // SRS resource indicator
  count += dci_nr_srs_id_size(cfg);

  // Precoding information and number of layers
  if (!cfg->pusch_tx_config_non_codebook && cfg->nof_ul_layers > 1) {
    ERROR("Not implemented");
    return 0;
  }

  // Antenna ports
  count += dci_nr_ul_ports_size(cfg);

  // SRS request - 2 or 3 bits
  count += cfg->enable_sul ? 3 : 2;

  // CSI request - 0, 1, 2, 3, 4, 5, or 6 bits
  count += SRSRAN_MIN(6, cfg->report_trigger_size);

  // CBG transmission information - 0, 2, 4, 6, or 8 bits
  count += cfg->pusch_nof_cbg;

  // PTRS-DMRS association - 0 or 2 bits
  count += dci_nr_ptrs_size(cfg);

  // beta_offset indicator – 0 or 2 bits
  if (cfg->pusch_dynamic_betas) {
    count += 2;
  }

  // DMRS sequence initialization - 0 or 1 bit
  if (!cfg->enable_transform_precoding) {
    count += 1;
  }

  // UL-SCH indicator – 1 bit
  count += 1;

  return count;
}

static int dci_nr_format_0_1_pack(const srsran_dci_nr_t* q, const srsran_dci_ul_nr_t* dci, srsran_dci_msg_nr_t* msg)
{
  const srsran_dci_cfg_nr_t* cfg       = &q->cfg;
  srsran_rnti_type_t         rnti_type = dci->ctx.rnti_type;

  if (rnti_type != srsran_rnti_type_c && rnti_type != srsran_rnti_type_cs && rnti_type != srsran_rnti_type_sp_csi &&
      rnti_type != srsran_rnti_type_mcs_c) {
    ERROR("Invalid RNTI (%s) for format 0_1", srsran_rnti_type_str(rnti_type));
    return SRSRAN_ERROR;
  }
  uint8_t* y = msg->payload;

  // Identifier for DCI formats – 1 bit
  *(y++) = 0; // The value of this bit field is always set to 0, indicating an UL DCI format

  // Carrier indicator – 0 or 3 bits
  srsran_bit_unpack(dci->cc_id, &y, cfg->carrier_indicator_size);

  // UL/SUL indicator – 0 bit for UEs not configured with supplementaryUplink ... otherwise, 1 bit
  srsran_bit_unpack(dci->sul, &y, cfg->enable_sul ? 1 : 0);

  // Bandwidth part indicator – 0, 1 or 2 bits
  srsran_bit_unpack(dci->bwp_id, &y, dci_nr_bwp_id_size(cfg->nof_ul_bwp));

  // Frequency domain resource assignment
  srsran_bit_unpack(dci->freq_domain_assigment,
                    &y,
                    dci_nr_freq_resource_size(cfg->pusch_alloc_type, cfg->nof_rb_groups, cfg->bwp_ul_active_bw));

  // Time domain resource assigment - 0, 1, 2, 3, or 4 bits
  srsran_bit_unpack(dci->time_domain_assigment, &y, dci_nr_time_res_size(cfg->nof_ul_time_res));

  // Frequency hopping flag - 0 or 1 bit:
  if (cfg->pusch_alloc_type != srsran_resource_alloc_type0 && cfg->enable_hopping) {
    srsran_bit_unpack(dci->freq_hopping_flag, &y, 1);
  }

  // Modulation and coding scheme – 5 bits
  srsran_bit_unpack(dci->mcs, &y, 5);

  // New data indicator – 1 bit
  srsran_bit_unpack(dci->ndi, &y, 1);

  // Redundancy version – 2 bits
  srsran_bit_unpack(dci->rv, &y, 2);

  // HARQ process number – 4 bits
  srsran_bit_unpack(dci->pid, &y, 4);

  // 1st DAI - 1 or 2 bits
  if (cfg->harq_ack_codebok == srsran_pdsch_harq_ack_codebook_semi_static) {
    srsran_bit_unpack(dci->dai1, &y, 1);
  } else {
    srsran_bit_unpack(dci->dai1, &y, 2);
  }

  // 2st DAI - 0 or 2 bits
  if (cfg->dynamic_dual_harq_ack_codebook) {
    srsran_bit_unpack(dci->dai2, &y, 2);
  }

  // TPC command for scheduled PUSCH – 2 bits
  srsran_bit_unpack(dci->tpc, &y, 2);

  // SRS resource indicator
  srsran_bit_unpack(dci->srs_id, &y, dci_nr_srs_id_size(cfg));

  // Precoding information and number of layers
  if (cfg->pusch_tx_config_non_codebook) {
    ERROR("Not implemented");
    return 0;
  }

  // Antenna ports
  srsran_bit_unpack(dci->ports, &y, dci_nr_ul_ports_size(cfg));

  // SRS request - 2 or 3 bits
  srsran_bit_unpack(dci->srs_request, &y, cfg->enable_sul ? 3 : 2);

  // CSI request - 0, 1, 2, 3, 4, 5, or 6 bits
  srsran_bit_unpack(dci->csi_request, &y, SRSRAN_MIN(6, cfg->report_trigger_size));

  // CBG transmission information - 0, 2, 4, 6, or 8 bits
  srsran_bit_unpack(dci->cbg_info, &y, cfg->pusch_nof_cbg);

  // PTRS-DMRS association - 0 or 2 bits
  srsran_bit_unpack(dci->ptrs_id, &y, dci_nr_ptrs_size(cfg));

  // beta_offset indicator – 0 or 2 bits
  if (cfg->pusch_dynamic_betas) {
    srsran_bit_unpack(dci->beta_id, &y, 2);
  }

  // DMRS sequence initialization - 0 or 1 bit
  if (!cfg->enable_transform_precoding) {
    srsran_bit_unpack(dci->dmrs_id, &y, 1);
  }

  // UL-SCH indicator – 1 bit
  srsran_bit_unpack(dci->ulsch, &y, 1);

  msg->nof_bits = srsran_dci_nr_size(q, msg->ctx.ss_type, srsran_dci_format_nr_0_1);
  if (msg->nof_bits != y - msg->payload) {
    ERROR("Unpacked bits read (%d) do NOT match payload size (%d)", msg->nof_bits, (int)(y - msg->payload));
    return SRSRAN_ERROR;
  }

  return SRSRAN_SUCCESS;
}

static int dci_nr_format_0_1_unpack(const srsran_dci_nr_t* q, srsran_dci_msg_nr_t* msg, srsran_dci_ul_nr_t* dci)
{
  const srsran_dci_cfg_nr_t* cfg       = &q->cfg;
  srsran_rnti_type_t         rnti_type = dci->ctx.rnti_type;

  if (rnti_type != srsran_rnti_type_c && rnti_type != srsran_rnti_type_cs && rnti_type != srsran_rnti_type_sp_csi &&
      rnti_type != srsran_rnti_type_mcs_c) {
    ERROR("Invalid RNTI (%s) for format 0_1", srsran_rnti_type_str(rnti_type));
    return SRSRAN_ERROR;
  }
  uint8_t* y = msg->payload;

  // Identifier for DCI formats – 1 bits
  if (*(y++) != 0) {
    ERROR("Wrond DCI format");
    return SRSRAN_ERROR;
  }

  // Carrier indicator – 0 or 3 bits
  dci->cc_id = srsran_bit_pack(&y, SRSRAN_MIN(cfg->carrier_indicator_size, 3));

  // UL/SUL indicator – 0 bit for UEs not configured with supplementaryUplink ... otherwise, 1 bit
  dci->sul = srsran_bit_pack(&y, cfg->enable_sul ? 1 : 0);

  // Bandwidth part indicator – 0, 1 or 2 bits
  dci->bwp_id = srsran_bit_pack(&y, dci_nr_bwp_id_size(cfg->nof_ul_bwp));

  // Frequency domain resource assignment
  dci->freq_domain_assigment =
      srsran_bit_pack(&y, dci_nr_freq_resource_size(cfg->pusch_alloc_type, cfg->nof_rb_groups, cfg->bwp_ul_active_bw));

  // Time domain resource assigment - 0, 1, 2, 3, or 4 bits
  dci->time_domain_assigment = srsran_bit_pack(&y, dci_nr_time_res_size(cfg->nof_ul_time_res));

  // Frequency hopping flag - 0 or 1 bit:
  if (cfg->pusch_alloc_type != srsran_resource_alloc_type0 && cfg->enable_hopping) {
    dci->freq_hopping_flag = srsran_bit_pack(&y, 1);
  }

  // Modulation and coding scheme – 5 bits
  dci->mcs = srsran_bit_pack(&y, 5);

  // New data indicator – 1 bit
  dci->ndi = srsran_bit_pack(&y, 1);

  // Redundancy version – 2 bits
  dci->rv = srsran_bit_pack(&y, 2);

  // HARQ process number – 4 bits
  dci->pid = srsran_bit_pack(&y, 4);

  // 1st DAI - 1 or 2 bits
  if (cfg->harq_ack_codebok == srsran_pdsch_harq_ack_codebook_semi_static) {
    dci->dai1 = srsran_bit_pack(&y, 1);
  } else {
    dci->dai1 = srsran_bit_pack(&y, 2);
  }

  // 2st DAI - 0 or 2 bits
  if (cfg->dynamic_dual_harq_ack_codebook) {
    dci->dai2 = srsran_bit_pack(&y, 2);
  }

  // TPC command for scheduled PUSCH – 2 bits
  dci->tpc = srsran_bit_pack(&y, 2);

  // SRS resource indicator
  dci->srs_id = srsran_bit_pack(&y, dci_nr_srs_id_size(cfg));

  // Precoding information and number of layers
  if (cfg->pusch_tx_config_non_codebook) {
    ERROR("Not implemented");
    return 0;
  }

  // Antenna ports
  if (!cfg->enable_transform_precoding && cfg->pusch_dmrs_max_len == srsran_dmrs_sch_len_1) {
    dci->ports = srsran_bit_pack(&y, 3);
  } else {
    ERROR("Not implemented");
    return 0;
  }

  // SRS request - 2 or 3 bits
  dci->srs_request = srsran_bit_pack(&y, cfg->enable_sul ? 3 : 2);

  // CSI request - 0, 1, 2, 3, 4, 5, or 6 bits
  dci->csi_request = srsran_bit_pack(&y, SRSRAN_MIN(6, cfg->report_trigger_size));

  // CBG transmission information - 0, 2, 4, 6, or 8 bits
  dci->cbg_info = srsran_bit_pack(&y, cfg->pusch_nof_cbg);

  // PTRS-DMRS association - 0 or 2 bits
  dci->ptrs_id = srsran_bit_pack(&y, dci_nr_ptrs_size(cfg));

  // beta_offset indicator – 0 or 2 bits
  if (cfg->pusch_dynamic_betas) {
    dci->beta_id = srsran_bit_pack(&y, 2);
  }

  // DMRS sequence initialization - 0 or 1 bit
  if (!cfg->enable_transform_precoding) {
    dci->dmrs_id = srsran_bit_pack(&y, 1);
  }

  // UL-SCH indicator – 1 bit
  dci->ulsch = srsran_bit_pack(&y, 1);

  if (msg->nof_bits != y - msg->payload) {
    ERROR("Unpacked bits read (%d) do NOT match payload size (%d)", msg->nof_bits, (int)(y - msg->payload));
    return SRSRAN_ERROR;
  }

  return SRSRAN_SUCCESS;
}

static uint32_t
dci_nr_format_0_1_to_str(const srsran_dci_nr_t* q, const srsran_dci_ul_nr_t* dci, char* str, uint32_t str_len)
{
  uint32_t                   len = 0;
  const srsran_dci_cfg_nr_t* cfg = &q->cfg;

  // Carrier indicator – 0 or 3 bits
  if (cfg->carrier_indicator_size) {
    len = srsran_print_check(str, str_len, len, "cc=%d ", dci->cc_id);
  }

  // UL/SUL indicator – 0 bit for UEs not configured with supplementaryUplink ... otherwise, 1 bit
  if (cfg->enable_sul) {
    len = srsran_print_check(str, str_len, len, "sul=%d ", dci->sul);
  }

  // Bandwidth part indicator – 0, 1 or 2 bits
  if (dci_nr_bwp_id_size(cfg->nof_ul_bwp) > 0) {
    len = srsran_print_check(str, str_len, len, "bwp=%d ", dci->bwp_id);
  }

  // Frequency domain resource assignment
  len = srsran_print_check(str, str_len, len, "f_alloc=0x%x ", dci->freq_domain_assigment);

  // Time domain resource assigment - 0, 1, 2, 3, or 4 bits
  len = srsran_print_check(str, str_len, len, "t_alloc=0x%x ", dci->time_domain_assigment);

  // Frequency hopping flag - 0 or 1 bit:
  if (cfg->pusch_alloc_type != srsran_resource_alloc_type0 && cfg->enable_hopping) {
    len = srsran_print_check(str, str_len, len, "hop=0x%x ", dci->freq_hopping_flag);
  }

  // Modulation and coding scheme – 5 bits
  len = srsran_print_check(str, str_len, len, "mcs=%d ", dci->mcs);

  // New data indicator – 1 bit
  len = srsran_print_check(str, str_len, len, "ndi=%d ", dci->ndi);

  // Redundancy version – 2 bits
  len = srsran_print_check(str, str_len, len, "rv=%d ", dci->rv);

  // HARQ process number – 4 bits
  len = srsran_print_check(str, str_len, len, "harq_id=%d ", dci->pid);

  // 1st DAI - 1 or 2 bits
  len = srsran_print_check(str, str_len, len, "dai1=%d ", dci->dai1);

  // 2st DAI - 0 or 2 bits
  if (cfg->dynamic_dual_harq_ack_codebook) {
    len = srsran_print_check(str, str_len, len, "dai2=%d ", dci->dai2);
  }

  // TPC command for scheduled PUSCH – 2 bits
  len = srsran_print_check(str, str_len, len, "tpc=%d ", dci->tpc);

  // SRS resource indicator
  if (dci_nr_srs_id_size(cfg) > 0) {
    len = srsran_print_check(str, str_len, len, "srs_id=%d ", dci->srs_id);
  }

  // Precoding information and number of layers
  if (cfg->pusch_tx_config_non_codebook) {
    ERROR("Not implemented");
    return 0;
  }

  // Antenna ports
  if (dci_nr_ul_ports_size(cfg)) {
    len = srsran_print_check(str, str_len, len, "ports=%d ", dci->ports);
  }

  // SRS request - 2 bits
  len = srsran_print_check(str, str_len, len, "srs_req=%d ", dci->srs_request);

  // CSI request - 0, 1, 2, 3, 4, 5, or 6 bits
  if (cfg->report_trigger_size > 0) {
    len = srsran_print_check(str, str_len, len, "csi_req=%d ", dci->csi_request);
  }

  // CBG transmission information - 0, 2, 4, 6, or 8 bits
  if (cfg->pusch_nof_cbg > 0) {
    len = srsran_print_check(str, str_len, len, "cbg_info=%d ", dci->cbg_info);
  }

  // PTRS-DMRS association - 0 or 2 bits
  if (dci_nr_ptrs_size(cfg) > 0) {
    len = srsran_print_check(str, str_len, len, "ptrs_id=%d ", dci->ptrs_id);
  }

  // beta_offset indicator – 0 or 2 bits
  if (cfg->pusch_dynamic_betas) {
    len = srsran_print_check(str, str_len, len, "beta_id=%d ", dci->beta_id);
  }

  // DMRS sequence initialization - 0 or 1 bit
  if (!cfg->enable_transform_precoding) {
    len = srsran_print_check(str, str_len, len, "dmrs_id=%d ", dci->dmrs_id);
  }

  // UL-SCH indicator – 1 bit
  len = srsran_print_check(str, str_len, len, "ulsch=%d ", dci->ulsch);

  return len;
}

static uint32_t dci_nr_rar_sizeof()
{
  // Fields described by TS 38.213 Table 8.2-1: Random Access Response Grant Content field size
  uint32_t count = 0;

  // Frequency hopping flag - 1 bit
  count += 1;

  // PUSCH frequency resource allocation - 14 bits
  count += 14;

  // PUSCH time resource allocation - 4 bits
  count += 4;

  // MCS - 4 bits
  count += 4;

  // TPC command for PUSCH - 3 bits
  count += 3;

  // CSI request - 1 bits
  count += 1;

  return count;
}

static int dci_nr_rar_pack(const srsran_dci_ul_nr_t* dci, srsran_dci_msg_nr_t* msg)
{
  // Fields described by TS 38.213 Table 8.2-1: Random Access Response Grant Content field size
  uint8_t* y = msg->payload;

  // Frequency hopping flag - 1 bit
  srsran_bit_unpack(dci->freq_hopping_flag, &y, 1);

  // PUSCH frequency resource allocation - 14 bits
  srsran_bit_unpack(dci->freq_domain_assigment, &y, 14);

  // PUSCH time resource allocation - 4 bits
  srsran_bit_unpack(dci->time_domain_assigment, &y, 4);

  // MCS - 4 bits
  srsran_bit_unpack(dci->mcs, &y, 4);

  // TPC command for PUSCH - 3 bits
  srsran_bit_unpack(dci->tpc, &y, 3);

  // CSI request - 1 bits
  srsran_bit_unpack(dci->csi_request, &y, 1);

  return SRSRAN_SUCCESS;
}

static int dci_nr_rar_unpack(srsran_dci_msg_nr_t* msg, srsran_dci_ul_nr_t* dci)
{
  // Fields described by TS 38.213 Table 8.2-1: Random Access Response Grant Content field size
  uint8_t* y = msg->payload;

  // Copy DCI MSG fields
  dci->ctx = msg->ctx;

  // Frequency hopping flag - 1 bit
  dci->freq_hopping_flag = srsran_bit_pack(&y, 1);

  // PUSCH frequency resource allocation - 14 bits
  dci->freq_domain_assigment = srsran_bit_pack(&y, 14);

  // PUSCH time resource allocation - 4 bits
  dci->time_domain_assigment = srsran_bit_pack(&y, 4);

  // MCS -4 bits
  dci->mcs = srsran_bit_pack(&y, 4);

  // TPC command for PUSCH - 3 bits
  dci->tpc = srsran_bit_pack(&y, 3);

  // CSI request - 1 bits
  dci->csi_request = srsran_bit_pack(&y, 1);

  return SRSRAN_SUCCESS;
}

static uint32_t dci_nr_rar_to_str(const srsran_dci_ul_nr_t* dci, char* str, uint32_t str_len)
{
  uint32_t len = 0;

  // Frequency hopping flag
  len = srsran_print_check(str, str_len, len, "hop=%d ", dci->freq_hopping_flag);

  // PUSCH frequency resource allocation
  len = srsran_print_check(str, str_len, len, "f_alloc=0x%x ", dci->freq_domain_assigment);

  // PUSCH time resource allocation
  len = srsran_print_check(str, str_len, len, "t_alloc=0x%x ", dci->time_domain_assigment);

  // Modulation and coding scheme
  len = srsran_print_check(str, str_len, len, "mcs=%d ", dci->mcs);

  // TPC command for scheduled PUSCH
  len = srsran_print_check(str, str_len, len, "tpc=%d ", dci->tpc);

  // CSI request
  len = srsran_print_check(str, str_len, len, "csi=%d ", dci->csi_request);

  return len;
}

static uint32_t dci_nr_format_1_0_sizeof(uint32_t N_DL_BWP_RB, srsran_rnti_type_t rnti_type)
{
  uint32_t count = 0;

  // Identifier for DCI formats – 1 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    count += 1;
  }

  if (rnti_type == srsran_rnti_type_p) {
    // Short Messages Indicator – 2 bits
    count += 2;

    // Short Messages – 8 bits
    count += 8;
  }

  // Frequency domain resource assignment
  int N = dci_nr_freq_resource_size_type1(N_DL_BWP_RB);
  if (N < SRSRAN_SUCCESS) {
    return SRSRAN_ERROR;
  }
  count += N;

  // Time domain resource assignment – 4 bits
  count += 4;

  // VRB-to-PRB mapping – 1 bit
  count += 1;

  // Modulation and coding scheme – 5 bits
  count += 5;

  // TB scaling – 2 bits
  if (rnti_type == srsran_rnti_type_p || rnti_type == srsran_rnti_type_ra) {
    count += 2;
  }

  // New data indicator – 1 bit
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    count += 1;
  }

  // Redundancy version – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_si || rnti_type == srsran_rnti_type_tc) {
    count += 2;
  }

  // HARQ process number – 4 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    count += 4;
  }

  // System information indicator – 1 bit
  if (rnti_type == srsran_rnti_type_si) {
    count += 1;
  }

  // Downlink assignment index – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    count += 2;
  }

  // TPC command for scheduled PUCCH – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    count += 2;
  }

  // PUCCH resource indicator – 3 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    count += 3;
  }

  // PDSCH-to-HARQ_feedback timing indicator – 3 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    count += 3;
  }

  // Reserved bits ...
  if (rnti_type == srsran_rnti_type_p) {
    // ... – 6 bits
    count += 2;
  } else if (rnti_type == srsran_rnti_type_si) {
    // ... – 15 bits
    count += 15;
  } else if (rnti_type == srsran_rnti_type_ra) {
    // ... – 16 bits
    count += 16;
  }

  return count;
}

static int dci_nr_format_1_0_pack(const srsran_dci_nr_t* q, const srsran_dci_dl_nr_t* dci, srsran_dci_msg_nr_t* msg)
{
  uint8_t*                   y           = msg->payload;
  srsran_rnti_type_t         rnti_type   = msg->ctx.rnti_type;
  srsran_search_space_type_t ss_type     = dci->ctx.ss_type;
  uint32_t                   N_DL_BWP_RB = SRSRAN_SEARCH_SPACE_IS_COMMON(ss_type)
                                               ? (q->cfg.coreset0_bw == 0) ? q->cfg.bwp_dl_initial_bw : q->cfg.coreset0_bw
                                               : q->cfg.bwp_dl_active_bw;

  // Identifier for DCI formats – 1 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    *y = 1;
    y++;
  }

  if (rnti_type == srsran_rnti_type_p) {
    // Short Messages Indicator – 2 bits
    srsran_bit_unpack(dci->smi, &y, 2);

    // Short Messages – 8 bits
    srsran_bit_unpack(dci->sm, &y, 8);
  }

  // Frequency domain resource assignment
  int N = dci_nr_freq_resource_size_type1(N_DL_BWP_RB);
  if (N < SRSRAN_SUCCESS) {
    return SRSRAN_ERROR;
  }
  srsran_bit_unpack(dci->freq_domain_assigment, &y, N);

  // Time domain resource assignment – 4 bits
  srsran_bit_unpack(dci->time_domain_assigment, &y, 4);

  // VRB-to-PRB mapping – 1 bit
  srsran_bit_unpack(dci->vrb_to_prb_mapping, &y, 1);

  // Modulation and coding scheme – 5 bits
  srsran_bit_unpack(dci->mcs, &y, 5);

  // TB scaling – 2 bits
  if (rnti_type == srsran_rnti_type_p || rnti_type == srsran_rnti_type_ra) {
    srsran_bit_unpack(dci->tb_scaling, &y, 2);
  }

  // New data indicator – 1 bit
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    srsran_bit_unpack(dci->ndi, &y, 1);
  }

  // Redundancy version – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_si || rnti_type == srsran_rnti_type_tc) {
    srsran_bit_unpack(dci->rv, &y, 2);
  }

  // HARQ process number – 4 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    srsran_bit_unpack(dci->pid, &y, 4);
  }

  // System information indicator – 1 bit
  if (rnti_type == srsran_rnti_type_si) {
    srsran_bit_unpack(dci->sii, &y, 1);
  }

  // Downlink assignment index – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    srsran_bit_unpack(dci->dai, &y, 2);
  }

  // TPC command for scheduled PUCCH – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    srsran_bit_unpack(dci->tpc, &y, 2);
  }

  // PUCCH resource indicator – 3 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    srsran_bit_unpack(dci->pucch_resource, &y, 3);
  }

  // PDSCH-to-HARQ_feedback timing indicator – 3 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    srsran_bit_unpack(dci->harq_feedback, &y, 3);
  }

  // Reserved bits ...
  if (rnti_type == srsran_rnti_type_p) {
    // ... – 6 bits
    srsran_bit_unpack(dci->reserved, &y, 6);
  } else if (rnti_type == srsran_rnti_type_si) {
    // ... – 15 bits
    srsran_bit_unpack(dci->reserved, &y, 15);
  } else if (rnti_type == srsran_rnti_type_ra) {
    // ... – 16 bits
    srsran_bit_unpack(dci->reserved, &y, 16);
  }

  msg->nof_bits     = srsran_dci_nr_size(q, msg->ctx.ss_type, srsran_dci_format_nr_1_0);
  uint32_t nof_bits = (uint32_t)(y - msg->payload);
  if (msg->nof_bits != nof_bits) {
    ERROR("Unpacked bits read (%d) do NOT match payload size (%d)", msg->nof_bits, nof_bits);
    return SRSRAN_ERROR;
  }

  return SRSRAN_SUCCESS;
}

static int dci_nr_format_1_0_unpack(const srsran_dci_nr_t* q, srsran_dci_msg_nr_t* msg, srsran_dci_dl_nr_t* dci)
{
  uint8_t*                   y           = msg->payload;
  srsran_rnti_type_t         rnti_type   = msg->ctx.rnti_type;
  srsran_search_space_type_t ss_type     = msg->ctx.ss_type;
  uint32_t                   N_DL_BWP_RB = SRSRAN_SEARCH_SPACE_IS_COMMON(ss_type)
                                               ? (q->cfg.coreset0_bw == 0) ? q->cfg.bwp_dl_initial_bw : q->cfg.coreset0_bw
                                               : q->cfg.bwp_dl_active_bw;

  uint32_t nof_bits = srsran_dci_nr_size(q, ss_type, srsran_dci_format_nr_1_0);
  if (msg->nof_bits != nof_bits) {
    ERROR("Invalid number of bits %d, expected %d", msg->nof_bits, nof_bits);
    return SRSRAN_ERROR;
  }

  // Identifier for DCI formats – 1 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    // The value of this bit field is always set to 1, indicating a DL DCI format
    if (*(y++) != 1) {
      ERROR("Wrond DCI format");
      return SRSRAN_ERROR;
    }
  }

  if (rnti_type == srsran_rnti_type_p) {
    // Short Messages Indicator – 2 bits
    dci->smi = srsran_bit_pack(&y, 2);

    // Short Messages – 8 bits
    dci->sm = srsran_bit_pack(&y, 8);
  }

  // Frequency domain resource assignment
  int N = dci_nr_freq_resource_size_type1(N_DL_BWP_RB);
  if (N < SRSRAN_SUCCESS) {
    return SRSRAN_ERROR;
  }
  dci->freq_domain_assigment = srsran_bit_pack(&y, N);

  // Time domain resource assignment – 4 bits
  dci->time_domain_assigment = srsran_bit_pack(&y, 4);

  // VRB-to-PRB mapping – 1 bit
  dci->vrb_to_prb_mapping = srsran_bit_pack(&y, 1);

  // Modulation and coding scheme – 5 bits
  dci->mcs = srsran_bit_pack(&y, 5);

  // TB scaling – 2 bits
  if (rnti_type == srsran_rnti_type_p || rnti_type == srsran_rnti_type_ra) {
    dci->tb_scaling = srsran_bit_pack(&y, 2);
  }

  // New data indicator – 1 bit
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    dci->ndi = srsran_bit_pack(&y, 1);
  }

  // Redundancy version – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_si || rnti_type == srsran_rnti_type_tc) {
    dci->rv = srsran_bit_pack(&y, 2);
  }

  // HARQ process number – 4 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    dci->pid = srsran_bit_pack(&y, 4);
  }

  // System information indicator – 1 bit
  if (rnti_type == srsran_rnti_type_si) {
    dci->sii = srsran_bit_pack(&y, 1);
  }

  // Downlink assignment index – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    dci->dai = srsran_bit_pack(&y, 2);
  }

  // TPC command for scheduled PUCCH – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    dci->tpc = srsran_bit_pack(&y, 2);
  }

  // PUCCH resource indicator – 3 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    dci->pucch_resource = srsran_bit_pack(&y, 3);
  }

  // PDSCH-to-HARQ_feedback timing indicator – 3 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    dci->harq_feedback = srsran_bit_pack(&y, 3);
  }

  // Reserved bits ...
  if (rnti_type == srsran_rnti_type_p) {
    // ... – 6 bits
    dci->reserved = srsran_bit_pack(&y, 6);
  } else if (rnti_type == srsran_rnti_type_si) {
    // ... – 15 bits
    dci->reserved = srsran_bit_pack(&y, 15);
  } else if (rnti_type == srsran_rnti_type_ra) {
    // ... – 16 bits
    dci->reserved = srsran_bit_pack(&y, 16);
  }

  if (msg->nof_bits != y - msg->payload) {
    ERROR("Unpacked bits read (%d) do NOT match payload size (%d)", msg->nof_bits, (int)(y - msg->payload));
    return SRSRAN_ERROR;
  }

  return SRSRAN_SUCCESS;
}

static uint32_t dci_nr_format_1_0_to_str(const srsran_dci_dl_nr_t* dci, char* str, uint32_t str_len)
{
  uint32_t           len       = 0;
  srsran_rnti_type_t rnti_type = dci->ctx.rnti_type;

  if (rnti_type == srsran_rnti_type_p) {
    len = srsran_print_check(str, str_len, len, "smi=%d sm=%d ", dci->smi, dci->sm);
  }

  // Frequency domain resource assignment
  len = srsran_print_check(str, str_len, len, "f_alloc=0x%x ", dci->freq_domain_assigment);

  // Time domain resource assignment – 4 bits
  len = srsran_print_check(str, str_len, len, "t_alloc=0x%x ", dci->time_domain_assigment);

  // VRB-to-PRB mapping – 1 bit
  len = srsran_print_check(str, str_len, len, "vrb_to_prb_map=%d ", dci->vrb_to_prb_mapping);

  // Modulation and coding scheme – 5 bits
  len = srsran_print_check(str, str_len, len, "mcs=%d ", dci->mcs);

  // TB scaling – 2 bits
  if (rnti_type == srsran_rnti_type_p || rnti_type == srsran_rnti_type_ra) {
    len = srsran_print_check(str, str_len, len, "tb_scaling=%d ", dci->tb_scaling);
  }

  // New data indicator – 1 bit
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    len = srsran_print_check(str, str_len, len, "ndi=%d ", dci->ndi);
  }

  // Redundancy version – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_si || rnti_type == srsran_rnti_type_tc) {
    len = srsran_print_check(str, str_len, len, "rv=%d ", dci->rv);
  }

  // HARQ process number – 4 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    len = srsran_print_check(str, str_len, len, "harq_id=%d ", dci->pid);
  }

  // System information indicator – 1 bit
  if (rnti_type == srsran_rnti_type_si) {
    len = srsran_print_check(str, str_len, len, "sii=%d ", dci->sii);
    len = srsran_print_check(str, str_len, len, "coreset0_bw=%d ", dci->coreset0_bw);
  }

  // Downlink assignment index – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    len = srsran_print_check(str, str_len, len, "dai=%d ", dci->dai);
  }

  // TPC command for scheduled PUCCH – 2 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    len = srsran_print_check(str, str_len, len, "pucch_tpc=%d ", dci->tpc);
  }

  // PUCCH resource indicator – 3 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    len = srsran_print_check(str, str_len, len, "pucch_res=%d ", dci->pucch_resource);
  }

  // PDSCH-to-HARQ_feedback timing indicator – 3 bits
  if (rnti_type == srsran_rnti_type_c || rnti_type == srsran_rnti_type_tc) {
    len = srsran_print_check(str, str_len, len, "harq_feedback=%d ", dci->harq_feedback);
  }

  // Reserved bits ...
  if (rnti_type == srsran_rnti_type_p || rnti_type == srsran_rnti_type_si || rnti_type == srsran_rnti_type_ra) {
    len = srsran_print_check(str, str_len, len, "reserved=0x%x ", dci->reserved);
  }

  return len;
}

static uint32_t dci_nr_format_1_1_sizeof(const srsran_dci_cfg_nr_t* cfg, srsran_rnti_type_t rnti_type)
{
  int count = 0;

  if (rnti_type != srsran_rnti_type_c && rnti_type != srsran_rnti_type_cs && rnti_type != srsran_rnti_type_mcs_c) {
    ERROR("Invalid RNTI (%s) for format 1_1", srsran_rnti_type_str(rnti_type));
    return SRSRAN_ERROR;
  }

  // Identifier for DCI formats – 1 bits
  count += 1;

  // Carrier indicator – 0 or 3 bits
  count += (int)cfg->carrier_indicator_size;

  // Bandwidth part indicator – 0, 1 or 2 bits
  count += (int)dci_nr_bwp_id_size(cfg->nof_dl_bwp);

  // Frequency domain resource assignment
  count += dci_nr_freq_resource_size(cfg->pdsch_alloc_type, cfg->nof_rb_groups, cfg->bwp_dl_active_bw);

  // Time domain resource assignment – 0, 1, 2, 3, or 4 bits
  count += dci_nr_time_res_size(cfg->nof_dl_time_res);

  // VRB-to-PRB mapping – 0 or 1
  if (cfg->pdsch_alloc_type != srsran_resource_alloc_type0 && cfg->pdsch_inter_prb_to_prb) {
    count += 1;
  }

  // PRB bundling size indicator – 0 or 1 bits
  // ... not implemented

  // Rate matching indicator – 0, 1, or 2 bits
  if (cfg->pdsch_rm_pattern1) {
    count += 1;
  }
  if (cfg->pdsch_rm_pattern2) {
    count += 1;
  }

  // ZP CSI-RS trigger - 0, 1, or 2 bits
  count += (int)SRSRAN_CEIL_LOG2(cfg->nof_aperiodic_zp + 1);

  // For transport block 1:
  // Modulation and coding scheme – 5 bits
  count += 5;

  // New data indicator – 1 bit
  count += 1;

  // Redundancy version – 2 bits
  count += 2;

  // For transport block 2:
  if (cfg->pdsch_2cw) {
    // Modulation and coding scheme – 5 bits
    count += 5;

    // New data indicator – 1 bit
    count += 1;

    // Redundancy version – 2 bits
    count += 2;
  }

  // HARQ process number – 4 bits
  count += 4;

  // Downlink assignment index (dynamic HARQ-ACK codebook only)
  if (cfg->harq_ack_codebok == srsran_pdsch_harq_ack_codebook_dynamic) {
    if (cfg->multiple_scell) {
      count += 4;
    } else {
      count += 2;
    }
  }

  // TPC command for scheduled PUCCH – 2 bits
  count += 2;

  // PUCCH resource indicator – 3 bits
  count += 3;

  // PDSCH-to-HARQ_feedback timing indicator – 0, 1, 2, or 3 bits
  count += (int)SRSRAN_CEIL_LOG2(cfg->nof_dl_to_ul_ack);

  // Antenna port(s) – 4, 5, or 6 bits
  count += dci_nr_dl_ports_size(cfg);

  // Transmission configuration indication – 0 or 3 bits
  if (cfg->pdsch_tci) {
    count += 3;
  }

  // SRS request – 2 or 3 bits
  count += cfg->enable_sul ? 3 : 2;

  // CBG transmission information (CBGTI) – 0, 2, 4, 6, or 8 bits
  count += cfg->pdsch_nof_cbg;

  // CBG flushing out information (CBGFI) – 0 or 1 bit
  if (cfg->pdsch_cbg_flush) {
    count += 1;
  }

  // DMRS sequence initialization – 1 bit
  count += 1;

  return count;
}

static int dci_nr_format_1_1_pack(const srsran_dci_nr_t* q, const srsran_dci_dl_nr_t* dci, srsran_dci_msg_nr_t* msg)
{
  uint8_t*                   y         = msg->payload;
  srsran_rnti_type_t         rnti_type = msg->ctx.rnti_type;
  const srsran_dci_cfg_nr_t* cfg       = &q->cfg;

  if (rnti_type != srsran_rnti_type_c && rnti_type != srsran_rnti_type_cs && rnti_type != srsran_rnti_type_mcs_c) {
    ERROR("Invalid RNTI (%s) for format 1_1", srsran_rnti_type_str(rnti_type));
    return SRSRAN_ERROR;
  }

  // Identifier for DCI formats – 1 bits
  *(y++) = 1;

  // Carrier indicator – 0 or 3 bits
  srsran_bit_unpack(dci->cc_id, &y, cfg->carrier_indicator_size);

  // Bandwidth part indicator – 0, 1 or 2 bits
  srsran_bit_unpack(dci->bwp_id, &y, dci_nr_bwp_id_size(cfg->nof_dl_bwp));

  // Frequency domain resource assignment
  srsran_bit_unpack(dci->freq_domain_assigment,
                    &y,
                    dci_nr_freq_resource_size(cfg->pdsch_alloc_type, cfg->nof_rb_groups, cfg->bwp_dl_active_bw));

  // Time domain resource assignment – 0, 1, 2, 3, or 4 bits
  srsran_bit_unpack(dci->time_domain_assigment, &y, dci_nr_time_res_size(cfg->nof_dl_time_res));

  // VRB-to-PRB mapping – 0 or 1
  if (cfg->pdsch_alloc_type != srsran_resource_alloc_type0 && cfg->pdsch_inter_prb_to_prb) {
    srsran_bit_unpack(dci->vrb_to_prb_mapping, &y, 1);
  }

  // PRB bundling size indicator – 0 or 1 bits
  // ... not implemented

  // Rate matching indicator – 0, 1, or 2 bits
  if (cfg->pdsch_rm_pattern1) {
    srsran_bit_unpack(dci->rm_pattern1, &y, 1);
  }

  if (cfg->pdsch_rm_pattern2) {
    srsran_bit_unpack(dci->rm_pattern2, &y, 1);
  }

  // ZP CSI-RS trigger - 0, 1, or 2 bits
  srsran_bit_unpack(dci->zp_csi_rs_id, &y, SRSRAN_CEIL_LOG2(cfg->nof_aperiodic_zp + 1));

  // For transport block 1:
  // Modulation and coding scheme – 5 bits
  srsran_bit_unpack(dci->mcs, &y, 5);

  // New data indicator – 1 bit
  srsran_bit_unpack(dci->ndi, &y, 1);

  // Redundancy version – 2 bits
  srsran_bit_unpack(dci->rv, &y, 2);

  // For transport block 2:
  if (cfg->pdsch_2cw) {
    // Modulation and coding scheme – 5 bits
    srsran_bit_unpack(dci->mcs2, &y, 5);

    // New data indicator – 1 bit
    srsran_bit_unpack(dci->ndi2, &y, 1);

    // Redundancy version – 2 bits
    srsran_bit_unpack(dci->rv2, &y, 2);
  }

  // HARQ process number – 4 bits
  srsran_bit_unpack(dci->pid, &y, 4);

  // Downlink assignment index (dynamic HARQ-ACK codebook only)
  if (cfg->harq_ack_codebok == srsran_pdsch_harq_ack_codebook_dynamic) {
    if (cfg->multiple_scell) {
      srsran_bit_unpack(dci->dai, &y, 4);
    } else {
      srsran_bit_unpack(dci->dai, &y, 2);
    }
  }

  // TPC command for scheduled PUCCH – 2 bits
  srsran_bit_unpack(dci->tpc, &y, 2);

  // PUCCH resource indicator – 3 bits
  srsran_bit_unpack(dci->pucch_resource, &y, 3);

  // PDSCH-to-HARQ_feedback timing indicator – 0, 1, 2, or 3 bits
  srsran_bit_unpack(dci->harq_feedback, &y, (int)SRSRAN_CEIL_LOG2(cfg->nof_dl_to_ul_ack));

  // Antenna port(s) – 4, 5, or 6 bits
  srsran_bit_unpack(dci->ports, &y, dci_nr_dl_ports_size(cfg));

  // Transmission configuration indication – 0 or 3 bits
  if (cfg->pdsch_tci) {
    srsran_bit_unpack(dci->tci, &y, 3);
  }

  // SRS request – 2 or 3 bits
  srsran_bit_unpack(dci->srs_request, &y, cfg->enable_sul ? 3 : 2);

  // CBG transmission information (CBGTI) – 0, 2, 4, 6, or 8 bits
  srsran_bit_unpack(dci->cbg_info, &y, cfg->pdsch_nof_cbg);

  // CBG flushing out information (CBGFI) – 0 or 1 bit
  if (cfg->pdsch_cbg_flush) {
    srsran_bit_unpack(dci->cbg_flush, &y, 1);
  }

  // DMRS sequence initialization – 1 bit
  srsran_bit_unpack(dci->dmrs_id, &y, 1);

  msg->nof_bits = srsran_dci_nr_size(q, msg->ctx.ss_type, srsran_dci_format_nr_1_1);
  if (msg->nof_bits != y - msg->payload) {
    ERROR("Unpacked bits read (%d) do NOT match payload size (%d)", msg->nof_bits, (int)(y - msg->payload));
    return SRSRAN_ERROR;
  }
  return SRSRAN_SUCCESS;
}

static int dci_nr_format_1_1_unpack(const srsran_dci_nr_t* q, srsran_dci_msg_nr_t* msg, srsran_dci_dl_nr_t* dci)
{
  uint8_t*                   y         = msg->payload;
  srsran_rnti_type_t         rnti_type = msg->ctx.rnti_type;
  const srsran_dci_cfg_nr_t* cfg       = &q->cfg;

  if (rnti_type != srsran_rnti_type_c && rnti_type != srsran_rnti_type_cs && rnti_type != srsran_rnti_type_mcs_c) {
    ERROR("Invalid RNTI (%s) for format 1_1", srsran_rnti_type_str(rnti_type));
    return SRSRAN_ERROR;
  }

  uint32_t nof_bits = srsran_dci_nr_size(q, msg->ctx.ss_type, srsran_dci_format_nr_1_1);
  if (msg->nof_bits != nof_bits) {
    ERROR("Invalid number of bits %d, expected %d", msg->nof_bits, nof_bits);
    return SRSRAN_ERROR;
  }

  // Identifier for DCI formats – 1 bits
  // The value of this bit field is always set to 1, indicating a DL DCI format
  if (*(y++) != 1) {
    ERROR("Wrond DCI format");
    return SRSRAN_ERROR;
  }

  // Carrier indicator – 0 or 3 bits
  dci->cc_id = srsran_bit_pack(&y, cfg->carrier_indicator_size);

  // Bandwidth part indicator – 0, 1 or 2 bits
  dci->bwp_id = srsran_bit_pack(&y, dci_nr_bwp_id_size(cfg->nof_dl_bwp));

  // Frequency domain resource assignment
  dci->freq_domain_assigment =
      srsran_bit_pack(&y, dci_nr_freq_resource_size(cfg->pdsch_alloc_type, cfg->nof_rb_groups, cfg->bwp_dl_active_bw));

  // Time domain resource assignment – 0, 1, 2, 3, or 4 bits
  dci->time_domain_assigment = srsran_bit_pack(&y, dci_nr_time_res_size(cfg->nof_dl_time_res));

  // VRB-to-PRB mapping – 0 or 1
  if (cfg->pdsch_alloc_type != srsran_resource_alloc_type0 && cfg->pdsch_inter_prb_to_prb) {
    dci->vrb_to_prb_mapping = srsran_bit_pack(&y, 1);
  }

  // PRB bundling size indicator – 0 or 1 bits
  // ... not implemented

  // Rate matching indicator – 0, 1, or 2 bits
  if (cfg->pdsch_rm_pattern1) {
    dci->rm_pattern1 = srsran_bit_pack(&y, 1);
  }

  if (cfg->pdsch_rm_pattern2) {
    dci->rm_pattern2 = srsran_bit_pack(&y, 1);
  }

  // ZP CSI-RS trigger - 0, 1, or 2 bits
  dci->zp_csi_rs_id = srsran_bit_pack(&y, SRSRAN_CEIL_LOG2(cfg->nof_aperiodic_zp + 1));

  // For transport block 1:
  // Modulation and coding scheme – 5 bits
  dci->mcs = srsran_bit_pack(&y, 5);

  // New data indicator – 1 bit
  dci->ndi = srsran_bit_pack(&y, 1);

  // Redundancy version – 2 bits
  dci->rv = srsran_bit_pack(&y, 2);

  // For transport block 2:
  if (cfg->pdsch_2cw) {
    // Modulation and coding scheme – 5 bits
    dci->mcs2 = srsran_bit_pack(&y, 5);

    // New data indicator – 1 bit
    dci->ndi2 = srsran_bit_pack(&y, 1);

    // Redundancy version – 2 bits
    dci->rv2 = srsran_bit_pack(&y, 2);
  }

  // HARQ process number – 4 bits
  dci->pid = srsran_bit_pack(&y, 4);

  // Downlink assignment index (dynamic HARQ-ACK codebook only)
  if (cfg->harq_ack_codebok == srsran_pdsch_harq_ack_codebook_dynamic) {
    if (cfg->multiple_scell) {
      dci->dai = srsran_bit_pack(&y, 4);
    } else {
      dci->dai = srsran_bit_pack(&y, 2);
    }
  }

  // TPC command for scheduled PUCCH – 2 bits
  dci->tpc = srsran_bit_pack(&y, 2);

  // PUCCH resource indicator – 3 bits
  dci->pucch_resource = srsran_bit_pack(&y, 3);

  // PDSCH-to-HARQ_feedback timing indicator – 0, 1, 2, or 3 bits
  dci->harq_feedback = srsran_bit_pack(&y, (int)SRSRAN_CEIL_LOG2(cfg->nof_dl_to_ul_ack));

  // Antenna port(s) – 4, 5, or 6 bits
  dci->ports = srsran_bit_pack(&y, dci_nr_dl_ports_size(cfg));

  // Transmission configuration indication – 0 or 3 bits
  if (cfg->pdsch_tci) {
    dci->tci = srsran_bit_pack(&y, 3);
  }

  // SRS request – 2 or 3 bits
  dci->srs_request = srsran_bit_pack(&y, cfg->enable_sul ? 3 : 2);

  // CBG transmission information (CBGTI) – 0, 2, 4, 6, or 8 bits
  dci->cbg_info = srsran_bit_pack(&y, cfg->pdsch_nof_cbg);

  // CBG flushing out information (CBGFI) – 0 or 1 bit
  if (cfg->pdsch_cbg_flush) {
    dci->cbg_flush = srsran_bit_pack(&y, 1);
  }

  // DMRS sequence initialization – 1 bit
  dci->dmrs_id = srsran_bit_pack(&y, 1);

  uint32_t nof_unpacked_bits = (uint32_t)(y - msg->payload);
  if (nof_unpacked_bits != nof_bits) {
    ERROR("Unpacked bits read (%d) do NOT match payload size (%d)", nof_unpacked_bits, nof_bits);
    return SRSRAN_ERROR;
  }

  return SRSRAN_SUCCESS;
}

static uint32_t
dci_nr_format_1_1_to_str(const srsran_dci_nr_t* q, const srsran_dci_dl_nr_t* dci, char* str, uint32_t str_len)
{
  uint32_t                   len = 0;
  const srsran_dci_cfg_nr_t* cfg = &q->cfg;

  // Carrier indicator – 0 or 3 bits
  if (cfg->carrier_indicator_size > 0) {
    len = srsran_print_check(str, str_len, len, "cc=%d ", dci->cc_id);
  }

  // Bandwidth part indicator – 0, 1 or 2 bits
  if (dci_nr_bwp_id_size(cfg->nof_dl_bwp) > 0) {
    len = srsran_print_check(str, str_len, len, "bwp=%d ", dci->bwp_id);
  }

  // Frequency domain resource assignment
  len = srsran_print_check(str, str_len, len, "f_alloc=0x%x ", dci->freq_domain_assigment);

  // Time domain resource assignment – 0, 1, 2, 3, or 4 bits
  if (cfg->nof_dl_time_res > 0) {
    len = srsran_print_check(str, str_len, len, "t_alloc=0x%x ", dci->time_domain_assigment);
  }

  // VRB-to-PRB mapping – 0 or 1
  if (cfg->pdsch_alloc_type != srsran_resource_alloc_type0 && cfg->pdsch_inter_prb_to_prb) {
    len = srsran_print_check(str, str_len, len, "vrb_to_prb_map=%d ", dci->vrb_to_prb_mapping);
  }

  // PRB bundling size indicator – 0 or 1 bits
  // ... not implemented

  // Rate matching indicator – 0, 1, or 2 bits
  if (cfg->pdsch_rm_pattern1) {
    len = srsran_print_check(str, str_len, len, "rm_pattern1=%d ", dci->rm_pattern1);
  }

  if (cfg->pdsch_rm_pattern2) {
    len = srsran_print_check(str, str_len, len, "rm_pattern2=%d ", dci->rm_pattern2);
  }

  // ZP CSI-RS trigger - 0, 1, or 2 bits
  if (SRSRAN_CEIL_LOG2(cfg->nof_aperiodic_zp + 1) > 0) {
    len = srsran_print_check(str, str_len, len, "zp_csi_rs_id=%d ", dci->zp_csi_rs_id);
  }

  // For transport block 1:
  // Modulation and coding scheme – 5 bits
  len = srsran_print_check(str, str_len, len, "mcs=%d ", dci->mcs);

  // New data indicator – 1 bit
  len = srsran_print_check(str, str_len, len, "ndi=%d ", dci->ndi);

  // Redundancy version – 2 bits
  len = srsran_print_check(str, str_len, len, "rv=%d ", dci->rv);

  // For transport block 2:
  if (cfg->pdsch_2cw) {
    // Modulation and coding scheme – 5 bits
    len = srsran_print_check(str, str_len, len, "mcs2=%d ", dci->mcs2);

    // New data indicator – 1 bit
    len = srsran_print_check(str, str_len, len, "ndi2=%d ", dci->ndi2);

    // Redundancy version – 2 bits
    len = srsran_print_check(str, str_len, len, "rv2=%d ", dci->rv2);
  }

  // HARQ process number – 4 bits
  len = srsran_print_check(str, str_len, len, "harq_id=%d ", dci->pid);

  // Downlink assignment index (dynamic HARQ-ACK codebook only)
  if (cfg->harq_ack_codebok == srsran_pdsch_harq_ack_codebook_dynamic) {
    len = srsran_print_check(str, str_len, len, "dai=%d ", dci->dai);
  }

  // TPC command for scheduled PUCCH – 2 bits
  len = srsran_print_check(str, str_len, len, "tpc=%d ", dci->tpc);

  // PDSCH-to-HARQ_feedback timing indicator – 0, 1, 2, or 3 bits
  if (cfg->nof_dl_to_ul_ack > 0) {
    len = srsran_print_check(str, str_len, len, "harq_feedback=%d ", dci->harq_feedback);
  }

  // Antenna port(s) – 4, 5, or 6 bits
  if (dci_nr_dl_ports_size(cfg) > 0) {
    len = srsran_print_check(str, str_len, len, "ports=%d ", dci->ports);
  }

  // Transmission configuration indication – 0 or 3 bits
  if (cfg->pdsch_tci) {
    len = srsran_print_check(str, str_len, len, "tci=%d ", dci->tci);
  }

  // SRS request – 2 or 3 bits
  len = srsran_print_check(str, str_len, len, "srs_request=%d ", dci->srs_request);

  // CBG transmission information (CBGTI) – 0, 2, 4, 6, or 8 bits
  if (cfg->pdsch_nof_cbg > 0) {
    len = srsran_print_check(str, str_len, len, "cbg_info=%d ", dci->cbg_info);
  }

  // CBG flushing out information (CBGFI) – 0 or 1 bit
  if (cfg->pdsch_cbg_flush > 0) {
    len = srsran_print_check(str, str_len, len, "cbg_flush=%d ", dci->cbg_flush);
  }

  // DMRS sequence initialization – 1 bit
  len = srsran_print_check(str, str_len, len, "dmrs_id=%d ", dci->dmrs_id);

  return len;
}

int srsran_dci_nr_set_cfg(srsran_dci_nr_t* q, const srsran_dci_cfg_nr_t* cfg)
{
  if (q == NULL || cfg == NULL) {
    return SRSRAN_ERROR;
  }

  // Reset current setup
  SRSRAN_MEM_ZERO(q, srsran_dci_nr_t, 1);

  // Copy configuration
  q->cfg = *cfg;

  // Step 0
  // - Determine DCI format 0_0 monitored in a common search space according to clause 7.3.1.1.1 where N_UL_BWP_RB is
  // given by the size of the initial UL bandwidth part.
  uint32_t size_dci_0_0_common = dci_nr_format_0_0_sizeof(cfg->bwp_ul_initial_bw, cfg);

  // - Determine DCI format 1_0 monitored in a common search space according to clause 7.3.1.2.1 where N_DL_BWP_RB given
  // by:
  //   - the size of CORESET 0 if CORESET 0 is configured for the cell; and
  //   - the size of initial DL bandwidth part if CORESET 0 is not configured for the cell.
  uint32_t size_dci_1_0_common = dci_nr_format_1_0_sizeof(cfg->bwp_dl_initial_bw, srsran_rnti_type_c);
  if (cfg->coreset0_bw != 0) {
    size_dci_1_0_common = dci_nr_format_1_0_sizeof(cfg->coreset0_bw, srsran_rnti_type_c);
  }

  // - If DCI format 0_0 is monitored in common search space and if the number of information bits in the DCI format 0_0
  // prior to padding is less than the payload size of the DCI format 1_0 monitored in common search space for
  // scheduling the same serving cell, a number of zero padding bits are generated for the DCI format 0_0 until the
  // payload size equals that of the DCI format 1_0.
  if (cfg->monitor_common_0_0 && size_dci_0_0_common < size_dci_1_0_common) {
    q->dci_0_0_common_padd = size_dci_1_0_common - size_dci_0_0_common;
  } else {
    q->dci_0_0_common_padd = 0;
  }

  // - If DCI format 0_0 is monitored in common search space and if the number of information bits in the DCI format 0_0
  // prior to truncation is larger than the payload size of the DCI format 1_0 monitored in common search space for
  // scheduling the same serving cell, the bitwidth of the frequency domain resource assignment field in the DCI format
  // 0_0 is reduced by truncating the first few most significant bits such that the size of DCI format 0_0 equals the
  // size of the DCI format 1_0.
  if (cfg->monitor_common_0_0 && size_dci_0_0_common > size_dci_1_0_common) {
    q->dci_0_0_common_trunc = size_dci_0_0_common - size_dci_1_0_common;
  } else {
    q->dci_0_0_common_trunc = 0;
  }

  q->dci_0_0_and_1_0_common_size = SRSRAN_MAX(size_dci_1_0_common, DCI_NR_MIN_SIZE);

  // Step 1
  // - Determine DCI format 0_0 monitored in a UE-specific search space according to clause 7.3.1.1.1 where N_UL_BWP_RB
  // is the size of the active UL bandwidth part.
  uint32_t size_dci_0_0_ue = dci_nr_format_0_0_sizeof(cfg->bwp_ul_active_bw, cfg);

  // - Determine DCI format 1_0 monitored in a UE-specific search space according to clause 7.3.1.2.1 where N_DL_BWP_RB
  // is the size of the active DL bandwidth part.
  uint32_t size_dci_1_0_ue = dci_nr_format_1_0_sizeof(cfg->bwp_dl_active_bw, srsran_rnti_type_c);

  // - For a UE configured with supplementaryUplink in ServingCellConfig in a cell, if PUSCH is configured to be
  // transmitted on both the SUL and the non-SUL of the cell and if the number of information bits in DCI format 0_0 in
  // UE-specific search space for the SUL is not equal to the number of information bits in DCI format 0_0 in
  // UE-specific search space for the non-SUL, a number of zero padding bits are generated for the smaller DCI format
  // 0_0 until the payload size equals that of the larger DCI format 0_0.
  // ... Not implemented

  // - If DCI format 0_0 is monitored in UE-specific search space and if the number of information bits in the DCI
  // format 0_0 prior to padding is less than the payload size of the DCI format 1_0 monitored in UE-specific search
  // space for scheduling the same serving cell, a number of zero padding bits are generated for the DCI format 0_0
  // until the payload size equals that of the DCI format 1_0.
  if (cfg->monitor_0_0_and_1_0 && size_dci_0_0_ue < size_dci_1_0_ue) {
    q->dci_0_0_ue_padd = size_dci_1_0_ue - size_dci_0_0_ue;
  }

  // - If DCI format 1_0 is monitored in UE-specific search space and if the number of information bits in the DCI
  // format 1_0 prior to padding is less than the payload size of the DCI format 0_0 monitored in UE-specific search
  // space for scheduling the same serving cell, zeros shall be appended to the DCI format 1_0 until the payload size
  // equals that of the DCI format 0_0
  if (cfg->monitor_0_0_and_1_0 && size_dci_1_0_ue < size_dci_0_0_ue) {
    q->dci_1_0_ue_padd = size_dci_0_0_ue - size_dci_1_0_ue;
  }

  q->dci_0_0_and_1_0_ue_size = SRSRAN_MAX(SRSRAN_MAX(size_dci_0_0_ue, size_dci_1_0_ue), DCI_NR_MIN_SIZE);

  // Step 2
  // For a UE configured with supplementaryUplink in ServingCellConfig in a cell, if PUSCH is configured to be
  // transmitted on both the SUL and the non-SUL of the cell and if the number of information bits in format 0_1 for
  // the SUL is not equal to the number of information bits in format 0_1 for the non-SUL, zeros shall be appended
  // to smaller format 0_1 until the payload size equals that of the larger format 0_1.
  // ... Not implemented

  uint32_t size_dci_0_1 = dci_nr_format_0_1_sizeof(cfg, srsran_rnti_type_c);
  uint32_t size_dci_1_1 = dci_nr_format_1_1_sizeof(cfg, srsran_rnti_type_c);
  if (size_dci_0_1 == 0 || size_dci_1_1 == 0) {
    return SRSRAN_ERROR;
  }

  // If the size of DCI format 0_1 monitored in a UE-specific search space equals that of a DCI format 0_0/1_0
  // monitored in another UE-specific search space, one bit of zero padding shall be appended to DCI format 0_1.
  if (size_dci_0_1 == q->dci_0_0_and_1_0_ue_size) {
    q->dci_0_1_padd++;
  }

  // If the size of DCI format 1_1 monitored in a UE-specific search space equals that of a DCI format 0_0/1_0
  // monitored in another UE-specific search space, one bit of zero padding shall be appended to DCI format 1_1.
  if (size_dci_1_1 == q->dci_0_0_and_1_0_ue_size) {
    q->dci_1_1_padd++;
  }

  q->dci_0_1_size = size_dci_0_1 + q->dci_0_1_padd;
  q->dci_1_1_size = size_dci_1_1 + q->dci_1_1_padd;

  // Step 3
  // If both of the following conditions are fulfilled the size alignment procedure is complete:
  // - the total number of different DCI sizes configured to monitor is no more than 4 for the cell
  // - the total number of different DCI sizes with C-RNTI configured to monitor is no more than 3 for the cell
  // ... Current code is compatible with only possible sizes!

  return SRSRAN_SUCCESS;
}

uint32_t srsran_dci_nr_size(const srsran_dci_nr_t* q, srsran_search_space_type_t ss_type, srsran_dci_format_nr_t format)
{
  // Check input
  if (q == NULL) {
    return 0;
  }

  // For common search space, only formats 0_0 and 1_0
  if (SRSRAN_SEARCH_SPACE_IS_COMMON(ss_type)) {
    return q->dci_0_0_and_1_0_common_size;
  }

  // DCI formats 0_0 and 1_0 in UE-specific
  if (format == srsran_dci_format_nr_0_0 || format == srsran_dci_format_nr_1_0) {
    return q->dci_0_0_and_1_0_ue_size;
  }

  // DCI format 0_1 in UE-specific
  if (format == srsran_dci_format_nr_0_1) {
    return q->dci_0_1_size;
  }

  // DCI format 1_1 in UE-specific
  if (format == srsran_dci_format_nr_1_1) {
    return q->dci_1_1_size;
  }

  // RAR packed MSG3 DCI
  if (format == srsran_dci_format_nr_rar) {
    return dci_nr_rar_sizeof();
  }

  // Not implemented
  return 0;
}

bool srsran_dci_nr_valid_direction(const srsran_dci_msg_nr_t* dci)
{
  // Check pointer
  if (dci == NULL) {
    return false;
  }

  // UL direction
  uint32_t expected_direction = 0;

  // Set DL direction if is DL grant
  if (dci->ctx.format == srsran_dci_format_nr_1_0 || dci->ctx.format == srsran_dci_format_nr_1_1) {
    expected_direction = 1;
  }

  // The format bit is only present for these RNTI
  if (dci->ctx.rnti_type == srsran_rnti_type_c || dci->ctx.rnti_type == srsran_rnti_type_tc) {
    return dci->payload[0] == expected_direction;
  }

  // For other RNTI types, assume always DL on 1_0
  return (dci->ctx.format == srsran_dci_format_nr_1_0);
}

int srsran_dci_nr_dl_pack(const srsran_dci_nr_t* q, const srsran_dci_dl_nr_t* dci, srsran_dci_msg_nr_t* msg)
{
  if (q == NULL || dci == NULL || msg == NULL) {
    return SRSRAN_ERROR;
  }

  // Copy DCI MSG fields
  msg->ctx = dci->ctx;

  // Pack DCI
  switch (msg->ctx.format) {
    case srsran_dci_format_nr_1_0:
      return dci_nr_format_1_0_pack(q, dci, msg);
    case srsran_dci_format_nr_1_1:
      return dci_nr_format_1_1_pack(q, dci, msg);
    default:
      ERROR("Unsupported DCI format %d", msg->ctx.format);
  }

  return SRSRAN_ERROR;
}

int srsran_dci_nr_dl_unpack(const srsran_dci_nr_t* q, srsran_dci_msg_nr_t* msg, srsran_dci_dl_nr_t* dci)
{
  if (q == NULL || dci == NULL || msg == NULL) {
    return SRSRAN_ERROR;
  }

  // Copy DCI MSG fields
  dci->ctx         = msg->ctx;
  dci->coreset0_bw = q->cfg.coreset0_bw;

  // Pack DCI
  switch (msg->ctx.format) {
    case srsran_dci_format_nr_1_0:
      return dci_nr_format_1_0_unpack(q, msg, dci);
    case srsran_dci_format_nr_1_1:
      return dci_nr_format_1_1_unpack(q, msg, dci);
    default:
      ERROR("Unsupported DCI format %d", msg->ctx.format);
  }
  return SRSRAN_ERROR;
}

int srsran_dci_nr_ul_pack(const srsran_dci_nr_t* q, const srsran_dci_ul_nr_t* dci, srsran_dci_msg_nr_t* msg)
{
  if (msg == NULL || dci == NULL) {
    return SRSRAN_ERROR;
  }

  if (dci->ctx.format != srsran_dci_format_nr_rar && q == NULL) {
    return SRSRAN_ERROR;
  }

  // Copy DCI MSG fields
  msg->ctx = dci->ctx;

  // Pack DCI
  switch (msg->ctx.format) {
    case srsran_dci_format_nr_0_0:
      return dci_nr_format_0_0_pack(q, dci, msg);
    case srsran_dci_format_nr_0_1:
      return dci_nr_format_0_1_pack(q, dci, msg);
    case srsran_dci_format_nr_rar:
      return dci_nr_rar_pack(dci, msg);
    default:
      ERROR("Unsupported DCI format %d", msg->ctx.format);
  }

  return SRSRAN_ERROR;
}

int srsran_dci_nr_ul_unpack(const srsran_dci_nr_t* q, srsran_dci_msg_nr_t* msg, srsran_dci_ul_nr_t* dci)
{
  if (msg == NULL || dci == NULL) {
    return SRSRAN_ERROR;
  }

  if (msg->ctx.format != srsran_dci_format_nr_rar && q == NULL) {
    return SRSRAN_ERROR;
  }

  // Copy DCI MSG fields
  dci->ctx = msg->ctx;

  // Pack DCI
  switch (msg->ctx.format) {
    case srsran_dci_format_nr_0_0:
      return dci_nr_format_0_0_unpack(q, msg, dci);
    case srsran_dci_format_nr_0_1:
      return dci_nr_format_0_1_unpack(q, msg, dci);
    case srsran_dci_format_nr_rar:
      return dci_nr_rar_unpack(msg, dci);
    default:
      ERROR("Unsupported DCI format %d", msg->ctx.format);
  }
  return SRSRAN_ERROR;
}

uint32_t srsran_dci_ctx_to_str(const srsran_dci_ctx_t* ctx, char* str, uint32_t str_len)
{
  if (ctx == NULL || str == NULL) {
    return 0;
  }

  uint32_t len = 0;

  // Print base
  len = srsran_print_check(str,
                           str_len,
                           len,
                           "%s-rnti=0x%04x dci=%s ss=%s ",
                           srsran_rnti_type_str_short(ctx->rnti_type),
                           ctx->rnti,
                           srsran_dci_format_nr_string(ctx->format),
                           srsran_ss_type_str(ctx->ss_type));

  if (ctx->format != srsran_dci_format_nr_rar) {
    len = srsran_print_check(str, str_len, len, "L=%d cce=%d ", ctx->location.L, ctx->location.ncce);
  }

  return len;
}

uint32_t srsran_dci_ul_nr_to_str(const srsran_dci_nr_t* q, const srsran_dci_ul_nr_t* dci, char* str, uint32_t str_len)
{
  if (q == NULL || dci == NULL || str == NULL) {
    return 0;
  }

  uint32_t len = 0;

  len += srsran_dci_ctx_to_str(&dci->ctx, &str[len], str_len - len);

  // Pack DCI
  switch (dci->ctx.format) {
    case srsran_dci_format_nr_0_0:
      len += dci_nr_format_0_0_to_str(dci, &str[len], str_len - len);
      break;
    case srsran_dci_format_nr_0_1:
      len += dci_nr_format_0_1_to_str(q, dci, &str[len], str_len - len);
      break;
    case srsran_dci_format_nr_rar:
      len += dci_nr_rar_to_str(dci, &str[len], str_len - len);
      break;
    default:
      len = srsran_print_check(str, str_len, len, "<invalid ul dci> ");
      break;
  }

  return len;
}

uint32_t srsran_dci_dl_nr_to_str(const srsran_dci_nr_t* q, const srsran_dci_dl_nr_t* dci, char* str, uint32_t str_len)
{
  if (q == NULL || dci == NULL || str == NULL) {
    return SRSRAN_ERROR;
  }

  uint32_t len = 0;

  len += srsran_dci_ctx_to_str(&dci->ctx, &str[len], str_len - len);

  // Pack DCI
  switch (dci->ctx.format) {
    case srsran_dci_format_nr_1_0:
      len += dci_nr_format_1_0_to_str(dci, &str[len], str_len - len);
      break;
    case srsran_dci_format_nr_1_1:
      len += dci_nr_format_1_1_to_str(q, dci, &str[len], str_len - len);
      break;
    default:
      len = srsran_print_check(str, str_len, len, "<invalid dl dci> ");
      break;
  }

  return len;
}
