/*
 * Copyright (c) 2011 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Nicola Baldo  <nbaldo@cttc.es>
 * Author: Marco Miozzo <mmiozzo@cttc.es>
 */

#include "lte-ue-mac.h"

#include "ff-mac-common.h"
#include "lte-common.h"
#include "lte-control-messages.h"
#include "lte-radio-bearer-tag.h"

#include "ns3/log.h"
#include "ns3/packet-burst.h"
#include "ns3/packet.h"
#include "ns3/pointer.h"
#include "ns3/random-variable-stream.h"
#include "ns3/simulator.h"

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("LteUeMac");

NS_OBJECT_ENSURE_REGISTERED(LteUeMac);

///////////////////////////////////////////////////////////
// SAP forwarders
///////////////////////////////////////////////////////////

/// UeMemberLteUeCmacSapProvider class
class UeMemberLteUeCmacSapProvider : public LteUeCmacSapProvider
{
  public:
    /**
     * Constructor
     *
     * @param mac the UE MAC
     */
    UeMemberLteUeCmacSapProvider(LteUeMac* mac);

    // inherited from LteUeCmacSapProvider
    void ConfigureRach(RachConfig rc) override;
    void StartContentionBasedRandomAccessProcedure() override;
    void StartNonContentionBasedRandomAccessProcedure(uint16_t rnti,
                                                      uint8_t preambleId,
                                                      uint8_t prachMask) override;
    void SetRnti(uint16_t rnti) override;
    void AddLc(uint8_t lcId,
               LteUeCmacSapProvider::LogicalChannelConfig lcConfig,
               LteMacSapUser* msu) override;
    void RemoveLc(uint8_t lcId) override;
    void Reset() override;
    void NotifyConnectionSuccessful() override;
    void SetImsi(uint64_t imsi) override;

  private:
    LteUeMac* m_mac; ///< the UE MAC
};

UeMemberLteUeCmacSapProvider::UeMemberLteUeCmacSapProvider(LteUeMac* mac)
    : m_mac(mac)
{
}

void
UeMemberLteUeCmacSapProvider::ConfigureRach(RachConfig rc)
{
    m_mac->DoConfigureRach(rc);
}

void
UeMemberLteUeCmacSapProvider::StartContentionBasedRandomAccessProcedure()
{
    m_mac->DoStartContentionBasedRandomAccessProcedure();
}

void
UeMemberLteUeCmacSapProvider::StartNonContentionBasedRandomAccessProcedure(uint16_t rnti,
                                                                           uint8_t preambleId,
                                                                           uint8_t prachMask)
{
    m_mac->DoStartNonContentionBasedRandomAccessProcedure(rnti, preambleId, prachMask);
}

void
UeMemberLteUeCmacSapProvider::SetRnti(uint16_t rnti)
{
    m_mac->DoSetRnti(rnti);
}

void
UeMemberLteUeCmacSapProvider::AddLc(uint8_t lcId, LogicalChannelConfig lcConfig, LteMacSapUser* msu)
{
    m_mac->DoAddLc(lcId, lcConfig, msu);
}

void
UeMemberLteUeCmacSapProvider::RemoveLc(uint8_t lcid)
{
    m_mac->DoRemoveLc(lcid);
}

void
UeMemberLteUeCmacSapProvider::Reset()
{
    m_mac->DoReset();
}

void
UeMemberLteUeCmacSapProvider::NotifyConnectionSuccessful()
{
    m_mac->DoNotifyConnectionSuccessful();
}

void
UeMemberLteUeCmacSapProvider::SetImsi(uint64_t imsi)
{
    m_mac->DoSetImsi(imsi);
}

/// UeMemberLteMacSapProvider class
class UeMemberLteMacSapProvider : public LteMacSapProvider
{
  public:
    /**
     * Constructor
     *
     * @param mac the UE MAC
     */
    UeMemberLteMacSapProvider(LteUeMac* mac);

    // inherited from LteMacSapProvider
    void TransmitPdu(TransmitPduParameters params) override;
    void ReportBufferStatus(ReportBufferStatusParameters params) override;

  private:
    LteUeMac* m_mac; ///< the UE MAC
};

UeMemberLteMacSapProvider::UeMemberLteMacSapProvider(LteUeMac* mac)
    : m_mac(mac)
{
}

