/*
 * Copyright (c) 2011 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Marco Miozzo <marco.miozzo@cttc.es>
 *         Nicola Baldo  <nbaldo@cttc.es>
 * Modified by:
 *          Danilo Abrignani <danilo.abrignani@unibo.it> (Carrier Aggregation - GSoC 2015)
 *          Biljana Bojovic <biljana.bojovic@cttc.es> (Carrier Aggregation)
 */

#include "lte-enb-mac.h"

#include "lte-common.h"
#include "lte-control-messages.h"
#include "lte-enb-cmac-sap.h"
#include "lte-mac-sap.h"
#include "lte-radio-bearer-tag.h"

#include "ns3/log.h"
#include "ns3/packet.h"
#include "ns3/pointer.h"
#include "ns3/simulator.h"

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("LteEnbMac");

NS_OBJECT_ENSURE_REGISTERED(LteEnbMac);

// //////////////////////////////////////
// member SAP forwarders
// //////////////////////////////////////

/// EnbMacMemberLteEnbCmacSapProvider class
class EnbMacMemberLteEnbCmacSapProvider : public LteEnbCmacSapProvider
{
  public:
    /**
     * Constructor
     *
     * @param mac the MAC
     */
    EnbMacMemberLteEnbCmacSapProvider(LteEnbMac* mac);

    // inherited from LteEnbCmacSapProvider
    void ConfigureMac(uint16_t ulBandwidth, uint16_t dlBandwidth) override;
    void AddUe(uint16_t rnti) override;
    void RemoveUe(uint16_t rnti) override;
    void AddLc(LcInfo lcinfo, LteMacSapUser* msu) override;
    void ReconfigureLc(LcInfo lcinfo) override;
    void ReleaseLc(uint16_t rnti, uint8_t lcid) override;
    void UeUpdateConfigurationReq(UeConfig params) override;
    RachConfig GetRachConfig() override;
    AllocateNcRaPreambleReturnValue AllocateNcRaPreamble(uint16_t rnti) override;

  private:
    LteEnbMac* m_mac; ///< the MAC
};

EnbMacMemberLteEnbCmacSapProvider::EnbMacMemberLteEnbCmacSapProvider(LteEnbMac* mac)
    : m_mac(mac)
{
}

void
EnbMacMemberLteEnbCmacSapProvider::ConfigureMac(uint16_t ulBandwidth, uint16_t dlBandwidth)
{
    m_mac->DoConfigureMac(ulBandwidth, dlBandwidth);
}

void
EnbMacMemberLteEnbCmacSapProvider::AddUe(uint16_t rnti)
{
    m_mac->DoAddUe(rnti);
}

void
EnbMacMemberLteEnbCmacSapProvider::RemoveUe(uint16_t rnti)
{
    m_mac->DoRemoveUe(rnti);
}

void
EnbMacMemberLteEnbCmacSapProvider::AddLc(LcInfo lcinfo, LteMacSapUser* msu)
{
    m_mac->DoAddLc(lcinfo, msu);
}

void
EnbMacMemberLteEnbCmacSapProvider::ReconfigureLc(LcInfo lcinfo)
{
    m_mac->DoReconfigureLc(lcinfo);
}

void
EnbMacMemberLteEnbCmacSapProvider::ReleaseLc(uint16_t rnti, uint8_t lcid)
{
    m_mac->DoReleaseLc(rnti, lcid);
}

void
EnbMacMemberLteEnbCmacSapProvider::UeUpdateConfigurationReq(UeConfig params)
{
    m_mac->DoUeUpdateConfigurationReq(params);
}

LteEnbCmacSapProvider::RachConfig
EnbMacMemberLteEnbCmacSapProvider::GetRachConfig()
{
    return m_mac->DoGetRachConfig();
}

LteEnbCmacSapProvider::AllocateNcRaPreambleReturnValue
EnbMacMemberLteEnbCmacSapProvider::AllocateNcRaPreamble(uint16_t rnti)
{
    return m_mac->DoAllocateNcRaPreamble(rnti);
}

/// EnbMacMemberFfMacSchedSapUser class
class EnbMacMemberFfMacSchedSapUser : public FfMacSchedSapUser
{
  public:
    /**
     * Constructor
     *
     * @param mac the MAC
     */
    EnbMacMemberFfMacSchedSapUser(LteEnbMac* mac);

    void SchedDlConfigInd(const SchedDlConfigIndParameters& params) override;
    void SchedUlConfigInd(const SchedUlConfigIndParameters& params) override;

  private:
    LteEnbMac* m_mac; ///< the MAC
};

EnbMacMemberFfMacSchedSapUser::EnbMacMemberFfMacSchedSapUser(LteEnbMac* mac)
    : m_mac(mac)
{
}

void
EnbMacMemberFfMacSchedSapUser::SchedDlConfigInd(const SchedDlConfigIndParameters& params)
{
    m_mac->DoSchedDlConfigInd(params);
}

void
EnbMacMemberFfMacSchedSapUser::SchedUlConfigInd(const SchedUlConfigIndParameters& params)
{
    m_mac->DoSchedUlConfigInd(params);
}

/// EnbMacMemberFfMacCschedSapUser class
class EnbMacMemberFfMacCschedSapUser : public FfMacCschedSapUser
{
  public:
    /**
     * Constructor
     *
     * @param mac the MAC
     */
    EnbMacMemberFfMacCschedSapUser(LteEnbMac* mac);

    void CschedCellConfigCnf(const CschedCellConfigCnfParameters& params) override;
    void CschedUeConfigCnf(const CschedUeConfigCnfParameters& params) override;
    void CschedLcConfigCnf(const CschedLcConfigCnfParameters& params) override;
    void CschedLcReleaseCnf(const CschedLcReleaseCnfParameters& params) override;
    void CschedUeReleaseCnf(const CschedUeReleaseCnfParameters& params) override;
    void CschedUeConfigUpdateInd(const CschedUeConfigUpdateIndParameters& params) override;
    void CschedCellConfigUpdateInd(const CschedCellConfigUpdateIndParameters& params) override;

  private:
    LteEnbMac* m_mac; ///< the MAC
};

EnbMacMemberFfMacCschedSapUser::EnbMacMemberFfMacCschedSapUser(LteEnbMac* mac)
    : m_mac(mac)
{
}

void
EnbMacMemberFfMacCschedSapUser::CschedCellConfigCnf(const CschedCellConfigCnfParameters& params)
{
    m_mac->DoCschedCellConfigCnf(params);
}

void
EnbMacMemberFfMacCschedSapUser::CschedUeConfigCnf(const CschedUeConfigCnfParameters& params)
{
    m_mac->DoCschedUeConfigCnf(params);
}