void
UeMemberLteMacSapProvider::TransmitPdu(TransmitPduParameters params)
{
    m_mac->DoTransmitPdu(params);
}

void
UeMemberLteMacSapProvider::ReportBufferStatus(ReportBufferStatusParameters params)
{
    m_mac->DoReportBufferStatus(params);
}

/**
 * UeMemberLteUePhySapUser
 */
class UeMemberLteUePhySapUser : public LteUePhySapUser
{
  public:
    /**
     * Constructor
     *
     * @param mac the UE MAC
     */
    UeMemberLteUePhySapUser(LteUeMac* mac);

    // inherited from LtePhySapUser
    void ReceivePhyPdu(Ptr<Packet> p) override;
    void SubframeIndication(uint32_t frameNo, uint32_t subframeNo) override;
    void ReceiveLteControlMessage(Ptr<LteControlMessage> msg) override;

  private:
    LteUeMac* m_mac; ///< the UE MAC
};

UeMemberLteUePhySapUser::UeMemberLteUePhySapUser(LteUeMac* mac)
    : m_mac(mac)
{
}

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

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

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

//////////////////////////////////////////////////////////
// LteUeMac methods
///////////////////////////////////////////////////////////

TypeId
LteUeMac::GetTypeId()
{
    static TypeId tid =
        TypeId("ns3::LteUeMac")
            .SetParent<Object>()
            .SetGroupName("Lte")
            .AddConstructor<LteUeMac>()
            .AddTraceSource("RaResponseTimeout",
                            "trace fired upon RA response timeout",
                            MakeTraceSourceAccessor(&LteUeMac::m_raResponseTimeoutTrace),
                            "ns3::LteUeMac::RaResponseTimeoutTracedCallback")

        ;
    return tid;
}

LteUeMac::LteUeMac()
    : m_bsrPeriodicity(MilliSeconds(1)), // ideal behavior
      m_bsrLast(),
      m_freshUlBsr(false),
      m_harqProcessId(0),
      m_miUlHarqProcessesPacketTimer(HARQ_PERIOD, 0),
      m_rnti(0),
      m_imsi(0),
      m_rachConfigured(false),
      m_waitingForRaResponse(false)
{
    NS_LOG_FUNCTION(this);
    m_miUlHarqProcessesPacket.resize(HARQ_PERIOD);
    for (std::size_t i = 0; i < m_miUlHarqProcessesPacket.size(); i++)
    {
        Ptr<PacketBurst> pb = CreateObject<PacketBurst>();
        m_miUlHarqProcessesPacket.at(i) = pb;
    }

    m_macSapProvider = new UeMemberLteMacSapProvider(this);
    m_cmacSapProvider = new UeMemberLteUeCmacSapProvider(this);
    m_uePhySapUser = new UeMemberLteUePhySapUser(this);
    m_raPreambleUniformVariable = CreateObject<UniformRandomVariable>();
    m_componentCarrierId = 0;
}

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

void
LteUeMac::DoDispose()
{
    NS_LOG_FUNCTION(this);
    m_miUlHarqProcessesPacket.clear();
    delete m_macSapProvider;
    delete m_cmacSapProvider;
    delete m_uePhySapUser;
    Object::DoDispose();
}

LteUePhySapUser*
LteUeMac::GetLteUePhySapUser()
{
    return m_uePhySapUser;
}

void
LteUeMac::SetLteUePhySapProvider(LteUePhySapProvider* s)
{
    m_uePhySapProvider = s;
}

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

void
LteUeMac::SetLteUeCmacSapUser(LteUeCmacSapUser* s)
{
    m_cmacSapUser = s;
}

LteUeCmacSapProvider*
LteUeMac::GetLteUeCmacSapProvider()
{
    return m_cmacSapProvider;
}

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

void
LteUeMac::DoTransmitPdu(LteMacSapProvider::TransmitPduParameters params)
{
    NS_LOG_FUNCTION(this);
    NS_ASSERT_MSG(m_rnti == params.rnti, "RNTI mismatch between RLC and MAC");
    LteRadioBearerTag tag(params.rnti, params.lcid, 0 /* UE works in SISO mode*/);
    params.pdu->AddPacketTag(tag);
    // store pdu in HARQ buffer
    m_miUlHarqProcessesPacket.at(m_harqProcessId)->AddPacket(params.pdu);
    m_miUlHarqProcessesPacketTimer.at(m_harqProcessId) = HARQ_PERIOD;
    m_uePhySapProvider->SendMacPdu(params.pdu);
}

void
LteUeMac::DoReportBufferStatus(LteMacSapProvider::ReportBufferStatusParameters params)
{
    NS_LOG_FUNCTION(this << (uint32_t)params.lcid);

    auto it = m_ulBsrReceived.find(params.lcid);
    if (it != m_ulBsrReceived.end())
    {
        // update entry
        (*it).second = params;
    }
    else
    {
        m_ulBsrReceived.insert(
            std::pair<uint8_t, LteMacSapProvider::ReportBufferStatusParameters>(params.lcid,
                                                                                params));
    }
    m_freshUlBsr = true;
}

void
LteUeMac::SendReportBufferStatus()
{
    NS_LOG_FUNCTION(this);

    if (m_rnti == 0)
    {
        NS_LOG_INFO("MAC not initialized, BSR deferred");
        return;
    }

    if (m_ulBsrReceived.empty())
    {
        NS_LOG_INFO("No BSR report to transmit");
        return;
    }
    MacCeListElement_s bsr;
    bsr.m_rnti = m_rnti;
    bsr.m_macCeType = MacCeListElement_s::BSR;

    // BSR is reported for each LCG
    std::vector<uint32_t> queue(4, 0); // one value per each of the 4 LCGs, initialized to 0
    for (auto it = m_ulBsrReceived.begin(); it != m_ulBsrReceived.end(); it++)
    {
        uint8_t lcid = it->first;
        auto lcInfoMapIt = m_lcInfoMap.find(lcid);
        NS_ASSERT(lcInfoMapIt != m_lcInfoMap.end());
        NS_ASSERT_MSG((lcid != 0) ||
                          (((*it).second.txQueueSize == 0) && ((*it).second.retxQueueSize == 0) &&
                           ((*it).second.statusPduSize == 0)),
                      "BSR should not be used for LCID 0");
        uint8_t lcg = lcInfoMapIt->second.lcConfig.logicalChannelGroup;
        queue.at(lcg) +=
            ((*it).second.txQueueSize + (*it).second.retxQueueSize + (*it).second.statusPduSize);
    }

    // FF API says that all 4 LCGs are always present
    bsr.m_macCeValue.m_bufferStatus.push_back(BufferSizeLevelBsr::BufferSize2BsrId(queue.at(0)));
    bsr.m_macCeValue.m_bufferStatus.push_back(BufferSizeLevelBsr::BufferSize2BsrId(queue.at(1)));
    bsr.m_macCeValue.m_bufferStatus.push_back(BufferSizeLevelBsr::BufferSize2BsrId(queue.at(2)));
    bsr.m_macCeValue.m_bufferStatus.push_back(BufferSizeLevelBsr::BufferSize2BsrId(queue.at(3)));

    // create the feedback to eNB
    Ptr<BsrLteControlMessage> msg = Create<BsrLteControlMessage>();
    msg->SetBsr(bsr);
    m_uePhySapProvider->SendLteControlMessage(msg);
}

void
LteUeMac::RandomlySelectAndSendRaPreamble()
{
    NS_LOG_FUNCTION(this);
    // 3GPP 36.321 5.1.1
    NS_ASSERT_MSG(m_rachConfigured, "RACH not configured");
    // assume that there is no Random Access Preambles group B
    m_raPreambleId =
        m_raPreambleUniformVariable->GetInteger(0, m_rachConfig.numberOfRaPreambles - 1);
    bool contention = true;
    SendRaPreamble(contention);
}