void
EnbMacMemberFfMacCschedSapUser::CschedLcConfigCnf(const CschedLcConfigCnfParameters& params)
{
    m_mac->DoCschedLcConfigCnf(params);
}

void
EnbMacMemberFfMacCschedSapUser::CschedLcReleaseCnf(const CschedLcReleaseCnfParameters& params)
{
    m_mac->DoCschedLcReleaseCnf(params);
}

void
EnbMacMemberFfMacCschedSapUser::CschedUeReleaseCnf(const CschedUeReleaseCnfParameters& params)
{
    m_mac->DoCschedUeReleaseCnf(params);
}

void
EnbMacMemberFfMacCschedSapUser::CschedUeConfigUpdateInd(
    const CschedUeConfigUpdateIndParameters& params)
{
    m_mac->DoCschedUeConfigUpdateInd(params);
}

void
EnbMacMemberFfMacCschedSapUser::CschedCellConfigUpdateInd(
    const CschedCellConfigUpdateIndParameters& params)
{
    m_mac->DoCschedCellConfigUpdateInd(params);
}

/// ---------- PHY-SAP
class EnbMacMemberLteEnbPhySapUser : public LteEnbPhySapUser
{
  public:
    /**
     * Constructor
     *
     * @param mac the MAC
     */
    EnbMacMemberLteEnbPhySapUser(LteEnbMac* mac);

    // inherited from LteEnbPhySapUser
    void ReceivePhyPdu(Ptr<Packet> p) override;
    void SubframeIndication(uint32_t frameNo, uint32_t subframeNo) override;
    void ReceiveLteControlMessage(Ptr<LteControlMessage> msg) override;
    void ReceiveRachPreamble(uint32_t prachId) override;
    void UlCqiReport(FfMacSchedSapProvider::SchedUlCqiInfoReqParameters ulcqi) override;
    void UlInfoListElementHarqFeedback(UlInfoListElement_s params) override;
    void DlInfoListElementHarqFeedback(DlInfoListElement_s params) override;

  private:
    LteEnbMac* m_mac; ///< the MAC
};

EnbMacMemberLteEnbPhySapUser::EnbMacMemberLteEnbPhySapUser(LteEnbMac* mac)
    : m_mac(mac)
{
}

void
EnbMacMemberLteEnbPhySapUser::ReceivePhyPdu(Ptr<Packet> p)
{
    m_mac->DoReceivePhyPdu(p);
}

void
EnbMacMemberLteEnbPhySapUser::SubframeIndication(uint32_t frameNo, uint32_t subframeNo)
{
    m_mac->DoSubframeIndication(frameNo, subframeNo);
}

void
EnbMacMemberLteEnbPhySapUser::ReceiveLteControlMessage(Ptr<LteControlMessage> msg)
{
    m_mac->DoReceiveLteControlMessage(msg);
}

void
EnbMacMemberLteEnbPhySapUser::ReceiveRachPreamble(uint32_t prachId)
{
    m_mac->DoReceiveRachPreamble(prachId);
}

void
EnbMacMemberLteEnbPhySapUser::UlCqiReport(FfMacSchedSapProvider::SchedUlCqiInfoReqParameters ulcqi)
{
    m_mac->DoUlCqiReport(ulcqi);
}

void
EnbMacMemberLteEnbPhySapUser::UlInfoListElementHarqFeedback(UlInfoListElement_s params)
{
    m_mac->DoUlInfoListElementHarqFeedback(params);
}

void
EnbMacMemberLteEnbPhySapUser::DlInfoListElementHarqFeedback(DlInfoListElement_s params)
{
    m_mac->DoDlInfoListElementHarqFeedback(params);
}

// //////////////////////////////////////
// generic LteEnbMac methods
// //////////////////////////////////////

TypeId
LteEnbMac::GetTypeId()
{
    static TypeId tid =
        TypeId("ns3::LteEnbMac")
            .SetParent<Object>()
            .SetGroupName("Lte")
            .AddConstructor<LteEnbMac>()
            .AddAttribute("NumberOfRaPreambles",
                          "how many random access preambles are available for the contention based "
                          "RACH process",
                          UintegerValue(52),
                          MakeUintegerAccessor(&LteEnbMac::m_numberOfRaPreambles),
                          MakeUintegerChecker<uint8_t>(4, 64))
            .AddAttribute("PreambleTransMax",
                          "Maximum number of random access preamble transmissions",
                          UintegerValue(50),
                          MakeUintegerAccessor(&LteEnbMac::m_preambleTransMax),
                          MakeUintegerChecker<uint8_t>(3, 200))
            .AddAttribute("RaResponseWindowSize",
                          "length of the window (in TTIs) for the reception of the random access "
                          "response (RAR); the resulting RAR timeout is this value + 3 ms",
                          UintegerValue(3),
                          MakeUintegerAccessor(&LteEnbMac::m_raResponseWindowSize),
                          MakeUintegerChecker<uint8_t>(2, 10))
            .AddAttribute("ConnEstFailCount",
                          "how many time T300 timer can expire on the same cell",
                          UintegerValue(1),
                          MakeUintegerAccessor(&LteEnbMac::m_connEstFailCount),
                          MakeUintegerChecker<uint8_t>(1, 4))
            .AddTraceSource("DlScheduling",
                            "Information regarding DL scheduling.",
                            MakeTraceSourceAccessor(&LteEnbMac::m_dlScheduling),
                            "ns3::LteEnbMac::DlSchedulingTracedCallback")
            .AddTraceSource("UlScheduling",
                            "Information regarding UL scheduling.",
                            MakeTraceSourceAccessor(&LteEnbMac::m_ulScheduling),
                            "ns3::LteEnbMac::UlSchedulingTracedCallback")
            .AddAttribute("ComponentCarrierId",
                          "ComponentCarrier Id, needed to reply on the appropriate sap.",
                          UintegerValue(0),
                          MakeUintegerAccessor(&LteEnbMac::m_componentCarrierId),
                          MakeUintegerChecker<uint8_t>(0, 4));

    return tid;
}

LteEnbMac::LteEnbMac()
    : m_ccmMacSapUser(nullptr)
{
    NS_LOG_FUNCTION(this);
    m_macSapProvider = new EnbMacMemberLteMacSapProvider<LteEnbMac>(this);
    m_cmacSapProvider = new EnbMacMemberLteEnbCmacSapProvider(this);
    m_schedSapUser = new EnbMacMemberFfMacSchedSapUser(this);
    m_cschedSapUser = new EnbMacMemberFfMacCschedSapUser(this);
    m_enbPhySapUser = new EnbMacMemberLteEnbPhySapUser(this);
    m_ccmMacSapProvider = new MemberLteCcmMacSapProvider<LteEnbMac>(this);
}