void
LteUeMac::SendRaPreamble(bool contention)
{
    NS_LOG_FUNCTION(this << (uint32_t)m_raPreambleId << contention);
    // Since regular UL LteControlMessages need m_ulConfigured = true in
    // order to be sent by the UE, the rach preamble needs to be sent
    // with a dedicated primitive (not
    // m_uePhySapProvider->SendLteControlMessage (msg)) so that it can
    // bypass the m_ulConfigured flag. This is reasonable, since In fact
    // the RACH preamble is sent on 6RB bandwidth so the uplink
    // bandwidth does not need to be configured.
    NS_ASSERT(m_subframeNo > 0); // sanity check for subframe starting at 1
    m_raRnti = m_subframeNo - 1;
    m_uePhySapProvider->SendRachPreamble(m_raPreambleId, m_raRnti);
    NS_LOG_INFO(this << " sent preamble id " << (uint32_t)m_raPreambleId << ", RA-RNTI "
                     << (uint32_t)m_raRnti);
    // 3GPP 36.321 5.1.4
    Time raWindowBegin = MilliSeconds(3);
    Time raWindowEnd = MilliSeconds(3 + m_rachConfig.raResponseWindowSize);
    Simulator::Schedule(raWindowBegin, &LteUeMac::StartWaitingForRaResponse, this);
    m_noRaResponseReceivedEvent =
        Simulator::Schedule(raWindowEnd, &LteUeMac::RaResponseTimeout, this, contention);
}

void
LteUeMac::StartWaitingForRaResponse()
{
    NS_LOG_FUNCTION(this);
    m_waitingForRaResponse = true;
}

void
LteUeMac::RecvRaResponse(BuildRarListElement_s raResponse)
{
    NS_LOG_FUNCTION(this);
    m_waitingForRaResponse = false;
    m_noRaResponseReceivedEvent.Cancel();
    NS_LOG_INFO("got RAR for RAPID " << (uint32_t)m_raPreambleId
                                     << ", setting T-C-RNTI = " << raResponse.m_rnti);
    m_rnti = raResponse.m_rnti;
    m_cmacSapUser->SetTemporaryCellRnti(m_rnti);
    // in principle we should wait for contention resolution,
    // but in the current LTE model when two or more identical
    // preambles are sent no one is received, so there is no need
    // for contention resolution
    m_cmacSapUser->NotifyRandomAccessSuccessful();
    // trigger tx opportunity for Message 3 over LC 0
    // this is needed since Message 3's UL GRANT is in the RAR, not in UL-DCIs
    const uint8_t lc0Lcid = 0;
    auto lc0InfoIt = m_lcInfoMap.find(lc0Lcid);
    NS_ASSERT(lc0InfoIt != m_lcInfoMap.end());
    auto lc0BsrIt = m_ulBsrReceived.find(lc0Lcid);
    if ((lc0BsrIt != m_ulBsrReceived.end()) && (lc0BsrIt->second.txQueueSize > 0))
    {
        NS_ASSERT_MSG(raResponse.m_grant.m_tbSize > lc0BsrIt->second.txQueueSize,
                      "segmentation of Message 3 is not allowed");
        // this function can be called only from primary carrier
        if (m_componentCarrierId > 0)
        {
            NS_FATAL_ERROR("Function called on wrong componentCarrier");
        }
        LteMacSapUser::TxOpportunityParameters txOpParams;
        txOpParams.bytes = raResponse.m_grant.m_tbSize;
        txOpParams.layer = 0;
        txOpParams.harqId = 0;
        txOpParams.componentCarrierId = m_componentCarrierId;
        txOpParams.rnti = m_rnti;
        txOpParams.lcid = lc0Lcid;
        lc0InfoIt->second.macSapUser->NotifyTxOpportunity(txOpParams);
        lc0BsrIt->second.txQueueSize = 0;
    }
}

void
LteUeMac::RaResponseTimeout(bool contention)
{
    NS_LOG_FUNCTION(this << contention);
    m_waitingForRaResponse = false;
    // 3GPP 36.321 5.1.4
    ++m_preambleTransmissionCounter;
    // fire RA response timeout trace
    m_raResponseTimeoutTrace(m_imsi,
                             contention,
                             m_preambleTransmissionCounter,
                             m_rachConfig.preambleTransMax + 1);
    if (m_preambleTransmissionCounter == m_rachConfig.preambleTransMax + 1)
    {
        NS_LOG_INFO("RAR timeout, preambleTransMax reached => giving up");
        m_cmacSapUser->NotifyRandomAccessFailed();
    }
    else
    {
        NS_LOG_INFO("RAR timeout, re-send preamble");
        if (contention)
        {
            RandomlySelectAndSendRaPreamble();
        }
        else
        {
            SendRaPreamble(contention);
        }
    }
}