LteEnbMac::~LteEnbMac()
{
    NS_LOG_FUNCTION(this);
}

void
LteEnbMac::DoDispose()
{
    NS_LOG_FUNCTION(this);
    m_dlCqiReceived.clear();
    m_ulCqiReceived.clear();
    m_ulCeReceived.clear();
    m_dlInfoListReceived.clear();
    m_ulInfoListReceived.clear();
    m_miDlHarqProcessesPackets.clear();
    delete m_macSapProvider;
    delete m_cmacSapProvider;
    delete m_schedSapUser;
    delete m_cschedSapUser;
    delete m_enbPhySapUser;
    delete m_ccmMacSapProvider;
}

void
LteEnbMac::SetComponentCarrierId(uint8_t index)
{
    m_componentCarrierId = index;
}

void
LteEnbMac::SetFfMacSchedSapProvider(FfMacSchedSapProvider* s)
{
    m_schedSapProvider = s;
}

FfMacSchedSapUser*
LteEnbMac::GetFfMacSchedSapUser()
{
    return m_schedSapUser;
}

void
LteEnbMac::SetFfMacCschedSapProvider(FfMacCschedSapProvider* s)
{
    m_cschedSapProvider = s;
}

FfMacCschedSapUser*
LteEnbMac::GetFfMacCschedSapUser()
{
    return m_cschedSapUser;
}

void
LteEnbMac::SetLteMacSapUser(LteMacSapUser* s)
{
    m_macSapUser = s;
}

LteMacSapProvider*
LteEnbMac::GetLteMacSapProvider()
{
    return m_macSapProvider;
}

void
LteEnbMac::SetLteEnbCmacSapUser(LteEnbCmacSapUser* s)
{
    m_cmacSapUser = s;
}

LteEnbCmacSapProvider*
LteEnbMac::GetLteEnbCmacSapProvider()
{
    return m_cmacSapProvider;
}

void
LteEnbMac::SetLteEnbPhySapProvider(LteEnbPhySapProvider* s)
{
    m_enbPhySapProvider = s;
}

LteEnbPhySapUser*
LteEnbMac::GetLteEnbPhySapUser()
{
    return m_enbPhySapUser;
}

void
LteEnbMac::SetLteCcmMacSapUser(LteCcmMacSapUser* s)
{
    m_ccmMacSapUser = s;
}

LteCcmMacSapProvider*
LteEnbMac::GetLteCcmMacSapProvider()
{
    return m_ccmMacSapProvider;
}

void
LteEnbMac::DoSubframeIndication(uint32_t frameNo, uint32_t subframeNo)
{
    NS_LOG_FUNCTION(this << " EnbMac - frame " << frameNo << " subframe " << subframeNo);

    // Store current frame / subframe number
    m_frameNo = frameNo;
    m_subframeNo = subframeNo;

    // --- DOWNLINK ---
    // Send Dl-CQI info to the scheduler
    if (!m_dlCqiReceived.empty())
    {
        FfMacSchedSapProvider::SchedDlCqiInfoReqParameters dlcqiInfoReq;
        dlcqiInfoReq.m_sfnSf = ((0x3FF & frameNo) << 4) | (0xF & subframeNo);
        dlcqiInfoReq.m_cqiList.insert(dlcqiInfoReq.m_cqiList.begin(),
                                      m_dlCqiReceived.begin(),
                                      m_dlCqiReceived.end());
        m_dlCqiReceived.erase(m_dlCqiReceived.begin(), m_dlCqiReceived.end());
        m_schedSapProvider->SchedDlCqiInfoReq(dlcqiInfoReq);
    }

    if (!m_receivedRachPreambleCount.empty())
    {
        // process received RACH preambles and notify the scheduler
        FfMacSchedSapProvider::SchedDlRachInfoReqParameters rachInfoReqParams;
        NS_ASSERT(subframeNo > 0 && subframeNo <= 10); // subframe in 1..10
        for (auto it = m_receivedRachPreambleCount.begin(); it != m_receivedRachPreambleCount.end();
             ++it)
        {
            NS_LOG_INFO(this << " preambleId " << (uint32_t)it->first << ": " << it->second
                             << " received");
            NS_ASSERT(it->second != 0);
            if (it->second > 1)
            {
                NS_LOG_INFO("preambleId " << (uint32_t)it->first << ": collision");
                // in case of collision we assume that no preamble is
                // successfully received, hence no RAR is sent
            }
            else
            {
                uint16_t rnti;
                auto jt = m_allocatedNcRaPreambleMap.find(it->first);
                if (jt != m_allocatedNcRaPreambleMap.end())
                {
                    rnti = jt->second.rnti;
                    NS_LOG_INFO("preambleId previously allocated for NC based RA, RNTI ="
                                << (uint32_t)rnti << ", sending RAR");
                }
                else
                {
                    rnti = m_cmacSapUser->AllocateTemporaryCellRnti();

                    if (rnti == 0)
                    {
                        // If rnti = 0, UE context was not created (not enough SRS)
                        // Therefore don't send RAR for this preamble
                        NS_LOG_INFO("UE context not created, no RAR to send");
                        continue;
                    }
                    NS_LOG_INFO("preambleId " << (uint32_t)it->first << ": allocated T-C-RNTI "
                                              << (uint32_t)rnti << ", sending RAR");
                }

                RachListElement_s rachLe;
                rachLe.m_rnti = rnti;
                rachLe.m_estimatedSize = 144; // to be confirmed
                rachInfoReqParams.m_rachList.push_back(rachLe);
                m_rapIdRntiMap.insert(std::pair<uint16_t, uint32_t>(rnti, it->first));
            }
        }
        m_schedSapProvider->SchedDlRachInfoReq(rachInfoReqParams);
        m_receivedRachPreambleCount.clear();
    }
    // Get downlink transmission opportunities
    uint32_t dlSchedFrameNo = m_frameNo;
    uint32_t dlSchedSubframeNo = m_subframeNo;
    //   NS_LOG_DEBUG (this << " sfn " << frameNo << " sbfn " << subframeNo);
    if (dlSchedSubframeNo + m_macChTtiDelay > 10)
    {
        dlSchedFrameNo++;
        dlSchedSubframeNo = (dlSchedSubframeNo + m_macChTtiDelay) % 10;
    }
    else
    {
        dlSchedSubframeNo = dlSchedSubframeNo + m_macChTtiDelay;
    }
    FfMacSchedSapProvider::SchedDlTriggerReqParameters dlparams;
    dlparams.m_sfnSf = ((0x3FF & dlSchedFrameNo) << 4) | (0xF & dlSchedSubframeNo);

    // Forward DL HARQ Feedbacks collected during last TTI
    if (!m_dlInfoListReceived.empty())
    {
        dlparams.m_dlInfoList = m_dlInfoListReceived;
        // empty local buffer
        m_dlInfoListReceived.clear();
    }

    m_schedSapProvider->SchedDlTriggerReq(dlparams);

    // --- UPLINK ---
    // Send UL-CQI info to the scheduler
    for (std::size_t i = 0; i < m_ulCqiReceived.size(); i++)
    {
        if (subframeNo > 1)
        {
            m_ulCqiReceived.at(i).m_sfnSf = ((0x3FF & frameNo) << 4) | (0xF & (subframeNo - 1));
        }
        else
        {
            m_ulCqiReceived.at(i).m_sfnSf = ((0x3FF & (frameNo - 1)) << 4) | (0xF & 10);
        }
        m_schedSapProvider->SchedUlCqiInfoReq(m_ulCqiReceived.at(i));
    }
    m_ulCqiReceived.clear();

    // Send BSR reports to the scheduler
    if (!m_ulCeReceived.empty())
    {
        FfMacSchedSapProvider::SchedUlMacCtrlInfoReqParameters ulMacReq;
        ulMacReq.m_sfnSf = ((0x3FF & frameNo) << 4) | (0xF & subframeNo);
        ulMacReq.m_macCeList.insert(ulMacReq.m_macCeList.begin(),
                                    m_ulCeReceived.begin(),
                                    m_ulCeReceived.end());
        m_ulCeReceived.erase(m_ulCeReceived.begin(), m_ulCeReceived.end());
        m_schedSapProvider->SchedUlMacCtrlInfoReq(ulMacReq);
    }

    // Get uplink transmission opportunities
    uint32_t ulSchedFrameNo = m_frameNo;
    uint32_t ulSchedSubframeNo = m_subframeNo;
    //   NS_LOG_DEBUG (this << " sfn " << frameNo << " sbfn " << subframeNo);
    if (ulSchedSubframeNo + (m_macChTtiDelay + UL_PUSCH_TTIS_DELAY) > 10)
    {
        ulSchedFrameNo++;
        ulSchedSubframeNo = (ulSchedSubframeNo + (m_macChTtiDelay + UL_PUSCH_TTIS_DELAY)) % 10;
    }
    else
    {
        ulSchedSubframeNo = ulSchedSubframeNo + (m_macChTtiDelay + UL_PUSCH_TTIS_DELAY);
    }
    FfMacSchedSapProvider::SchedUlTriggerReqParameters ulparams;
    ulparams.m_sfnSf = ((0x3FF & ulSchedFrameNo) << 4) | (0xF & ulSchedSubframeNo);

    // Forward DL HARQ Feedbacks collected during last TTI
    if (!m_ulInfoListReceived.empty())
    {
        ulparams.m_ulInfoList = m_ulInfoListReceived;
        // empty local buffer
        m_ulInfoListReceived.clear();
    }

    m_schedSapProvider->SchedUlTriggerReq(ulparams);
}

void
LteEnbMac::DoReceiveLteControlMessage(Ptr<LteControlMessage> msg)
{
    NS_LOG_FUNCTION(this << msg);
    if (msg->GetMessageType() == LteControlMessage::DL_CQI)
    {
        Ptr<DlCqiLteControlMessage> dlcqi = DynamicCast<DlCqiLteControlMessage>(msg);
        ReceiveDlCqiLteControlMessage(dlcqi);
    }
    else if (msg->GetMessageType() == LteControlMessage::BSR)
    {
        Ptr<BsrLteControlMessage> bsr = DynamicCast<BsrLteControlMessage>(msg);
        ReceiveBsrMessage(bsr->GetBsr());
    }
    else if (msg->GetMessageType() == LteControlMessage::DL_HARQ)
    {
        Ptr<DlHarqFeedbackLteControlMessage> dlharq =
            DynamicCast<DlHarqFeedbackLteControlMessage>(msg);
        DoDlInfoListElementHarqFeedback(dlharq->GetDlHarqFeedback());
    }
    else
    {
        NS_LOG_LOGIC(this << " LteControlMessage type " << msg->GetMessageType()
                          << " not recognized");
    }
}

void
LteEnbMac::DoReceiveRachPreamble(uint8_t rapId)
{
    NS_LOG_FUNCTION(this << (uint32_t)rapId);
    // just record that the preamble has been received; it will be processed later
    ++m_receivedRachPreambleCount[rapId]; // will create entry if not exists
}

void
LteEnbMac::DoUlCqiReport(FfMacSchedSapProvider::SchedUlCqiInfoReqParameters ulcqi)
{
    if (ulcqi.m_ulCqi.m_type == UlCqi_s::PUSCH)
    {
        NS_LOG_DEBUG(this << " eNB rxed an PUSCH UL-CQI");
    }
    else if (ulcqi.m_ulCqi.m_type == UlCqi_s::SRS)
    {
        NS_LOG_DEBUG(this << " eNB rxed an SRS UL-CQI");
    }
    m_ulCqiReceived.push_back(ulcqi);
}

void
LteEnbMac::ReceiveDlCqiLteControlMessage(Ptr<DlCqiLteControlMessage> msg)
{
    NS_LOG_FUNCTION(this << msg);

    CqiListElement_s dlcqi = msg->GetDlCqi();
    NS_LOG_LOGIC(this << "Enb Received DL-CQI rnti" << dlcqi.m_rnti);
    NS_ASSERT(dlcqi.m_rnti != 0);
    m_dlCqiReceived.push_back(dlcqi);
}

void
LteEnbMac::ReceiveBsrMessage(MacCeListElement_s bsr)
{
    NS_LOG_FUNCTION(this);
    m_ccmMacSapUser->UlReceiveMacCe(bsr, m_componentCarrierId);
}

void
LteEnbMac::DoReportMacCeToScheduler(MacCeListElement_s bsr)
{
    NS_LOG_FUNCTION(this);
    NS_LOG_DEBUG(this << " bsr Size " << (uint16_t)m_ulCeReceived.size());
    // send to LteCcmMacSapUser
    m_ulCeReceived.push_back(
        bsr); // this to called when LteUlCcmSapProvider::ReportMacCeToScheduler is called
    NS_LOG_DEBUG(this << " bsr Size after push_back " << (uint16_t)m_ulCeReceived.size());
}