void
LteUeMac::DoConfigureRach(LteUeCmacSapProvider::RachConfig rc)
{
    NS_LOG_FUNCTION(this);
    m_rachConfig = rc;
    m_rachConfigured = true;
}

void
LteUeMac::DoStartContentionBasedRandomAccessProcedure()
{
    NS_LOG_FUNCTION(this);

    // 3GPP 36.321 5.1.1
    NS_ASSERT_MSG(m_rachConfigured, "RACH not configured");
    m_preambleTransmissionCounter = 0;
    m_backoffParameter = 0;
    RandomlySelectAndSendRaPreamble();
}

void
LteUeMac::DoSetRnti(uint16_t rnti)
{
    NS_LOG_FUNCTION(this);
    m_rnti = rnti;
}

void
LteUeMac::DoSetImsi(uint64_t imsi)
{
    NS_LOG_FUNCTION(this);
    m_imsi = imsi;
}

void
LteUeMac::DoStartNonContentionBasedRandomAccessProcedure(uint16_t rnti,
                                                         uint8_t preambleId,
                                                         uint8_t prachMask)
{
    NS_LOG_FUNCTION(this << rnti << (uint16_t)preambleId << (uint16_t)prachMask);
    NS_ASSERT_MSG(prachMask == 0,
                  "requested PRACH MASK = " << (uint32_t)prachMask
                                            << ", but only PRACH MASK = 0 is supported");
    m_rnti = rnti;
    m_raPreambleId = preambleId;
    m_preambleTransmissionCounter = 0;
    bool contention = false;
    SendRaPreamble(contention);
}

void
LteUeMac::DoAddLc(uint8_t lcId,
                  LteUeCmacSapProvider::LogicalChannelConfig lcConfig,
                  LteMacSapUser* msu)
{
    NS_LOG_FUNCTION(this << " lcId" << (uint32_t)lcId);
    NS_ASSERT_MSG(m_lcInfoMap.find(lcId) == m_lcInfoMap.end(),
                  "cannot add channel because LCID " << (uint16_t)lcId << " is already present");

    LcInfo lcInfo;
    lcInfo.lcConfig = lcConfig;
    lcInfo.macSapUser = msu;
    m_lcInfoMap[lcId] = lcInfo;
}

void
LteUeMac::DoRemoveLc(uint8_t lcId)
{
    NS_LOG_FUNCTION(this << " lcId" << lcId);
    NS_ASSERT_MSG(m_lcInfoMap.find(lcId) != m_lcInfoMap.end(), "could not find LCID " << lcId);
    m_lcInfoMap.erase(lcId);
    m_ulBsrReceived.erase(lcId); // empty BSR buffer for this lcId
}

void
LteUeMac::DoReset()
{
    NS_LOG_FUNCTION(this);
    auto it = m_lcInfoMap.begin();
    while (it != m_lcInfoMap.end())
    {
        // don't delete CCCH)
        if (it->first == 0)
        {
            ++it;
        }
        else
        {
            // note: use of postfix operator preserves validity of iterator
            m_lcInfoMap.erase(it++);
        }
    }
    // note: rnti will be assigned by the eNB using RA response message
    m_rnti = 0;
    m_noRaResponseReceivedEvent.Cancel();
    m_rachConfigured = false;
    m_freshUlBsr = false;
    m_ulBsrReceived.clear();
}

void
LteUeMac::DoNotifyConnectionSuccessful()
{
    NS_LOG_FUNCTION(this);
    m_uePhySapProvider->NotifyConnectionSuccessful();
}

void
LteUeMac::DoReceivePhyPdu(Ptr<Packet> p)
{
    LteRadioBearerTag tag;
    p->RemovePacketTag(tag);
    if (tag.GetRnti() == m_rnti)
    {
        // packet is for the current user
        auto it = m_lcInfoMap.find(tag.GetLcid());
        if (it != m_lcInfoMap.end())
        {
            LteMacSapUser::ReceivePduParameters rxPduParams;
            rxPduParams.p = p;
            rxPduParams.rnti = m_rnti;
            rxPduParams.lcid = tag.GetLcid();
            it->second.macSapUser->ReceivePdu(rxPduParams);
        }
        else
        {
            NS_LOG_WARN("received packet with unknown lcid " << (uint32_t)tag.GetLcid());
        }
    }
}