void
LteEnbMac::DoReceivePhyPdu(Ptr<Packet> p)
{
    NS_LOG_FUNCTION(this);
    LteRadioBearerTag tag;
    p->RemovePacketTag(tag);

    // store info of the packet received

    //   u_int rnti = tag.GetRnti ();
    //   u_int lcid = tag.GetLcid ();
    //   auto it = m_ulInfoListElements.find (tag.GetRnti ());
    //   if (it == m_ulInfoListElements.end ())
    //     {
    //       // new RNTI
    //       UlInfoListElement_s ulinfonew;
    //       ulinfonew.m_rnti = tag.GetRnti ();
    //       // always allocate full size of ulReception vector, initializing all elements to 0
    //       ulinfonew.m_ulReception.assign (MAX_LC_LIST+1, 0);
    //       // set the element for the current LCID
    //       ulinfonew.m_ulReception.at (tag.GetLcid ()) = p->GetSize ();
    //       ulinfonew.m_receptionStatus = UlInfoListElement_s::Ok;
    //       ulinfonew.m_tpc = 0; // Tx power control not implemented at this stage
    //       m_ulInfoListElements.insert (std::pair<uint16_t, UlInfoListElement_s > (tag.GetRnti (),
    //       ulinfonew));
    //
    //     }
    //   else
    //     {
    //       // existing RNTI: we just set the value for the current
    //       // LCID. Note that the corresponding element had already been
    //       // allocated previously.
    //       NS_ASSERT_MSG ((*it).second.m_ulReception.at (tag.GetLcid ()) == 0, "would overwrite
    //       previously written ulReception element");
    //       (*it).second.m_ulReception.at (tag.GetLcid ()) = p->GetSize ();
    //       (*it).second.m_receptionStatus = UlInfoListElement_s::Ok;
    //     }

    // forward the packet to the correspondent RLC
    uint16_t rnti = tag.GetRnti();
    uint8_t lcid = tag.GetLcid();
    auto rntiIt = m_rlcAttached.find(rnti);
    NS_ASSERT_MSG(rntiIt != m_rlcAttached.end(), "could not find RNTI" << rnti);
    auto lcidIt = rntiIt->second.find(lcid);
    // NS_ASSERT_MSG (lcidIt != rntiIt->second.end (), "could not find LCID" << lcid);

    LteMacSapUser::ReceivePduParameters rxPduParams;
    rxPduParams.p = p;
    rxPduParams.rnti = rnti;
    rxPduParams.lcid = lcid;

    // Receive PDU only if LCID is found
    if (lcidIt != rntiIt->second.end())
    {
        (*lcidIt).second->ReceivePdu(rxPduParams);
    }
}

// ////////////////////////////////////////////
// CMAC SAP
// ////////////////////////////////////////////

void
LteEnbMac::DoConfigureMac(uint16_t ulBandwidth, uint16_t dlBandwidth)
{
    NS_LOG_FUNCTION(this << " ulBandwidth=" << ulBandwidth << " dlBandwidth=" << dlBandwidth);
    FfMacCschedSapProvider::CschedCellConfigReqParameters params{};
    // Configure the subset of parameters used by FfMacScheduler
    params.m_ulBandwidth = ulBandwidth;
    params.m_dlBandwidth = dlBandwidth;
    m_macChTtiDelay = m_enbPhySapProvider->GetMacChTtiDelay();
    // ...more parameters can be configured
    m_cschedSapProvider->CschedCellConfigReq(params);
}

void
LteEnbMac::DoAddUe(uint16_t rnti)
{
    NS_LOG_FUNCTION(this << " rnti=" << rnti);
    std::map<uint8_t, LteMacSapUser*> empty;
    auto ret =
        m_rlcAttached.insert(std::pair<uint16_t, std::map<uint8_t, LteMacSapUser*>>(rnti, empty));
    NS_ASSERT_MSG(ret.second, "element already present, RNTI already existed");

    FfMacCschedSapProvider::CschedUeConfigReqParameters params;
    params.m_rnti = rnti;
    params.m_transmissionMode =
        0; // set to default value (SISO) for avoiding random initialization (valgrind error)

    m_cschedSapProvider->CschedUeConfigReq(params);

    // Create DL transmission HARQ buffers
    std::vector<Ptr<PacketBurst>> dlHarqLayer0pkt;
    dlHarqLayer0pkt.resize(8);
    for (uint8_t i = 0; i < 8; i++)
    {
        Ptr<PacketBurst> pb = CreateObject<PacketBurst>();
        dlHarqLayer0pkt.at(i) = pb;
    }
    std::vector<Ptr<PacketBurst>> dlHarqLayer1pkt;
    dlHarqLayer1pkt.resize(8);
    for (uint8_t i = 0; i < 8; i++)
    {
        Ptr<PacketBurst> pb = CreateObject<PacketBurst>();
        dlHarqLayer1pkt.at(i) = pb;
    }
    DlHarqProcessesBuffer_t buf;
    buf.push_back(dlHarqLayer0pkt);
    buf.push_back(dlHarqLayer1pkt);
    m_miDlHarqProcessesPackets.insert(std::pair<uint16_t, DlHarqProcessesBuffer_t>(rnti, buf));
}

void
LteEnbMac::DoRemoveUe(uint16_t rnti)
{
    NS_LOG_FUNCTION(this << " rnti=" << rnti);
    FfMacCschedSapProvider::CschedUeReleaseReqParameters params;
    params.m_rnti = rnti;
    m_cschedSapProvider->CschedUeReleaseReq(params);
    m_rlcAttached.erase(rnti);
    m_miDlHarqProcessesPackets.erase(rnti);

    NS_LOG_DEBUG("start checking for unprocessed preamble for rnti: " << rnti);
    // remove unprocessed preamble received for RACH during handover
    auto jt = m_allocatedNcRaPreambleMap.begin();
    while (jt != m_allocatedNcRaPreambleMap.end())
    {
        if (jt->second.rnti == rnti)
        {
            auto it = m_receivedRachPreambleCount.find(jt->first);
            if (it != m_receivedRachPreambleCount.end())
            {
                m_receivedRachPreambleCount.erase(it->first);
            }
            jt = m_allocatedNcRaPreambleMap.erase(jt);
        }
        else
        {
            ++jt;
        }
    }

    auto itCeRxd = m_ulCeReceived.begin();
    while (itCeRxd != m_ulCeReceived.end())
    {
        if (itCeRxd->m_rnti == rnti)
        {
            itCeRxd = m_ulCeReceived.erase(itCeRxd);
        }
        else
        {
            itCeRxd++;
        }
    }
}

void
LteEnbMac::DoAddLc(LteEnbCmacSapProvider::LcInfo lcinfo, LteMacSapUser* msu)
{
    NS_LOG_FUNCTION(this << lcinfo.rnti << (uint16_t)lcinfo.lcId);

    LteFlowId_t flow(lcinfo.rnti, lcinfo.lcId);

    auto rntiIt = m_rlcAttached.find(lcinfo.rnti);
    NS_ASSERT_MSG(rntiIt != m_rlcAttached.end(), "RNTI not found");
    auto lcidIt = rntiIt->second.find(lcinfo.lcId);
    if (lcidIt == rntiIt->second.end())
    {
        rntiIt->second.insert(std::pair<uint8_t, LteMacSapUser*>(lcinfo.lcId, msu));
    }
    else
    {
        NS_LOG_ERROR("LC already exists");
    }

    // CCCH (LCID 0) is pre-configured
    // see FF LTE MAC Scheduler
    // Interface Specification v1.11,
    // 4.3.4 logicalChannelConfigListElement
    if (lcinfo.lcId != 0)
    {
        FfMacCschedSapProvider::CschedLcConfigReqParameters params;
        params.m_rnti = lcinfo.rnti;
        params.m_reconfigureFlag = false;

        LogicalChannelConfigListElement_s lccle;
        lccle.m_logicalChannelIdentity = lcinfo.lcId;
        lccle.m_logicalChannelGroup = lcinfo.lcGroup;
        lccle.m_direction = LogicalChannelConfigListElement_s::DIR_BOTH;
        lccle.m_qci = lcinfo.qci;
        lccle.m_eRabMaximulBitrateUl = lcinfo.mbrUl;
        lccle.m_eRabMaximulBitrateDl = lcinfo.mbrDl;
        lccle.m_eRabGuaranteedBitrateUl = lcinfo.gbrUl;
        lccle.m_eRabGuaranteedBitrateDl = lcinfo.gbrDl;
        lccle.m_qosBearerType =
            static_cast<LogicalChannelConfigListElement_s::QosBearerType_e>(lcinfo.resourceType);

        params.m_logicalChannelConfigList.push_back(lccle);

        m_cschedSapProvider->CschedLcConfigReq(params);
    }
}

void
LteEnbMac::DoReconfigureLc(LteEnbCmacSapProvider::LcInfo lcinfo)
{
    NS_FATAL_ERROR("not implemented");
}

void
LteEnbMac::DoReleaseLc(uint16_t rnti, uint8_t lcid)
{
    NS_LOG_FUNCTION(this);

    // Find user based on rnti and then erase lcid stored against the same
    auto rntiIt = m_rlcAttached.find(rnti);
    rntiIt->second.erase(lcid);

    FfMacCschedSapProvider::CschedLcReleaseReqParameters params;
    params.m_rnti = rnti;
    params.m_logicalChannelIdentity.push_back(lcid);
    m_cschedSapProvider->CschedLcReleaseReq(params);
}

void
LteEnbMac::DoUeUpdateConfigurationReq(LteEnbCmacSapProvider::UeConfig params)
{
    NS_LOG_FUNCTION(this);

    // propagates to scheduler
    FfMacCschedSapProvider::CschedUeConfigReqParameters req;
    req.m_rnti = params.m_rnti;
    req.m_transmissionMode = params.m_transmissionMode;
    req.m_reconfigureFlag = true;
    m_cschedSapProvider->CschedUeConfigReq(req);
}

LteEnbCmacSapProvider::RachConfig
LteEnbMac::DoGetRachConfig() const
{
    LteEnbCmacSapProvider::RachConfig rc;
    rc.numberOfRaPreambles = m_numberOfRaPreambles;
    rc.preambleTransMax = m_preambleTransMax;
    rc.raResponseWindowSize = m_raResponseWindowSize;
    rc.connEstFailCount = m_connEstFailCount;
    return rc;
}

LteEnbCmacSapProvider::AllocateNcRaPreambleReturnValue
LteEnbMac::DoAllocateNcRaPreamble(uint16_t rnti)
{
    bool found = false;
    uint8_t preambleId;
    for (preambleId = m_numberOfRaPreambles; preambleId < 64; ++preambleId)
    {
        auto it = m_allocatedNcRaPreambleMap.find(preambleId);
        /**
         * Allocate preamble only if its free. The non-contention preamble
         * assigned to UE during handover or PDCCH order is valid only until the
         * time duration of the “expiryTime” of the preamble is reached. This
         * timer value is only maintained at the eNodeB and the UE has no way of
         * knowing if this timer has expired. If the UE tries to send the preamble
         * again after the expiryTime and the preamble is re-assigned to another
         * UE, it results in errors. This has been solved by re-assigning the
         * preamble to another UE only if it is not being used (An UE can be using
         * the preamble even after the expiryTime duration).
         */
        if ((it != m_allocatedNcRaPreambleMap.end()) && (it->second.expiryTime < Simulator::Now()))
        {
            if (!m_cmacSapUser->IsRandomAccessCompleted(it->second.rnti))
            {
                // random access of the UE is not completed,
                // check other preambles
                continue;
            }
        }
        if ((it == m_allocatedNcRaPreambleMap.end()) || (it->second.expiryTime < Simulator::Now()))
        {
            found = true;
            NcRaPreambleInfo preambleInfo;
            uint32_t expiryIntervalMs =
                (uint32_t)m_preambleTransMax * ((uint32_t)m_raResponseWindowSize + 5);

            preambleInfo.expiryTime = Simulator::Now() + MilliSeconds(expiryIntervalMs);
            preambleInfo.rnti = rnti;
            NS_LOG_INFO("allocated preamble for NC based RA: preamble "
                        << preambleId << ", RNTI " << preambleInfo.rnti << ", exiryTime "
                        << preambleInfo.expiryTime);
            m_allocatedNcRaPreambleMap[preambleId] =
                preambleInfo; // create if not exist, update otherwise
            break;
        }
    }
    LteEnbCmacSapProvider::AllocateNcRaPreambleReturnValue ret;
    if (found)
    {
        ret.valid = true;
        ret.raPreambleId = preambleId;
        ret.raPrachMaskIndex = 0;
    }
    else
    {
        ret.valid = false;
        ret.raPreambleId = 0;
        ret.raPrachMaskIndex = 0;
    }
    return ret;
}

// ////////////////////////////////////////////
// MAC SAP
// ////////////////////////////////////////////

void
LteEnbMac::DoTransmitPdu(LteMacSapProvider::TransmitPduParameters params)
{
    NS_LOG_FUNCTION(this);
    LteRadioBearerTag tag(params.rnti, params.lcid, params.layer);
    params.pdu->AddPacketTag(tag);
    params.componentCarrierId = m_componentCarrierId;
    // Store pkt in HARQ buffer
    auto it = m_miDlHarqProcessesPackets.find(params.rnti);
    NS_ASSERT(it != m_miDlHarqProcessesPackets.end());
    NS_LOG_DEBUG(this << " LAYER " << (uint16_t)tag.GetLayer() << " HARQ ID "
                      << (uint16_t)params.harqProcessId);

    //(*it).second.at (params.layer).at (params.harqProcessId) = params.pdu;//->Copy ();
    (*it).second.at(params.layer).at(params.harqProcessId)->AddPacket(params.pdu);
    m_enbPhySapProvider->SendMacPdu(params.pdu);
}