void
LteUeMac::DoReceiveLteControlMessage(Ptr<LteControlMessage> msg)
{
    NS_LOG_FUNCTION(this);
    if (msg->GetMessageType() == LteControlMessage::UL_DCI)
    {
        Ptr<UlDciLteControlMessage> msg2 = DynamicCast<UlDciLteControlMessage>(msg);
        UlDciListElement_s dci = msg2->GetDci();
        if (dci.m_ndi == 1)
        {
            // New transmission -> empty pkt buffer queue (for deleting eventual pkts not acked )
            Ptr<PacketBurst> pb = CreateObject<PacketBurst>();
            m_miUlHarqProcessesPacket.at(m_harqProcessId) = pb;
            // Retrieve data from RLC
            uint16_t activeLcs = 0;
            uint32_t statusPduMinSize = 0;
            for (auto itBsr = m_ulBsrReceived.begin(); itBsr != m_ulBsrReceived.end(); itBsr++)
            {
                if (((*itBsr).second.statusPduSize > 0) || ((*itBsr).second.retxQueueSize > 0) ||
                    ((*itBsr).second.txQueueSize > 0))
                {
                    activeLcs++;
                    if (((*itBsr).second.statusPduSize != 0) &&
                        ((*itBsr).second.statusPduSize < statusPduMinSize))
                    {
                        statusPduMinSize = (*itBsr).second.statusPduSize;
                    }
                    if (((*itBsr).second.statusPduSize != 0) && (statusPduMinSize == 0))
                    {
                        statusPduMinSize = (*itBsr).second.statusPduSize;
                    }
                }
            }
            if (activeLcs == 0)
            {
                NS_LOG_ERROR(this << " No active flows for this UL-DCI");
                return;
            }
            uint32_t bytesPerActiveLc = dci.m_tbSize / activeLcs;
            bool statusPduPriority = false;
            if ((statusPduMinSize != 0) && (bytesPerActiveLc < statusPduMinSize))
            {
                // send only the status PDU which has highest priority
                statusPduPriority = true;
                NS_LOG_DEBUG(this << " Reduced resource -> send only Status, b ytes "
                                  << statusPduMinSize);
                if (dci.m_tbSize < statusPduMinSize)
                {
                    NS_FATAL_ERROR("Insufficient Tx Opportunity for sending a status message");
                }
            }
            NS_LOG_LOGIC(this << " UE " << m_rnti << ": UL-CQI notified TxOpportunity of "
                              << dci.m_tbSize << " => " << bytesPerActiveLc
                              << " bytes per active LC"
                              << " statusPduMinSize " << statusPduMinSize);

            LteMacSapUser::TxOpportunityParameters txOpParams;

            for (auto it = m_lcInfoMap.begin(); it != m_lcInfoMap.end(); it++)
            {
                auto itBsr = m_ulBsrReceived.find((*it).first);
                NS_LOG_DEBUG(this << " Processing LC " << (uint32_t)(*it).first
                                  << " bytesPerActiveLc " << bytesPerActiveLc);
                if ((itBsr != m_ulBsrReceived.end()) &&
                    (((*itBsr).second.statusPduSize > 0) || ((*itBsr).second.retxQueueSize > 0) ||
                     ((*itBsr).second.txQueueSize > 0)))
                {
                    if ((statusPduPriority) && ((*itBsr).second.statusPduSize == statusPduMinSize))
                    {
                        txOpParams.bytes = (*itBsr).second.statusPduSize;
                        txOpParams.layer = 0;
                        txOpParams.harqId = 0;
                        txOpParams.componentCarrierId = m_componentCarrierId;
                        txOpParams.rnti = m_rnti;
                        txOpParams.lcid = (*it).first;
                        (*it).second.macSapUser->NotifyTxOpportunity(txOpParams);
                        NS_LOG_LOGIC(this << "\t" << bytesPerActiveLc << " send  "
                                          << (*itBsr).second.statusPduSize << " status bytes to LC "
                                          << (uint32_t)(*it).first << " statusQueue "
                                          << (*itBsr).second.statusPduSize << " retxQueue"
                                          << (*itBsr).second.retxQueueSize << " txQueue"
                                          << (*itBsr).second.txQueueSize);
                        (*itBsr).second.statusPduSize = 0;
                        break;
                    }
                    else
                    {
                        uint32_t bytesForThisLc = bytesPerActiveLc;
                        NS_LOG_LOGIC(this << "\t" << bytesPerActiveLc << " bytes to LC "
                                          << (uint32_t)(*it).first << " statusQueue "
                                          << (*itBsr).second.statusPduSize << " retxQueue"
                                          << (*itBsr).second.retxQueueSize << " txQueue"
                                          << (*itBsr).second.txQueueSize);
                        if (((*itBsr).second.statusPduSize > 0) &&
                            (bytesForThisLc > (*itBsr).second.statusPduSize))
                        {
                            txOpParams.bytes = (*itBsr).second.statusPduSize;
                            txOpParams.layer = 0;
                            txOpParams.harqId = 0;
                            txOpParams.componentCarrierId = m_componentCarrierId;
                            txOpParams.rnti = m_rnti;
                            txOpParams.lcid = (*it).first;
                            (*it).second.macSapUser->NotifyTxOpportunity(txOpParams);
                            bytesForThisLc -= (*itBsr).second.statusPduSize;
                            NS_LOG_DEBUG(this << " serve STATUS " << (*itBsr).second.statusPduSize);
                            (*itBsr).second.statusPduSize = 0;
                        }
                        else
                        {
                            if ((*itBsr).second.statusPduSize > bytesForThisLc)
                            {
                                NS_FATAL_ERROR(
                                    "Insufficient Tx Opportunity for sending a status message");
                            }
                        }

                        if ((bytesForThisLc > 7) // 7 is the min TxOpportunity useful for Rlc
                            && (((*itBsr).second.retxQueueSize > 0) ||
                                ((*itBsr).second.txQueueSize > 0)))
                        {
                            if ((*itBsr).second.retxQueueSize > 0)
                            {
                                NS_LOG_DEBUG(this << " serve retx DATA, bytes " << bytesForThisLc);
                                txOpParams.bytes = bytesForThisLc;
                                txOpParams.layer = 0;
                                txOpParams.harqId = 0;
                                txOpParams.componentCarrierId = m_componentCarrierId;
                                txOpParams.rnti = m_rnti;
                                txOpParams.lcid = (*it).first;
                                (*it).second.macSapUser->NotifyTxOpportunity(txOpParams);
                                if ((*itBsr).second.retxQueueSize >= bytesForThisLc)
                                {
                                    (*itBsr).second.retxQueueSize -= bytesForThisLc;
                                }
                                else
                                {
                                    (*itBsr).second.retxQueueSize = 0;
                                }
                            }
                            else if ((*itBsr).second.txQueueSize > 0)
                            {
                                uint16_t lcid = (*it).first;
                                uint32_t rlcOverhead;
                                if (lcid == 1)
                                {
                                    // for SRB1 (using RLC AM) it's better to
                                    // overestimate RLC overhead rather than
                                    // underestimate it and risk unneeded
                                    // segmentation which increases delay
                                    rlcOverhead = 4;
                                }
                                else
                                {
                                    // minimum RLC overhead due to header
                                    rlcOverhead = 2;
                                }
                                NS_LOG_DEBUG(this << " serve tx DATA, bytes " << bytesForThisLc
                                                  << ", RLC overhead " << rlcOverhead);
                                txOpParams.bytes = bytesForThisLc;
                                txOpParams.layer = 0;
                                txOpParams.harqId = 0;
                                txOpParams.componentCarrierId = m_componentCarrierId;
                                txOpParams.rnti = m_rnti;
                                txOpParams.lcid = (*it).first;
                                (*it).second.macSapUser->NotifyTxOpportunity(txOpParams);
                                if ((*itBsr).second.txQueueSize >= bytesForThisLc - rlcOverhead)
                                {
                                    (*itBsr).second.txQueueSize -= bytesForThisLc - rlcOverhead;
                                }
                                else
                                {
                                    (*itBsr).second.txQueueSize = 0;
                                }
                            }
                        }
                        else
                        {
                            if (((*itBsr).second.retxQueueSize > 0) ||
                                ((*itBsr).second.txQueueSize > 0))
                            {
                                // resend BSR info for updating eNB peer MAC
                                m_freshUlBsr = true;
                            }
                        }
                        NS_LOG_LOGIC(this << "\t" << bytesPerActiveLc << "\t new queues "
                                          << (uint32_t)(*it).first << " statusQueue "
                                          << (*itBsr).second.statusPduSize << " retxQueue"
                                          << (*itBsr).second.retxQueueSize << " txQueue"
                                          << (*itBsr).second.txQueueSize);
                    }
                }
            }
        }
        else
        {
            // HARQ retransmission -> retrieve data from HARQ buffer
            NS_LOG_DEBUG(this << " UE MAC RETX HARQ " << (uint16_t)m_harqProcessId);
            Ptr<PacketBurst> pb = m_miUlHarqProcessesPacket.at(m_harqProcessId);
            for (auto j = pb->Begin(); j != pb->End(); ++j)
            {
                Ptr<Packet> pkt = (*j)->Copy();
                m_uePhySapProvider->SendMacPdu(pkt);
            }
            m_miUlHarqProcessesPacketTimer.at(m_harqProcessId) = HARQ_PERIOD;
        }
    }
    else if (msg->GetMessageType() == LteControlMessage::RAR)
    {
        if (m_waitingForRaResponse)
        {
            Ptr<RarLteControlMessage> rarMsg = DynamicCast<RarLteControlMessage>(msg);
            uint16_t raRnti = rarMsg->GetRaRnti();
            NS_LOG_LOGIC(this << "got RAR with RA-RNTI " << (uint32_t)raRnti << ", expecting "
                              << (uint32_t)m_raRnti);
            if (raRnti == m_raRnti) // RAR corresponds to TX subframe of preamble
            {
                for (auto it = rarMsg->RarListBegin(); it != rarMsg->RarListEnd(); ++it)
                {
                    if (it->rapId == m_raPreambleId) // RAR is for me
                    {
                        RecvRaResponse(it->rarPayload);
                        /// @todo RRC generates the RecvRaResponse messaged
                        /// for avoiding holes in transmission at PHY layer
                        /// (which produce erroneous UL CQI evaluation)
                    }
                }
            }
        }
    }
    else
    {
        NS_LOG_WARN(this << " LteControlMessage not recognized");
    }
}