void
LteEnbMac::DoReportBufferStatus(LteMacSapProvider::ReportBufferStatusParameters params)
{
    NS_LOG_FUNCTION(this);
    FfMacSchedSapProvider::SchedDlRlcBufferReqParameters req;
    req.m_rnti = params.rnti;
    req.m_logicalChannelIdentity = params.lcid;
    req.m_rlcTransmissionQueueSize = params.txQueueSize;
    req.m_rlcTransmissionQueueHolDelay = params.txQueueHolDelay;
    req.m_rlcRetransmissionQueueSize = params.retxQueueSize;
    req.m_rlcRetransmissionHolDelay = params.retxQueueHolDelay;
    req.m_rlcStatusPduSize = params.statusPduSize;
    m_schedSapProvider->SchedDlRlcBufferReq(req);
}

// ////////////////////////////////////////////
// SCHED SAP
// ////////////////////////////////////////////

void
LteEnbMac::DoSchedDlConfigInd(FfMacSchedSapUser::SchedDlConfigIndParameters ind)
{
    NS_LOG_FUNCTION(this);
    // Create DL PHY PDU
    Ptr<PacketBurst> pb = CreateObject<PacketBurst>();
    LteMacSapUser::TxOpportunityParameters txOpParams;

    for (std::size_t i = 0; i < ind.m_buildDataList.size(); i++)
    {
        for (std::size_t layer = 0; layer < ind.m_buildDataList.at(i).m_dci.m_ndi.size(); layer++)
        {
            if (ind.m_buildDataList.at(i).m_dci.m_ndi.at(layer) == 1)
            {
                // new data -> force emptying correspondent harq pkt buffer
                auto it = m_miDlHarqProcessesPackets.find(ind.m_buildDataList.at(i).m_rnti);
                NS_ASSERT(it != m_miDlHarqProcessesPackets.end());
                for (std::size_t lcId = 0; lcId < (*it).second.size(); lcId++)
                {
                    Ptr<PacketBurst> pb = CreateObject<PacketBurst>();
                    (*it).second.at(lcId).at(ind.m_buildDataList.at(i).m_dci.m_harqProcess) = pb;
                }
            }
        }
        for (std::size_t j = 0; j < ind.m_buildDataList.at(i).m_rlcPduList.size(); j++)
        {
            for (std::size_t k = 0; k < ind.m_buildDataList.at(i).m_rlcPduList.at(j).size(); k++)
            {
                if (ind.m_buildDataList.at(i).m_dci.m_ndi.at(k) == 1)
                {
                    // New Data -> retrieve it from RLC
                    uint16_t rnti = ind.m_buildDataList.at(i).m_rnti;
                    uint8_t lcid =
                        ind.m_buildDataList.at(i).m_rlcPduList.at(j).at(k).m_logicalChannelIdentity;
                    auto rntiIt = m_rlcAttached.find(rnti);
                    NS_ASSERT_MSG(rntiIt != m_rlcAttached.end(), "could not find RNTI" << rnti);
                    auto lcidIt = rntiIt->second.find(lcid);
                    NS_ASSERT_MSG(lcidIt != rntiIt->second.end(),
                                  "could not find LCID" << (uint32_t)lcid << " carrier id:"
                                                        << (uint16_t)m_componentCarrierId);
                    NS_LOG_DEBUG(this << " rnti= " << rnti << " lcid= " << (uint32_t)lcid
                                      << " layer= " << k);
                    txOpParams.bytes = ind.m_buildDataList.at(i).m_rlcPduList.at(j).at(k).m_size;
                    txOpParams.layer = k;
                    txOpParams.harqId = ind.m_buildDataList.at(i).m_dci.m_harqProcess;
                    txOpParams.componentCarrierId = m_componentCarrierId;
                    txOpParams.rnti = rnti;
                    txOpParams.lcid = lcid;
                    (*lcidIt).second->NotifyTxOpportunity(txOpParams);
                }
                else
                {
                    if (ind.m_buildDataList.at(i).m_dci.m_tbsSize.at(k) > 0)
                    {
                        // HARQ retransmission -> retrieve TB from HARQ buffer
                        auto it = m_miDlHarqProcessesPackets.find(ind.m_buildDataList.at(i).m_rnti);
                        NS_ASSERT(it != m_miDlHarqProcessesPackets.end());
                        Ptr<PacketBurst> pb =
                            (*it).second.at(k).at(ind.m_buildDataList.at(i).m_dci.m_harqProcess);
                        for (auto j = pb->Begin(); j != pb->End(); ++j)
                        {
                            Ptr<Packet> pkt = (*j)->Copy();
                            m_enbPhySapProvider->SendMacPdu(pkt);
                        }
                    }
                }
            }
        }
        // send the relative DCI
        Ptr<DlDciLteControlMessage> msg = Create<DlDciLteControlMessage>();
        msg->SetDci(ind.m_buildDataList.at(i).m_dci);
        m_enbPhySapProvider->SendLteControlMessage(msg);
    }

    // Fire the trace with the DL information
    for (uint32_t i = 0; i < ind.m_buildDataList.size(); i++)
    {
        // Only one TB used
        if (ind.m_buildDataList.at(i).m_dci.m_tbsSize.size() == 1)
        {
            DlSchedulingCallbackInfo dlSchedulingCallbackInfo;
            dlSchedulingCallbackInfo.frameNo = m_frameNo;
            dlSchedulingCallbackInfo.subframeNo = m_subframeNo;
            dlSchedulingCallbackInfo.rnti = ind.m_buildDataList.at(i).m_dci.m_rnti;
            dlSchedulingCallbackInfo.mcsTb1 = ind.m_buildDataList.at(i).m_dci.m_mcs.at(0);
            dlSchedulingCallbackInfo.sizeTb1 = ind.m_buildDataList.at(i).m_dci.m_tbsSize.at(0);
            dlSchedulingCallbackInfo.mcsTb2 = 0;
            dlSchedulingCallbackInfo.sizeTb2 = 0;
            dlSchedulingCallbackInfo.componentCarrierId = m_componentCarrierId;
            m_dlScheduling(dlSchedulingCallbackInfo);
        }
        // Two TBs used
        else if (ind.m_buildDataList.at(i).m_dci.m_tbsSize.size() == 2)
        {
            DlSchedulingCallbackInfo dlSchedulingCallbackInfo;
            dlSchedulingCallbackInfo.frameNo = m_frameNo;
            dlSchedulingCallbackInfo.subframeNo = m_subframeNo;
            dlSchedulingCallbackInfo.rnti = ind.m_buildDataList.at(i).m_dci.m_rnti;
            dlSchedulingCallbackInfo.mcsTb1 = ind.m_buildDataList.at(i).m_dci.m_mcs.at(0);
            dlSchedulingCallbackInfo.sizeTb1 = ind.m_buildDataList.at(i).m_dci.m_tbsSize.at(0);
            dlSchedulingCallbackInfo.mcsTb2 = ind.m_buildDataList.at(i).m_dci.m_mcs.at(1);
            dlSchedulingCallbackInfo.sizeTb2 = ind.m_buildDataList.at(i).m_dci.m_tbsSize.at(1);
            dlSchedulingCallbackInfo.componentCarrierId = m_componentCarrierId;
            m_dlScheduling(dlSchedulingCallbackInfo);
        }
        else
        {
            NS_FATAL_ERROR("Found element with more than two transport blocks");
        }
    }

    // Random Access procedure: send RARs
    Ptr<RarLteControlMessage> rarMsg = Create<RarLteControlMessage>();
    // see TS 36.321 5.1.4;  preambles were sent two frames ago
    // (plus 3GPP counts subframes from 0, not 1)
    uint16_t raRnti;
    if (m_subframeNo < 3)
    {
        raRnti = m_subframeNo + 7; // equivalent to +10-3
    }
    else
    {
        raRnti = m_subframeNo - 3;
    }
    rarMsg->SetRaRnti(raRnti);
    for (unsigned int i = 0; i < ind.m_buildRarList.size(); i++)
    {
        auto itRapId = m_rapIdRntiMap.find(ind.m_buildRarList.at(i).m_rnti);
        if (itRapId == m_rapIdRntiMap.end())
        {
            NS_FATAL_ERROR("Unable to find rapId of RNTI " << ind.m_buildRarList.at(i).m_rnti);
        }
        RarLteControlMessage::Rar rar;
        rar.rapId = itRapId->second;
        rar.rarPayload = ind.m_buildRarList.at(i);
        rarMsg->AddRar(rar);
        NS_LOG_INFO(this << " Send RAR message to RNTI " << ind.m_buildRarList.at(i).m_rnti
                         << " rapId " << itRapId->second);
    }
    if (!ind.m_buildRarList.empty())
    {
        m_enbPhySapProvider->SendLteControlMessage(rarMsg);
    }
    m_rapIdRntiMap.clear();
}