void
LteUeMac::RefreshHarqProcessesPacketBuffer()
{
    NS_LOG_FUNCTION(this);

    for (std::size_t i = 0; i < m_miUlHarqProcessesPacketTimer.size(); i++)
    {
        if (m_miUlHarqProcessesPacketTimer.at(i) == 0)
        {
            if (m_miUlHarqProcessesPacket.at(i)->GetSize() > 0)
            {
                // timer expired: drop packets in buffer for this process
                NS_LOG_INFO(this << " HARQ Proc Id " << i << " packets buffer expired");
                Ptr<PacketBurst> emptyPb = CreateObject<PacketBurst>();
                m_miUlHarqProcessesPacket.at(i) = emptyPb;
            }
        }
        else
        {
            m_miUlHarqProcessesPacketTimer.at(i)--;
        }
    }
}

void
LteUeMac::DoSubframeIndication(uint32_t frameNo, uint32_t subframeNo)
{
    NS_LOG_FUNCTION(this);
    m_frameNo = frameNo;
    m_subframeNo = subframeNo;
    RefreshHarqProcessesPacketBuffer();
    if ((Simulator::Now() >= m_bsrLast + m_bsrPeriodicity) && m_freshUlBsr)
    {
        if (m_componentCarrierId == 0)
        {
            // Send BSR through primary carrier
            SendReportBufferStatus();
        }
        m_bsrLast = Simulator::Now();
        m_freshUlBsr = false;
    }
    m_harqProcessId = (m_harqProcessId + 1) % HARQ_PERIOD;
}

int64_t
LteUeMac::AssignStreams(int64_t stream)
{
    NS_LOG_FUNCTION(this << stream);
    m_raPreambleUniformVariable->SetStream(stream);
    return 1;
}

} // namespace ns3