void
LteEnbMac::DoSchedUlConfigInd(FfMacSchedSapUser::SchedUlConfigIndParameters ind)
{
    NS_LOG_FUNCTION(this);

    for (unsigned int i = 0; i < ind.m_dciList.size(); i++)
    {
        // send the correspondent ul dci
        Ptr<UlDciLteControlMessage> msg = Create<UlDciLteControlMessage>();
        msg->SetDci(ind.m_dciList.at(i));
        m_enbPhySapProvider->SendLteControlMessage(msg);
    }

    // Fire the trace with the UL information
    for (uint32_t i = 0; i < ind.m_dciList.size(); i++)
    {
        m_ulScheduling(m_frameNo,
                       m_subframeNo,
                       ind.m_dciList.at(i).m_rnti,
                       ind.m_dciList.at(i).m_mcs,
                       ind.m_dciList.at(i).m_tbSize,
                       m_componentCarrierId);
    }
}

// ////////////////////////////////////////////
// CSCHED SAP
// ////////////////////////////////////////////

void
LteEnbMac::DoCschedCellConfigCnf(FfMacCschedSapUser::CschedCellConfigCnfParameters params)
{
    NS_LOG_FUNCTION(this);
}

void
LteEnbMac::DoCschedUeConfigCnf(FfMacCschedSapUser::CschedUeConfigCnfParameters params)
{
    NS_LOG_FUNCTION(this);
}

void
LteEnbMac::DoCschedLcConfigCnf(FfMacCschedSapUser::CschedLcConfigCnfParameters params)
{
    NS_LOG_FUNCTION(this);
    // Call the CSCHED primitive
    // m_cschedSap->LcConfigCompleted();
}

void
LteEnbMac::DoCschedLcReleaseCnf(FfMacCschedSapUser::CschedLcReleaseCnfParameters params)
{
    NS_LOG_FUNCTION(this);
}

void
LteEnbMac::DoCschedUeReleaseCnf(FfMacCschedSapUser::CschedUeReleaseCnfParameters params)
{
    NS_LOG_FUNCTION(this);
}

void
LteEnbMac::DoCschedUeConfigUpdateInd(FfMacCschedSapUser::CschedUeConfigUpdateIndParameters params)
{
    NS_LOG_FUNCTION(this);
    // propagates to RRC
    LteEnbCmacSapUser::UeConfig ueConfigUpdate;
    ueConfigUpdate.m_rnti = params.m_rnti;
    ueConfigUpdate.m_transmissionMode = params.m_transmissionMode;
    m_cmacSapUser->RrcConfigurationUpdateInd(ueConfigUpdate);
}

void
LteEnbMac::DoCschedCellConfigUpdateInd(
    FfMacCschedSapUser::CschedCellConfigUpdateIndParameters params)
{
    NS_LOG_FUNCTION(this);
}

void
LteEnbMac::DoUlInfoListElementHarqFeedback(UlInfoListElement_s params)
{
    NS_LOG_FUNCTION(this);
    m_ulInfoListReceived.push_back(params);
}

void
LteEnbMac::DoDlInfoListElementHarqFeedback(DlInfoListElement_s params)
{
    NS_LOG_FUNCTION(this);
    // Update HARQ buffer
    auto it = m_miDlHarqProcessesPackets.find(params.m_rnti);
    NS_ASSERT(it != m_miDlHarqProcessesPackets.end());
    for (std::size_t layer = 0; layer < params.m_harqStatus.size(); layer++)
    {
        if (params.m_harqStatus.at(layer) == DlInfoListElement_s::ACK)
        {
            // discard buffer
            Ptr<PacketBurst> emptyBuf = CreateObject<PacketBurst>();
            (*it).second.at(layer).at(params.m_harqProcessId) = emptyBuf;
            NS_LOG_DEBUG(this << " HARQ-ACK UE " << params.m_rnti << " harqId "
                              << (uint16_t)params.m_harqProcessId << " layer " << (uint16_t)layer);
        }
        else if (params.m_harqStatus.at(layer) == DlInfoListElement_s::NACK)
        {
            NS_LOG_DEBUG(this << " HARQ-NACK UE " << params.m_rnti << " harqId "
                              << (uint16_t)params.m_harqProcessId << " layer " << (uint16_t)layer);
        }
        else
        {
            NS_FATAL_ERROR(" HARQ functionality not implemented");
        }
    }
    m_dlInfoListReceived.push_back(params);
}

} // namespace ns3
