/*
 * Copyright (c) 2011, 2012 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
 * Copyright (c) 2018 Fraunhofer ESK : RLF extensions
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Nicola Baldo <nbaldo@cttc.es>
 *         Budiarto Herman <budiarto.herman@magister.fi>
 * Modified by:
 *          Danilo Abrignani <danilo.abrignani@unibo.it> (Carrier Aggregation - GSoC 2015)
 *          Biljana Bojovic <biljana.bojovic@cttc.es> (Carrier Aggregation)
 *          Vignesh Babu <ns3-dev@esk.fraunhofer.de> (RLF extensions)
 */

#include "lte-ue-rrc.h"

#include "lte-common.h"
#include "lte-pdcp.h"
#include "lte-radio-bearer-info.h"
#include "lte-rlc-am.h"
#include "lte-rlc-tm.h"
#include "lte-rlc-um.h"
#include "lte-rlc.h"

#include "ns3/fatal-error.h"
#include "ns3/log.h"
#include "ns3/object-factory.h"
#include "ns3/object-map.h"
#include "ns3/simulator.h"

#include <cmath>

namespace ns3
{
/**
 * @brief Artificial delay of UE measurements procedure.
 *
 * i.e. the period between the time layer-1-filtered measurements from PHY
 * layer is received and the earliest time the actual measurement report
 * submission to the serving cell is invoked.
 *
 * This delay exists because of racing condition between several UE measurements
 * functions which happen to be scheduled at the same time. The delay ensures
 * that:
 *  - measurements (e.g., layer-3 filtering) are always performed before
 *    reporting, thus the latter always use the latest measured RSRP and RSRQ;
 *    and
 *  - time-to-trigger check is always performed before the reporting, so there
 *    would still be chance for it to cancel the reporting if necessary.
 */
const Time UE_MEASUREMENT_REPORT_DELAY = MicroSeconds(1);

NS_LOG_COMPONENT_DEFINE("LteUeRrc");

/////////////////////////////
// CMAC SAP forwarder
/////////////////////////////

/// UeMemberLteUeCmacSapUser class
class UeMemberLteUeCmacSapUser : public LteUeCmacSapUser
{
  public:
    /**
     * Constructor
     *
     * @param rrc the RRC class
     */
    UeMemberLteUeCmacSapUser(LteUeRrc* rrc);

    void SetTemporaryCellRnti(uint16_t rnti) override;
    void NotifyRandomAccessSuccessful() override;
    void NotifyRandomAccessFailed() override;

  private:
    LteUeRrc* m_rrc; ///< the RRC class
};

UeMemberLteUeCmacSapUser::UeMemberLteUeCmacSapUser(LteUeRrc* rrc)
    : m_rrc(rrc)
{
}

void
UeMemberLteUeCmacSapUser::SetTemporaryCellRnti(uint16_t rnti)
{
    m_rrc->DoSetTemporaryCellRnti(rnti);
}

void
UeMemberLteUeCmacSapUser::NotifyRandomAccessSuccessful()
{
    m_rrc->DoNotifyRandomAccessSuccessful();
}

void
UeMemberLteUeCmacSapUser::NotifyRandomAccessFailed()
{
    m_rrc->DoNotifyRandomAccessFailed();
}

/////////////////////////////
// ue RRC methods
/////////////////////////////

NS_OBJECT_ENSURE_REGISTERED(LteUeRrc);

LteUeRrc::LteUeRrc()
    : m_cmacSapProvider(0),
      m_rrcSapUser(nullptr),
      m_macSapProvider(nullptr),
      m_asSapUser(nullptr),
      m_ccmRrcSapProvider(nullptr),
      m_state(IDLE_START),
      m_imsi(0),
      m_rnti(0),
      m_cellId(0),
      m_useRlcSm(true),
      m_connectionPending(false),
      m_hasReceivedMib(false),
      m_hasReceivedSib1(false),
      m_hasReceivedSib2(false),
      m_csgWhiteList(0),
      m_noOfSyncIndications(0),
      m_leaveConnectedMode(false),
      m_previousCellId(0),
      m_connEstFailCountLimit(0),
      m_connEstFailCount(0),
      m_numberOfComponentCarriers(MIN_NO_CC)
{
    NS_LOG_FUNCTION(this);
    m_cphySapUser.push_back(new MemberLteUeCphySapUser<LteUeRrc>(this));
    m_cmacSapUser.push_back(new UeMemberLteUeCmacSapUser(this));
    m_cphySapProvider.push_back(nullptr);
    m_cmacSapProvider.push_back(nullptr);
    m_rrcSapProvider = new MemberLteUeRrcSapProvider<LteUeRrc>(this);
    m_drbPdcpSapUser = new LtePdcpSpecificLtePdcpSapUser<LteUeRrc>(this);
    m_asSapProvider = new MemberLteAsSapProvider<LteUeRrc>(this);
    m_ccmRrcSapUser = new MemberLteUeCcmRrcSapUser<LteUeRrc>(this);
}

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

void
LteUeRrc::DoDispose()
{
    NS_LOG_FUNCTION(this);
    for (uint16_t i = 0; i < m_numberOfComponentCarriers; i++)
    {
        delete m_cphySapUser.at(i);
        delete m_cmacSapUser.at(i);
    }
    m_cphySapUser.clear();
    m_cmacSapUser.clear();
    delete m_rrcSapProvider;
    delete m_drbPdcpSapUser;
    delete m_asSapProvider;
    delete m_ccmRrcSapUser;
    m_cphySapProvider.erase(m_cphySapProvider.begin(), m_cphySapProvider.end());
    m_cphySapProvider.clear();
    m_cmacSapProvider.erase(m_cmacSapProvider.begin(), m_cmacSapProvider.end());
    m_cmacSapProvider.clear();
    m_drbMap.clear();
}

TypeId
LteUeRrc::GetTypeId()
{
    static TypeId tid =
        TypeId("ns3::LteUeRrc")
            .SetParent<Object>()
            .SetGroupName("Lte")
            .AddConstructor<LteUeRrc>()
            .AddAttribute("DataRadioBearerMap",
                          "List of UE RadioBearerInfo for Data Radio Bearers by LCID.",
                          ObjectMapValue(),
                          MakeObjectMapAccessor(&LteUeRrc::m_drbMap),
                          MakeObjectMapChecker<LteDataRadioBearerInfo>())
            .AddAttribute("Srb0",
                          "SignalingRadioBearerInfo for SRB0",
                          PointerValue(),
                          MakePointerAccessor(&LteUeRrc::m_srb0),
                          MakePointerChecker<LteSignalingRadioBearerInfo>())
            .AddAttribute("Srb1",
                          "SignalingRadioBearerInfo for SRB1",
                          PointerValue(),
                          MakePointerAccessor(&LteUeRrc::m_srb1),
                          MakePointerChecker<LteSignalingRadioBearerInfo>())
            .AddAttribute("CellId",
                          "Serving cell identifier",
                          UintegerValue(0), // unused, read-only attribute
                          MakeUintegerAccessor(&LteUeRrc::GetCellId),
                          MakeUintegerChecker<uint16_t>())
            .AddAttribute("C-RNTI",
                          "Cell Radio Network Temporary Identifier",
                          UintegerValue(0), // unused, read-only attribute
                          MakeUintegerAccessor(&LteUeRrc::GetRnti),
                          MakeUintegerChecker<uint16_t>())
            .AddAttribute(
                "T300",
                "Timer for the RRC Connection Establishment procedure "
                "(i.e., the procedure is deemed as failed if it takes longer than this). "
                "Standard values: 100ms, 200ms, 300ms, 400ms, 600ms, 1000ms, 1500ms, 2000ms",
                TimeValue(MilliSeconds(
                    100)), // see 3GPP 36.331 UE-TimersAndConstants & RLF-TimersAndConstants
                MakeTimeAccessor(&LteUeRrc::m_t300),
                MakeTimeChecker(MilliSeconds(100), MilliSeconds(2000)))
            .AddAttribute(
                "T310",
                "Timer for detecting the Radio link failure "
                "(i.e., the radio link is deemed as failed if this timer expires). "
                "Standard values: 0ms 50ms, 100ms, 200ms, 500ms, 1000ms, 2000ms",
                TimeValue(MilliSeconds(
                    1000)), // see 3GPP 36.331 UE-TimersAndConstants & RLF-TimersAndConstants
                MakeTimeAccessor(&LteUeRrc::m_t310),
                MakeTimeChecker(MilliSeconds(0), MilliSeconds(2000)))
            .AddAttribute(
                "N310",
                "This specifies the maximum number of out-of-sync indications. "
                "Standard values: 1, 2, 3, 4, 6, 8, 10, 20",
                UintegerValue(6), // see 3GPP 36.331 UE-TimersAndConstants & RLF-TimersAndConstants
                MakeUintegerAccessor(&LteUeRrc::m_n310),
                MakeUintegerChecker<uint8_t>(1, 20))
            .AddAttribute(
                "N311",
                "This specifies the maximum number of in-sync indications. "
                "Standard values: 1, 2, 3, 4, 5, 6, 8, 10",
                UintegerValue(2), // see 3GPP 36.331 UE-TimersAndConstants & RLF-TimersAndConstants
                MakeUintegerAccessor(&LteUeRrc::m_n311),
                MakeUintegerChecker<uint8_t>(1, 10))
            .AddTraceSource("MibReceived",
                            "trace fired upon reception of Master Information Block",
                            MakeTraceSourceAccessor(&LteUeRrc::m_mibReceivedTrace),
                            "ns3::LteUeRrc::MibSibHandoverTracedCallback")
            .AddTraceSource("Sib1Received",
                            "trace fired upon reception of System Information Block Type 1",
                            MakeTraceSourceAccessor(&LteUeRrc::m_sib1ReceivedTrace),
                            "ns3::LteUeRrc::MibSibHandoverTracedCallback")
            .AddTraceSource("Sib2Received",
                            "trace fired upon reception of System Information Block Type 2",
                            MakeTraceSourceAccessor(&LteUeRrc::m_sib2ReceivedTrace),
                            "ns3::LteUeRrc::ImsiCidRntiTracedCallback")
            .AddTraceSource("StateTransition",
                            "trace fired upon every UE RRC state transition",
                            MakeTraceSourceAccessor(&LteUeRrc::m_stateTransitionTrace),
                            "ns3::LteUeRrc::StateTracedCallback")
            .AddTraceSource("InitialCellSelectionEndOk",
                            "trace fired upon successful initial cell selection procedure",
                            MakeTraceSourceAccessor(&LteUeRrc::m_initialCellSelectionEndOkTrace),
                            "ns3::LteUeRrc::CellSelectionTracedCallback")
            .AddTraceSource("InitialCellSelectionEndError",
                            "trace fired upon failed initial cell selection procedure",
                            MakeTraceSourceAccessor(&LteUeRrc::m_initialCellSelectionEndErrorTrace),
                            "ns3::LteUeRrc::CellSelectionTracedCallback")
            .AddTraceSource("RandomAccessSuccessful",
                            "trace fired upon successful completion of the random access procedure",
                            MakeTraceSourceAccessor(&LteUeRrc::m_randomAccessSuccessfulTrace),
                            "ns3::LteUeRrc::ImsiCidRntiTracedCallback")
            .AddTraceSource("RandomAccessError",
                            "trace fired upon failure of the random access procedure",
                            MakeTraceSourceAccessor(&LteUeRrc::m_randomAccessErrorTrace),
                            "ns3::LteUeRrc::ImsiCidRntiTracedCallback")
            .AddTraceSource("ConnectionEstablished",
                            "trace fired upon successful RRC connection establishment",
                            MakeTraceSourceAccessor(&LteUeRrc::m_connectionEstablishedTrace),
                            "ns3::LteUeRrc::ImsiCidRntiTracedCallback")
            .AddTraceSource("ConnectionTimeout",
                            "trace fired upon timeout RRC connection establishment because of T300",
                            MakeTraceSourceAccessor(&LteUeRrc::m_connectionTimeoutTrace),
                            "ns3::LteUeRrc::ImsiCidRntiCountTracedCallback")
            .AddTraceSource("ConnectionReconfiguration",
                            "trace fired upon RRC connection reconfiguration",
                            MakeTraceSourceAccessor(&LteUeRrc::m_connectionReconfigurationTrace),
                            "ns3::LteUeRrc::ImsiCidRntiTracedCallback")
            .AddTraceSource("HandoverStart",
                            "trace fired upon start of a handover procedure",
                            MakeTraceSourceAccessor(&LteUeRrc::m_handoverStartTrace),
                            "ns3::LteUeRrc::MibSibHandoverTracedCallback")
            .AddTraceSource("HandoverEndOk",
                            "trace fired upon successful termination of a handover procedure",
                            MakeTraceSourceAccessor(&LteUeRrc::m_handoverEndOkTrace),
                            "ns3::LteUeRrc::ImsiCidRntiTracedCallback")
            .AddTraceSource("HandoverEndError",
                            "trace fired upon failure of a handover procedure",
                            MakeTraceSourceAccessor(&LteUeRrc::m_handoverEndErrorTrace),
                            "ns3::LteUeRrc::ImsiCidRntiTracedCallback")
            .AddTraceSource("SCarrierConfigured",
                            "trace fired after configuring secondary carriers",
                            MakeTraceSourceAccessor(&LteUeRrc::m_sCarrierConfiguredTrace),
                            "ns3::LteUeRrc::SCarrierConfiguredTracedCallback")
            .AddTraceSource("Srb1Created",
                            "trace fired after SRB1 is created",
                            MakeTraceSourceAccessor(&LteUeRrc::m_srb1CreatedTrace),
                            "ns3::LteUeRrc::ImsiCidRntiTracedCallback")
            .AddTraceSource("DrbCreated",
                            "trace fired after DRB is created",
                            MakeTraceSourceAccessor(&LteUeRrc::m_drbCreatedTrace),
                            "ns3::LteUeRrc::ImsiCidRntiLcIdTracedCallback")
            .AddTraceSource("RadioLinkFailure",
                            "trace fired upon failure of radio link",
                            MakeTraceSourceAccessor(&LteUeRrc::m_radioLinkFailureTrace),
                            "ns3::LteUeRrc::ImsiCidRntiTracedCallback")
            .AddTraceSource(
                "PhySyncDetection",
                "trace fired upon receiving in Sync or out of Sync indications from UE PHY",
                MakeTraceSourceAccessor(&LteUeRrc::m_phySyncDetectionTrace),
                "ns3::LteUeRrc::PhySyncDetectionTracedCallback");
    return tid;
}

void
LteUeRrc::SetLteUeCphySapProvider(LteUeCphySapProvider* s)
{
    NS_LOG_FUNCTION(this << s);
    m_cphySapProvider.at(0) = s;
}

void
LteUeRrc::SetLteUeCphySapProvider(LteUeCphySapProvider* s, uint8_t index)
{
    NS_LOG_FUNCTION(this << s);
    m_cphySapProvider.at(index) = s;
}

LteUeCphySapUser*
LteUeRrc::GetLteUeCphySapUser()
{
    NS_LOG_FUNCTION(this);
    return m_cphySapUser.at(0);
}

LteUeCphySapUser*
LteUeRrc::GetLteUeCphySapUser(uint8_t index)
{
    NS_LOG_FUNCTION(this);
    return m_cphySapUser.at(index);
}

void
LteUeRrc::SetLteUeCmacSapProvider(LteUeCmacSapProvider* s)
{
    NS_LOG_FUNCTION(this << s);
    m_cmacSapProvider.at(0) = s;
}

void
LteUeRrc::SetLteUeCmacSapProvider(LteUeCmacSapProvider* s, uint8_t index)
{
    NS_LOG_FUNCTION(this << s);
    m_cmacSapProvider.at(index) = s;
}

LteUeCmacSapUser*
LteUeRrc::GetLteUeCmacSapUser()
{
    NS_LOG_FUNCTION(this);
    return m_cmacSapUser.at(0);
}

LteUeCmacSapUser*
LteUeRrc::GetLteUeCmacSapUser(uint8_t index)
{
    NS_LOG_FUNCTION(this);
    return m_cmacSapUser.at(index);
}

void
LteUeRrc::SetLteUeRrcSapUser(LteUeRrcSapUser* s)
{
    NS_LOG_FUNCTION(this << s);
    m_rrcSapUser = s;
}

LteUeRrcSapProvider*
LteUeRrc::GetLteUeRrcSapProvider()
{
    NS_LOG_FUNCTION(this);
    return m_rrcSapProvider;
}

void
LteUeRrc::SetLteMacSapProvider(LteMacSapProvider* s)
{
    NS_LOG_FUNCTION(this << s);
    m_macSapProvider = s;
}

void
LteUeRrc::SetLteCcmRrcSapProvider(LteUeCcmRrcSapProvider* s)
{
    NS_LOG_FUNCTION(this << s);
    m_ccmRrcSapProvider = s;
}

LteUeCcmRrcSapUser*
LteUeRrc::GetLteCcmRrcSapUser()
{
    NS_LOG_FUNCTION(this);
    return m_ccmRrcSapUser;
}

void
LteUeRrc::SetAsSapUser(LteAsSapUser* s)
{
    m_asSapUser = s;
}

LteAsSapProvider*
LteUeRrc::GetAsSapProvider()
{
    return m_asSapProvider;
}

void
LteUeRrc::SetImsi(uint64_t imsi)
{
    NS_LOG_FUNCTION(this << imsi);
    m_imsi = imsi;

    // Communicate the IMSI to MACs and PHYs for all the component carriers
    for (uint16_t i = 0; i < m_numberOfComponentCarriers; i++)
    {
        m_cmacSapProvider.at(i)->SetImsi(m_imsi);
        m_cphySapProvider.at(i)->SetImsi(m_imsi);
    }
}

void
LteUeRrc::StorePreviousCellId(uint16_t cellId)
{
    NS_LOG_FUNCTION(this << cellId);
    m_previousCellId = cellId;
}

uint64_t
LteUeRrc::GetImsi() const
{
    return m_imsi;
}

uint16_t
LteUeRrc::GetRnti() const
{
    NS_LOG_FUNCTION(this);
    return m_rnti;
}

uint16_t
LteUeRrc::GetCellId() const
{
    NS_LOG_FUNCTION(this);
    return m_cellId;
}

bool
LteUeRrc::IsServingCell(uint16_t cellId) const
{
    NS_LOG_FUNCTION(this);
    for (auto& cphySap : m_cphySapProvider)
    {
        if (cellId == cphySap->GetCellId())
        {
            return true;
        }
    }
    return false;
}

uint8_t
LteUeRrc::GetUlBandwidth() const
{
    NS_LOG_FUNCTION(this);
    return m_ulBandwidth;
}

uint8_t
LteUeRrc::GetDlBandwidth() const
{
    NS_LOG_FUNCTION(this);
    return m_dlBandwidth;
}

uint32_t
LteUeRrc::GetDlEarfcn() const
{
    return m_dlEarfcn;
}

uint32_t
LteUeRrc::GetUlEarfcn() const
{
    NS_LOG_FUNCTION(this);
    return m_ulEarfcn;
}

LteUeRrc::State
LteUeRrc::GetState() const
{
    NS_LOG_FUNCTION(this);
    return m_state;
}

uint16_t
LteUeRrc::GetPreviousCellId() const
{
    NS_LOG_FUNCTION(this);
    return m_previousCellId;
}

void
LteUeRrc::SetUseRlcSm(bool val)
{
    NS_LOG_FUNCTION(this);
    m_useRlcSm = val;
}

void
LteUeRrc::DoInitialize()
{
    NS_LOG_FUNCTION(this);

    // setup the UE side of SRB0
    uint8_t lcid = 0;

    Ptr<LteRlc> rlc = CreateObject<LteRlcTm>()->GetObject<LteRlc>();
    rlc->SetLteMacSapProvider(m_macSapProvider);
    rlc->SetRnti(m_rnti);
    rlc->SetLcId(lcid);

    m_srb0 = CreateObject<LteSignalingRadioBearerInfo>();
    m_srb0->m_rlc = rlc;
    m_srb0->m_srbIdentity = 0;
    LteUeRrcSapUser::SetupParameters ueParams;
    ueParams.srb0SapProvider = m_srb0->m_rlc->GetLteRlcSapProvider();
    ueParams.srb1SapProvider = nullptr;
    m_rrcSapUser->Setup(ueParams);

    // CCCH (LCID 0) is pre-configured, here is the hardcoded configuration:
    LteUeCmacSapProvider::LogicalChannelConfig lcConfig;
    lcConfig.priority = 0;                   // highest priority
    lcConfig.prioritizedBitRateKbps = 65535; // maximum
    lcConfig.bucketSizeDurationMs = 65535;   // maximum
    lcConfig.logicalChannelGroup = 0;        // all SRBs mapped to LCG 0
    LteMacSapUser* msu =
        m_ccmRrcSapProvider->ConfigureSignalBearer(lcid, lcConfig, rlc->GetLteMacSapUser());
    m_cmacSapProvider.at(0)->AddLc(lcid, lcConfig, msu);
}

void
LteUeRrc::InitializeSap()
{
    if (m_numberOfComponentCarriers < MIN_NO_CC || m_numberOfComponentCarriers > MAX_NO_CC)
    {
        // this check is needed in order to maintain backward compatibility with scripts and tests
        // if case lte-helper is not used (like in several tests) the m_numberOfComponentCarriers
        // is not set and then an error is raised
        // In this case m_numberOfComponentCarriers is set to 1
        m_numberOfComponentCarriers = MIN_NO_CC;
    }
    if (m_numberOfComponentCarriers > MIN_NO_CC)
    {
        for (uint16_t i = 1; i < m_numberOfComponentCarriers; i++)
        {
            m_cphySapUser.push_back(new MemberLteUeCphySapUser<LteUeRrc>(this));
            m_cmacSapUser.push_back(new UeMemberLteUeCmacSapUser(this));
            m_cphySapProvider.push_back(nullptr);
            m_cmacSapProvider.push_back(nullptr);
        }
    }
}

void
LteUeRrc::DoSendData(Ptr<Packet> packet, uint8_t bid)
{
    NS_LOG_FUNCTION(this << packet);

    uint8_t drbid = Bid2Drbid(bid);

    if (drbid != 0)
    {
        auto it = m_drbMap.find(drbid);
        NS_ASSERT_MSG(it != m_drbMap.end(), "could not find bearer with drbid == " << drbid);

        LtePdcpSapProvider::TransmitPdcpSduParameters params;
        params.pdcpSdu = packet;
        params.rnti = m_rnti;
        params.lcid = it->second->m_logicalChannelIdentity;

        NS_LOG_LOGIC(this << " RNTI=" << m_rnti << " sending packet " << packet << " on DRBID "
                          << (uint32_t)drbid << " (LCID " << (uint32_t)params.lcid << ")"
                          << " (" << packet->GetSize() << " bytes)");
        it->second->m_pdcp->GetLtePdcpSapProvider()->TransmitPdcpSdu(params);
    }
}

void
LteUeRrc::DoDisconnect()
{
    NS_LOG_FUNCTION(this);

    switch (m_state)
    {
    case IDLE_START:
    case IDLE_CELL_SEARCH:
    case IDLE_WAIT_MIB_SIB1:
    case IDLE_WAIT_MIB:
    case IDLE_WAIT_SIB1:
    case IDLE_CAMPED_NORMALLY:
        NS_LOG_INFO("already disconnected");
        break;

    case IDLE_WAIT_SIB2:
    case IDLE_CONNECTING:
        NS_FATAL_ERROR("cannot abort connection setup procedure");
        break;

    case CONNECTED_NORMALLY:
    case CONNECTED_HANDOVER:
    case CONNECTED_PHY_PROBLEM:
    case CONNECTED_REESTABLISHING:
        LeaveConnectedMode();
        break;

    default: // i.e. IDLE_RANDOM_ACCESS
        NS_FATAL_ERROR("method unexpected in state " << m_state);
        break;
    }
}

void
LteUeRrc::DoReceivePdcpSdu(LtePdcpSapUser::ReceivePdcpSduParameters params)
{
    NS_LOG_FUNCTION(this);
    m_asSapUser->RecvData(params.pdcpSdu);
}

void
LteUeRrc::DoSetTemporaryCellRnti(uint16_t rnti)
{
    NS_LOG_FUNCTION(this << rnti);
    m_rnti = rnti;
    m_srb0->m_rlc->SetRnti(m_rnti);
    m_cphySapProvider.at(0)->SetRnti(m_rnti);
}

void
LteUeRrc::DoNotifyRandomAccessSuccessful()
{
    NS_LOG_FUNCTION(this << m_imsi << m_state);
    m_randomAccessSuccessfulTrace(m_imsi, m_cellId, m_rnti);

    switch (m_state)
    {
    case IDLE_RANDOM_ACCESS: {
        // we just received a RAR with a T-C-RNTI and an UL grant
        // send RRC connection request as message 3 of the random access procedure
        SwitchToState(IDLE_CONNECTING);
        LteRrcSap::RrcConnectionRequest msg;
        msg.ueIdentity = m_imsi;
        m_rrcSapUser->SendRrcConnectionRequest(msg);
        m_connectionTimeout = Simulator::Schedule(m_t300, &LteUeRrc::ConnectionTimeout, this);
    }
    break;

    case CONNECTED_HANDOVER: {
        LteRrcSap::RrcConnectionReconfigurationCompleted msg;
        msg.rrcTransactionIdentifier = m_lastRrcTransactionIdentifier;
        m_rrcSapUser->SendRrcConnectionReconfigurationCompleted(msg);

        // 3GPP TS 36.331 section 5.5.6.1 Measurements related actions upon handover
        for (auto measIdIt = m_varMeasConfig.measIdList.begin();
             measIdIt != m_varMeasConfig.measIdList.end();
             ++measIdIt)
        {
            VarMeasReportListClear(measIdIt->second.measId);
        }

        SwitchToState(CONNECTED_NORMALLY);
        m_cmacSapProvider.at(0)->NotifyConnectionSuccessful(); // RA successful during handover
        m_handoverEndOkTrace(m_imsi, m_cellId, m_rnti);
    }
    break;

    default:
        NS_FATAL_ERROR("unexpected event in state " << m_state);
        break;
    }
}

void
LteUeRrc::DoNotifyRandomAccessFailed()
{
    NS_LOG_FUNCTION(this << m_imsi << m_state);
    m_randomAccessErrorTrace(m_imsi, m_cellId, m_rnti);

    switch (m_state)
    {
    case IDLE_RANDOM_ACCESS: {
        SwitchToState(IDLE_CAMPED_NORMALLY);
        m_asSapUser->NotifyConnectionFailed();
    }
    break;

    case CONNECTED_HANDOVER: {
        m_handoverEndErrorTrace(m_imsi, m_cellId, m_rnti);
        /**
         * @todo After a handover failure because of a random access failure,
         *       send an RRC Connection Re-establishment and switch to
         *       CONNECTED_REESTABLISHING state.
         */
        if (!m_leaveConnectedMode)
        {
            m_leaveConnectedMode = true;
            SwitchToState(CONNECTED_PHY_PROBLEM);
            m_rrcSapUser->SendIdealUeContextRemoveRequest(m_rnti);
            // we should have called NotifyConnectionFailed
            // but that method would immediately ask you UE to
            // connect rather than doing cell selection again.
            m_asSapUser->NotifyConnectionReleased();
        }
    }
    break;

    default:
        NS_FATAL_ERROR("unexpected event in state " << m_state);
        break;
    }
}

void
LteUeRrc::DoSetCsgWhiteList(uint32_t csgId)
{
    NS_LOG_FUNCTION(this << m_imsi << csgId);
    m_csgWhiteList = csgId;
}

void
LteUeRrc::DoStartCellSelection(uint32_t dlEarfcn)
{
    NS_LOG_FUNCTION(this << m_imsi << dlEarfcn);
    NS_ASSERT_MSG(m_state == IDLE_START, "cannot start cell selection from state " << m_state);
    m_dlEarfcn = dlEarfcn;
    m_cphySapProvider.at(0)->StartCellSearch(dlEarfcn);
    SwitchToState(IDLE_CELL_SEARCH);
}

void
LteUeRrc::DoForceCampedOnEnb(uint16_t cellId, uint32_t dlEarfcn)
{
    NS_LOG_FUNCTION(this << m_imsi << cellId << dlEarfcn);

    switch (m_state)
    {
    case IDLE_START:
        m_cellId = cellId;
        m_dlEarfcn = dlEarfcn;
        m_cphySapProvider.at(0)->SynchronizeWithEnb(m_cellId, m_dlEarfcn);
        SwitchToState(IDLE_WAIT_MIB);
        break;

    case IDLE_CELL_SEARCH:
    case IDLE_WAIT_MIB_SIB1:
    case IDLE_WAIT_SIB1:
        NS_FATAL_ERROR("cannot abort cell selection " << m_state);
        break;

    case IDLE_WAIT_MIB:
        NS_LOG_INFO("already forced to camp to cell " << m_cellId);
        break;

    case IDLE_CAMPED_NORMALLY:
    case IDLE_WAIT_SIB2:
    case IDLE_RANDOM_ACCESS:
    case IDLE_CONNECTING:
        NS_LOG_INFO("already camped to cell " << m_cellId);
        break;

    case CONNECTED_NORMALLY:
    case CONNECTED_HANDOVER:
    case CONNECTED_PHY_PROBLEM:
    case CONNECTED_REESTABLISHING:
        NS_LOG_INFO("already connected to cell " << m_cellId);
        break;

    default:
        NS_FATAL_ERROR("unexpected event in state " << m_state);
        break;
    }
}

void
LteUeRrc::DoConnect()
{
    NS_LOG_FUNCTION(this << m_imsi);

    switch (m_state)
    {
    case IDLE_START:
    case IDLE_CELL_SEARCH:
    case IDLE_WAIT_MIB_SIB1:
    case IDLE_WAIT_SIB1:
    case IDLE_WAIT_MIB:
        m_connectionPending = true;
        break;

    case IDLE_CAMPED_NORMALLY:
        m_connectionPending = true;
        SwitchToState(IDLE_WAIT_SIB2);
        break;

    case IDLE_WAIT_SIB2:
    case IDLE_RANDOM_ACCESS:
    case IDLE_CONNECTING:
        NS_LOG_INFO("already connecting");
        break;

    case CONNECTED_NORMALLY:
    case CONNECTED_REESTABLISHING:
    case CONNECTED_HANDOVER:
        NS_LOG_INFO("already connected");
        break;

    default:
        NS_FATAL_ERROR("unexpected event in state " << m_state);
        break;
    }
}

// CPHY SAP methods

void
LteUeRrc::DoRecvMasterInformationBlock(uint16_t cellId, LteRrcSap::MasterInformationBlock msg)
{
    m_dlBandwidth = msg.dlBandwidth;
    m_cphySapProvider.at(0)->SetDlBandwidth(msg.dlBandwidth);
    m_hasReceivedMib = true;
    m_mibReceivedTrace(m_imsi, m_cellId, m_rnti, cellId);

    switch (m_state)
    {
    case IDLE_WAIT_MIB:
        // manual attachment
        SwitchToState(IDLE_CAMPED_NORMALLY);
        break;

    case IDLE_WAIT_MIB_SIB1:
        // automatic attachment from Idle mode cell selection
        SwitchToState(IDLE_WAIT_SIB1);
        break;

    default:
        // do nothing extra
        break;
    }
}

void
LteUeRrc::DoRecvSystemInformationBlockType1(uint16_t cellId,
                                            LteRrcSap::SystemInformationBlockType1 msg)
{
    NS_LOG_FUNCTION(this);
    switch (m_state)
    {
    case IDLE_WAIT_SIB1:
        NS_ASSERT_MSG(cellId == msg.cellAccessRelatedInfo.cellIdentity,
                      "Cell identity in SIB1 does not match with the originating cell");
        m_hasReceivedSib1 = true;
        m_lastSib1 = msg;
        m_sib1ReceivedTrace(m_imsi, m_cellId, m_rnti, cellId);
        EvaluateCellForSelection();
        break;

    case IDLE_CAMPED_NORMALLY:
    case IDLE_RANDOM_ACCESS:
    case IDLE_CONNECTING:
    case CONNECTED_NORMALLY:
    case CONNECTED_HANDOVER:
    case CONNECTED_PHY_PROBLEM:
    case CONNECTED_REESTABLISHING:
        NS_ASSERT_MSG(cellId == msg.cellAccessRelatedInfo.cellIdentity,
                      "Cell identity in SIB1 does not match with the originating cell");
        m_hasReceivedSib1 = true;
        m_lastSib1 = msg;
        m_sib1ReceivedTrace(m_imsi, m_cellId, m_rnti, cellId);
        break;

    case IDLE_WAIT_MIB_SIB1:
        // MIB has not been received, so ignore this SIB1

    default: // e.g. IDLE_START, IDLE_CELL_SEARCH, IDLE_WAIT_MIB, IDLE_WAIT_SIB2
        // do nothing
        break;
    }
}

void
LteUeRrc::DoReportUeMeasurements(LteUeCphySapUser::UeMeasurementsParameters params)
{
    NS_LOG_FUNCTION(this);

    // layer 3 filtering does not apply in IDLE mode
    bool useLayer3Filtering = (m_state == CONNECTED_NORMALLY);
    bool triggering = true;
    for (auto newMeasIt = params.m_ueMeasurementsList.begin();
         newMeasIt != params.m_ueMeasurementsList.end();
         ++newMeasIt)
    {
        if (params.m_componentCarrierId != 0)
        {
            triggering = false; // report is triggered only when an event is on the primary carrier
            // in this case the measurement received is related to secondary carriers
        }
        SaveUeMeasurements(newMeasIt->m_cellId,
                           newMeasIt->m_rsrp,
                           newMeasIt->m_rsrq,
                           useLayer3Filtering,
                           params.m_componentCarrierId);
    }

    if (m_state == IDLE_CELL_SEARCH)
    {
        // start decoding BCH
        SynchronizeToStrongestCell();
    }
    else
    {
        if (triggering)
        {
            for (auto measIdIt = m_varMeasConfig.measIdList.begin();
                 measIdIt != m_varMeasConfig.measIdList.end();
                 ++measIdIt)
            {
                MeasurementReportTriggering(measIdIt->first);
            }
        }
    }
}

// RRC SAP methods

void
LteUeRrc::DoCompleteSetup(LteUeRrcSapProvider::CompleteSetupParameters params)
{
    NS_LOG_FUNCTION(this << " RNTI " << m_rnti);
    m_srb0->m_rlc->SetLteRlcSapUser(params.srb0SapUser);
    if (m_srb1)
    {
        m_srb1->m_pdcp->SetLtePdcpSapUser(params.srb1SapUser);
    }
}

void
LteUeRrc::DoRecvSystemInformation(LteRrcSap::SystemInformation msg)
{
    NS_LOG_FUNCTION(this << " RNTI " << m_rnti);

    if (msg.haveSib2)
    {
        switch (m_state)
        {
        case IDLE_CAMPED_NORMALLY:
        case IDLE_WAIT_SIB2:
        case IDLE_RANDOM_ACCESS:
        case IDLE_CONNECTING:
        case CONNECTED_NORMALLY:
        case CONNECTED_HANDOVER:
        case CONNECTED_PHY_PROBLEM:
        case CONNECTED_REESTABLISHING:
            m_hasReceivedSib2 = true;
            m_ulBandwidth = msg.sib2.freqInfo.ulBandwidth;
            m_ulEarfcn = msg.sib2.freqInfo.ulCarrierFreq;
            m_sib2ReceivedTrace(m_imsi, m_cellId, m_rnti);
            LteUeCmacSapProvider::RachConfig rc;
            rc.numberOfRaPreambles = msg.sib2.radioResourceConfigCommon.rachConfigCommon
                                         .preambleInfo.numberOfRaPreambles;
            rc.preambleTransMax = msg.sib2.radioResourceConfigCommon.rachConfigCommon
                                      .raSupervisionInfo.preambleTransMax;
            rc.raResponseWindowSize = msg.sib2.radioResourceConfigCommon.rachConfigCommon
                                          .raSupervisionInfo.raResponseWindowSize;
            rc.connEstFailCount =
                msg.sib2.radioResourceConfigCommon.rachConfigCommon.txFailParam.connEstFailCount;
            m_connEstFailCountLimit = rc.connEstFailCount;
            NS_ASSERT_MSG(m_connEstFailCountLimit > 0 && m_connEstFailCountLimit < 5,
                          "SIB2 msg contains wrong value " << m_connEstFailCountLimit
                                                           << "of connEstFailCount");
            m_cmacSapProvider.at(0)->ConfigureRach(rc);
            m_cphySapProvider.at(0)->ConfigureUplink(m_ulEarfcn, m_ulBandwidth);
            m_cphySapProvider.at(0)->ConfigureReferenceSignalPower(
                msg.sib2.radioResourceConfigCommon.pdschConfigCommon.referenceSignalPower);
            if (m_state == IDLE_WAIT_SIB2)
            {
                NS_ASSERT(m_connectionPending);
                StartConnection();
            }
            break;

        default: // IDLE_START, IDLE_CELL_SEARCH, IDLE_WAIT_MIB, IDLE_WAIT_MIB_SIB1, IDLE_WAIT_SIB1
            // do nothing
            break;
        }
    }
}

void
LteUeRrc::DoRecvRrcConnectionSetup(LteRrcSap::RrcConnectionSetup msg)
{
    NS_LOG_FUNCTION(this << " RNTI " << m_rnti);
    switch (m_state)
    {
    case IDLE_CONNECTING: {
        ApplyRadioResourceConfigDedicated(msg.radioResourceConfigDedicated);
        m_connEstFailCount = 0;
        m_connectionTimeout.Cancel();
        SwitchToState(CONNECTED_NORMALLY);
        m_leaveConnectedMode = false;
        LteRrcSap::RrcConnectionSetupCompleted msg2;
        msg2.rrcTransactionIdentifier = msg.rrcTransactionIdentifier;
        m_rrcSapUser->SendRrcConnectionSetupCompleted(msg2);
        m_asSapUser->NotifyConnectionSuccessful();
        m_cmacSapProvider.at(0)->NotifyConnectionSuccessful();
        m_connectionEstablishedTrace(m_imsi, m_cellId, m_rnti);
        NS_ABORT_MSG_IF(m_noOfSyncIndications > 0,
                        "Sync indications should be zero "
                        "when a new RRC connection is established. Current value = "
                            << (uint16_t)m_noOfSyncIndications);
    }
    break;

    default:
        NS_FATAL_ERROR("method unexpected in state " << m_state);
        break;
    }
}

void
LteUeRrc::DoRecvRrcConnectionReconfiguration(LteRrcSap::RrcConnectionReconfiguration msg)
{
    NS_LOG_FUNCTION(this << " RNTI " << m_rnti);
    NS_LOG_INFO("DoRecvRrcConnectionReconfiguration haveNonCriticalExtension:"
                << msg.haveNonCriticalExtension);
    switch (m_state)
    {
    case CONNECTED_NORMALLY:
        if (msg.haveMobilityControlInfo)
        {
            NS_LOG_INFO("haveMobilityControlInfo == true");
            SwitchToState(CONNECTED_HANDOVER);
            if (m_radioLinkFailureDetected.IsPending())
            {
                ResetRlfParams();
            }
            const LteRrcSap::MobilityControlInfo& mci = msg.mobilityControlInfo;
            m_handoverStartTrace(m_imsi, m_cellId, m_rnti, mci.targetPhysCellId);
            // We should reset the MACs and PHYs for all the component carriers
            for (auto cmacSapProvider : m_cmacSapProvider)
            {
                cmacSapProvider->Reset();
            }
            for (auto cphySapProvider : m_cphySapProvider)
            {
                cphySapProvider->Reset();
            }
            m_ccmRrcSapProvider->Reset();
            StorePreviousCellId(m_cellId);
            m_cellId = mci.targetPhysCellId;
            NS_ASSERT(mci.haveCarrierFreq);
            NS_ASSERT(mci.haveCarrierBandwidth);
            m_cphySapProvider.at(0)->SynchronizeWithEnb(m_cellId, mci.carrierFreq.dlCarrierFreq);
            m_cphySapProvider.at(0)->SetDlBandwidth(mci.carrierBandwidth.dlBandwidth);
            m_cphySapProvider.at(0)->ConfigureUplink(mci.carrierFreq.ulCarrierFreq,
                                                     mci.carrierBandwidth.ulBandwidth);
            m_rnti = msg.mobilityControlInfo.newUeIdentity;
            m_srb0->m_rlc->SetRnti(m_rnti);
            NS_ASSERT_MSG(
                mci.haveRachConfigDedicated,
                "handover is only supported with non-contention-based random access procedure");
            m_cmacSapProvider.at(0)->StartNonContentionBasedRandomAccessProcedure(
                m_rnti,
                mci.rachConfigDedicated.raPreambleIndex,
                mci.rachConfigDedicated.raPrachMaskIndex);
            m_cphySapProvider.at(0)->SetRnti(m_rnti);
            m_lastRrcTransactionIdentifier = msg.rrcTransactionIdentifier;
            NS_ASSERT(msg.haveRadioResourceConfigDedicated);

            // we re-establish SRB1 by creating a new entity
            // note that we can't dispose the old entity now, because
            // it's in the current stack, so we would corrupt the stack
            // if we did so. Hence we schedule it for later disposal
            m_srb1Old = m_srb1;
            Simulator::ScheduleNow(&LteUeRrc::DisposeOldSrb1, this);
            m_srb1 =
                nullptr; // new instance will be be created within ApplyRadioResourceConfigDedicated

            m_drbMap.clear(); // dispose all DRBs
            ApplyRadioResourceConfigDedicated(msg.radioResourceConfigDedicated);
            if (msg.haveNonCriticalExtension)
            {
                NS_LOG_DEBUG(this << "RNTI " << m_rnti
                                  << " Handover. Configuring secondary carriers");
                ApplyRadioResourceConfigDedicatedSecondaryCarrier(msg.nonCriticalExtension);
            }

            if (msg.haveMeasConfig)
            {
                ApplyMeasConfig(msg.measConfig);
            }
            // RRC connection reconfiguration completed will be sent
            // after handover is complete
        }
        else
        {
            NS_LOG_INFO("haveMobilityControlInfo == false");
            if (msg.haveNonCriticalExtension)
            {
                ApplyRadioResourceConfigDedicatedSecondaryCarrier(msg.nonCriticalExtension);
                NS_LOG_DEBUG(this << "RNTI " << m_rnti << " Configured for CA");
            }
            if (msg.haveRadioResourceConfigDedicated)
            {
                ApplyRadioResourceConfigDedicated(msg.radioResourceConfigDedicated);
            }
            if (msg.haveMeasConfig)
            {
                ApplyMeasConfig(msg.measConfig);
            }
            LteRrcSap::RrcConnectionReconfigurationCompleted msg2;
            msg2.rrcTransactionIdentifier = msg.rrcTransactionIdentifier;
            m_rrcSapUser->SendRrcConnectionReconfigurationCompleted(msg2);
            m_connectionReconfigurationTrace(m_imsi, m_cellId, m_rnti);
        }
        break;

    default:
        NS_FATAL_ERROR("method unexpected in state " << m_state);
        break;
    }
}

void
LteUeRrc::DoRecvRrcConnectionReestablishment(LteRrcSap::RrcConnectionReestablishment msg)
{
    NS_LOG_FUNCTION(this << " RNTI " << m_rnti);
    switch (m_state)
    {
    case CONNECTED_REESTABLISHING: {
        /**
         * @todo After receiving RRC Connection Re-establishment, stop timer
         *       T301, fire a new trace source, reply with RRC Connection
         *       Re-establishment Complete, and finally switch to
         *       CONNECTED_NORMALLY state. See Section 5.3.7.5 of 3GPP TS
         *       36.331.
         */
    }
    break;

    default:
        NS_FATAL_ERROR("method unexpected in state " << m_state);
        break;
    }
}

void
LteUeRrc::DoRecvRrcConnectionReestablishmentReject(
    LteRrcSap::RrcConnectionReestablishmentReject msg)
{
    NS_LOG_FUNCTION(this << " RNTI " << m_rnti);
    switch (m_state)
    {
    case CONNECTED_REESTABLISHING: {
        /**
         * @todo After receiving RRC Connection Re-establishment Reject, stop
         *       timer T301. See Section 5.3.7.8 of 3GPP TS 36.331.
         */
        m_asSapUser->NotifyConnectionReleased(); // Inform upper layers
    }
    break;

    default:
        NS_FATAL_ERROR("method unexpected in state " << m_state);
        break;
    }
}

void
LteUeRrc::DoRecvRrcConnectionRelease(LteRrcSap::RrcConnectionRelease msg)
{
    NS_LOG_FUNCTION(this << " RNTI " << m_rnti);
    /// @todo Currently not implemented, see Section 5.3.8 of 3GPP TS 36.331.

    m_lastRrcTransactionIdentifier = msg.rrcTransactionIdentifier;
    // release resources at UE
    if (!m_leaveConnectedMode)
    {
        m_leaveConnectedMode = true;
        SwitchToState(CONNECTED_PHY_PROBLEM);
        m_rrcSapUser->SendIdealUeContextRemoveRequest(m_rnti);
        m_asSapUser->NotifyConnectionReleased();
    }
}

void
LteUeRrc::DoRecvRrcConnectionReject(LteRrcSap::RrcConnectionReject msg)
{
    NS_LOG_FUNCTION(this);
    m_connectionTimeout.Cancel();
    for (uint16_t i = 0; i < m_numberOfComponentCarriers; i++)
    {
        m_cmacSapProvider.at(i)->Reset(); // reset the MAC
    }
    m_hasReceivedSib2 = false; // invalidate the previously received SIB2
    SwitchToState(IDLE_CAMPED_NORMALLY);
    m_asSapUser->NotifyConnectionFailed(); // inform upper layer
}

void
LteUeRrc::DoSetNumberOfComponentCarriers(uint16_t noOfComponentCarriers)
{
    NS_LOG_FUNCTION(this);
    m_numberOfComponentCarriers = noOfComponentCarriers;
}

void
LteUeRrc::SynchronizeToStrongestCell()
{
    NS_LOG_FUNCTION(this);
    NS_ASSERT(m_state == IDLE_CELL_SEARCH);

    uint16_t maxRsrpCellId = 0;
    double maxRsrp = -std::numeric_limits<double>::infinity();
    double minRsrp = -140.0; // Minimum RSRP in dBm a UE can report

    for (auto it = m_storedMeasValues.begin(); it != m_storedMeasValues.end(); it++)
    {
        /*
         * This block attempts to find a cell with strongest RSRP and has not
         * yet been identified as "acceptable cell".
         */
        if (maxRsrp < it->second.rsrp && it->second.rsrp > minRsrp)
        {
            auto itCell = m_acceptableCell.find(it->first);
            if (itCell == m_acceptableCell.end())
            {
                maxRsrpCellId = it->first;
                maxRsrp = it->second.rsrp;
            }
        }
    }

    if (maxRsrpCellId == 0)
    {
        NS_LOG_WARN(this << " Cell search is unable to detect surrounding cell to attach to");
    }
    else
    {
        NS_LOG_LOGIC(this << " cell " << maxRsrpCellId
                          << " is the strongest untried surrounding cell");
        m_cphySapProvider.at(0)->SynchronizeWithEnb(maxRsrpCellId, m_dlEarfcn);
        SwitchToState(IDLE_WAIT_MIB_SIB1);
    }
}

void
LteUeRrc::EvaluateCellForSelection()
{
    NS_LOG_FUNCTION(this);
    NS_ASSERT(m_state == IDLE_WAIT_SIB1);
    NS_ASSERT(m_hasReceivedMib);
    NS_ASSERT(m_hasReceivedSib1);
    uint16_t cellId = m_lastSib1.cellAccessRelatedInfo.cellIdentity;

    // Cell selection criteria evaluation

    bool isSuitableCell = false;
    bool isAcceptableCell = false;
    auto storedMeasIt = m_storedMeasValues.find(cellId);
    double qRxLevMeas = storedMeasIt->second.rsrp;
    double qRxLevMin =
        EutranMeasurementMapping::IeValue2ActualQRxLevMin(m_lastSib1.cellSelectionInfo.qRxLevMin);
    NS_LOG_LOGIC(this << " cell selection to cellId=" << cellId << " qrxlevmeas=" << qRxLevMeas
                      << " dBm"
                      << " qrxlevmin=" << qRxLevMin << " dBm");

    if (qRxLevMeas - qRxLevMin > 0)
    {
        isAcceptableCell = true;

        uint32_t cellCsgId = m_lastSib1.cellAccessRelatedInfo.csgIdentity;
        bool cellCsgIndication = m_lastSib1.cellAccessRelatedInfo.csgIndication;

        isSuitableCell = (!cellCsgIndication || cellCsgId == m_csgWhiteList);

        NS_LOG_LOGIC(this << " csg(ue/cell/indication)=" << m_csgWhiteList << "/" << cellCsgId
                          << "/" << cellCsgIndication);
    }

    // Cell selection decision

    if (isSuitableCell)
    {
        m_cellId = cellId;
        m_cphySapProvider.at(0)->SynchronizeWithEnb(cellId, m_dlEarfcn);
        m_cphySapProvider.at(0)->SetDlBandwidth(m_dlBandwidth);
        m_initialCellSelectionEndOkTrace(m_imsi, cellId);
        // Once the UE is connected, m_connectionPending is
        // set to false. So, when RLF occurs and UE performs
        // cell selection upon leaving RRC_CONNECTED state,
        // the following call to DoConnect will make the
        // m_connectionPending to be true again. Thus,
        // upon calling SwitchToState (IDLE_CAMPED_NORMALLY)
        // UE state is instantly change to IDLE_WAIT_SIB2.
        // This will make the UE to read the SIB2 message
        // and start random access.
        if (!m_connectionPending)
        {
            NS_LOG_DEBUG("Calling DoConnect in state = " << m_state);
            DoConnect();
        }
        SwitchToState(IDLE_CAMPED_NORMALLY);
    }
    else
    {
        // ignore the MIB and SIB1 received from this cell
        m_hasReceivedMib = false;
        m_hasReceivedSib1 = false;

        m_initialCellSelectionEndErrorTrace(m_imsi, cellId);

        if (isAcceptableCell)
        {
            /*
             * The cells inserted into this list will not be considered for
             * subsequent cell search attempt.
             */
            m_acceptableCell.insert(cellId);
        }

        SwitchToState(IDLE_CELL_SEARCH);
        SynchronizeToStrongestCell(); // retry to a different cell
    }
}

void
LteUeRrc::ApplyRadioResourceConfigDedicatedSecondaryCarrier(
    LteRrcSap::NonCriticalExtensionConfiguration nonCec)
{
    NS_LOG_FUNCTION(this);

    m_sCellToAddModList = nonCec.sCellToAddModList;

    for (uint32_t sCellIndex : nonCec.sCellToReleaseList)
    {
        m_cphySapProvider.at(sCellIndex)->Reset();
        m_cmacSapProvider.at(sCellIndex)->Reset();
    }

    for (auto& scell : nonCec.sCellToAddModList)
    {
        uint8_t ccId = scell.sCellIndex;

        uint16_t physCellId = scell.cellIdentification.physCellId;
        uint16_t ulBand =
            scell.radioResourceConfigCommonSCell.ulConfiguration.ulFreqInfo.ulBandwidth;
        uint32_t ulEarfcn =
            scell.radioResourceConfigCommonSCell.ulConfiguration.ulFreqInfo.ulCarrierFreq;
        uint16_t dlBand = scell.radioResourceConfigCommonSCell.nonUlConfiguration.dlBandwidth;
        uint32_t dlEarfcn = scell.cellIdentification.dlCarrierFreq;
        uint8_t txMode = scell.radioResourceConfigDedicatedSCell.physicalConfigDedicatedSCell
                             .antennaInfo.transmissionMode;
        uint16_t srsIndex = scell.radioResourceConfigDedicatedSCell.physicalConfigDedicatedSCell
                                .soundingRsUlConfigDedicated.srsConfigIndex;

        m_cphySapProvider.at(ccId)->SynchronizeWithEnb(physCellId, dlEarfcn);
        m_cphySapProvider.at(ccId)->SetDlBandwidth(dlBand);
        m_cphySapProvider.at(ccId)->ConfigureUplink(ulEarfcn, ulBand);
        m_cphySapProvider.at(ccId)->ConfigureReferenceSignalPower(
            scell.radioResourceConfigCommonSCell.nonUlConfiguration.pdschConfigCommon
                .referenceSignalPower);
        m_cphySapProvider.at(ccId)->SetTransmissionMode(txMode);
        m_cphySapProvider.at(ccId)->SetRnti(m_rnti);
        m_cmacSapProvider.at(ccId)->SetRnti(m_rnti);
        // update PdschConfigDedicated (i.e. P_A value)
        LteRrcSap::PdschConfigDedicated pdschConfigDedicated =
            scell.radioResourceConfigDedicatedSCell.physicalConfigDedicatedSCell
                .pdschConfigDedicated;
        double paDouble = LteRrcSap::ConvertPdschConfigDedicated2Double(pdschConfigDedicated);
        m_cphySapProvider.at(ccId)->SetPa(paDouble);
        m_cphySapProvider.at(ccId)->SetSrsConfigurationIndex(srsIndex);
    }

    m_sCarrierConfiguredTrace(this, m_sCellToAddModList);
}

void
LteUeRrc::ApplyRadioResourceConfigDedicated(LteRrcSap::RadioResourceConfigDedicated rrcd)
{
    NS_LOG_FUNCTION(this);
    const LteRrcSap::PhysicalConfigDedicated& pcd = rrcd.physicalConfigDedicated;

    if (pcd.haveAntennaInfoDedicated)
    {
        m_cphySapProvider.at(0)->SetTransmissionMode(pcd.antennaInfo.transmissionMode);
    }
    if (pcd.haveSoundingRsUlConfigDedicated)
    {
        m_cphySapProvider.at(0)->SetSrsConfigurationIndex(
            pcd.soundingRsUlConfigDedicated.srsConfigIndex);
    }

    if (pcd.havePdschConfigDedicated)
    {
        // update PdschConfigDedicated (i.e. P_A value)
        m_pdschConfigDedicated = pcd.pdschConfigDedicated;
        double paDouble = LteRrcSap::ConvertPdschConfigDedicated2Double(m_pdschConfigDedicated);
        m_cphySapProvider.at(0)->SetPa(paDouble);
    }

    auto stamIt = rrcd.srbToAddModList.begin();
    if (stamIt != rrcd.srbToAddModList.end())
    {
        if (!m_srb1)
        {
            // SRB1 not setup yet
            NS_ASSERT_MSG((m_state == IDLE_CONNECTING) || (m_state == CONNECTED_HANDOVER),
                          "unexpected state " << m_state);
            NS_ASSERT_MSG(stamIt->srbIdentity == 1, "only SRB1 supported");

            const uint8_t lcid = 1; // fixed LCID for SRB1

            Ptr<LteRlc> rlc = CreateObject<LteRlcAm>();
            rlc->SetLteMacSapProvider(m_macSapProvider);
            rlc->SetRnti(m_rnti);
            rlc->SetLcId(lcid);

            Ptr<LtePdcp> pdcp = CreateObject<LtePdcp>();
            pdcp->SetRnti(m_rnti);
            pdcp->SetLcId(lcid);
            pdcp->SetLtePdcpSapUser(m_drbPdcpSapUser);
            pdcp->SetLteRlcSapProvider(rlc->GetLteRlcSapProvider());
            rlc->SetLteRlcSapUser(pdcp->GetLteRlcSapUser());

            m_srb1 = CreateObject<LteSignalingRadioBearerInfo>();
            m_srb1->m_rlc = rlc;
            m_srb1->m_pdcp = pdcp;
            m_srb1->m_srbIdentity = 1;
            m_srb1CreatedTrace(m_imsi, m_cellId, m_rnti);

            m_srb1->m_logicalChannelConfig.priority = stamIt->logicalChannelConfig.priority;
            m_srb1->m_logicalChannelConfig.prioritizedBitRateKbps =
                stamIt->logicalChannelConfig.prioritizedBitRateKbps;
            m_srb1->m_logicalChannelConfig.bucketSizeDurationMs =
                stamIt->logicalChannelConfig.bucketSizeDurationMs;
            m_srb1->m_logicalChannelConfig.logicalChannelGroup =
                stamIt->logicalChannelConfig.logicalChannelGroup;

            LteUeCmacSapProvider::LogicalChannelConfig lcConfig;
            lcConfig.priority = stamIt->logicalChannelConfig.priority;
            lcConfig.prioritizedBitRateKbps = stamIt->logicalChannelConfig.prioritizedBitRateKbps;
            lcConfig.bucketSizeDurationMs = stamIt->logicalChannelConfig.bucketSizeDurationMs;
            lcConfig.logicalChannelGroup = stamIt->logicalChannelConfig.logicalChannelGroup;
            LteMacSapUser* msu =
                m_ccmRrcSapProvider->ConfigureSignalBearer(lcid, lcConfig, rlc->GetLteMacSapUser());
            m_cmacSapProvider.at(0)->AddLc(lcid, lcConfig, msu);
            ++stamIt;
            NS_ASSERT_MSG(stamIt == rrcd.srbToAddModList.end(), "at most one SrbToAdd supported");

            LteUeRrcSapUser::SetupParameters ueParams;
            ueParams.srb0SapProvider = m_srb0->m_rlc->GetLteRlcSapProvider();
            ueParams.srb1SapProvider = m_srb1->m_pdcp->GetLtePdcpSapProvider();
            m_rrcSapUser->Setup(ueParams);
        }
        else
        {
            NS_LOG_INFO("request to modify SRB1 (skipping as currently not implemented)");
            // would need to modify m_srb1, and then propagate changes to the MAC
        }
    }

    for (auto dtamIt = rrcd.drbToAddModList.begin(); dtamIt != rrcd.drbToAddModList.end(); ++dtamIt)
    {
        NS_LOG_INFO(this << " IMSI " << m_imsi << " adding/modifying DRBID "
                         << (uint32_t)dtamIt->drbIdentity << " LC "
                         << (uint32_t)dtamIt->logicalChannelIdentity);
        NS_ASSERT_MSG(dtamIt->logicalChannelIdentity > 2,
                      "LCID value " << dtamIt->logicalChannelIdentity << " is reserved for SRBs");

        auto drbMapIt = m_drbMap.find(dtamIt->drbIdentity);
        if (drbMapIt == m_drbMap.end())
        {
            NS_LOG_INFO("New Data Radio Bearer");

            TypeId rlcTypeId;
            if (m_useRlcSm)
            {
                rlcTypeId = LteRlcSm::GetTypeId();
            }
            else
            {
                switch (dtamIt->rlcConfig.choice)
                {
                case LteRrcSap::RlcConfig::AM:
                    rlcTypeId = LteRlcAm::GetTypeId();
                    break;

                case LteRrcSap::RlcConfig::UM_BI_DIRECTIONAL:
                    rlcTypeId = LteRlcUm::GetTypeId();
                    break;

                default:
                    NS_FATAL_ERROR("unsupported RLC configuration");
                    break;
                }
            }

            ObjectFactory rlcObjectFactory;
            rlcObjectFactory.SetTypeId(rlcTypeId);
            Ptr<LteRlc> rlc = rlcObjectFactory.Create()->GetObject<LteRlc>();
            rlc->SetLteMacSapProvider(m_macSapProvider);
            rlc->SetRnti(m_rnti);
            rlc->SetLcId(dtamIt->logicalChannelIdentity);

            Ptr<LteDataRadioBearerInfo> drbInfo = CreateObject<LteDataRadioBearerInfo>();
            drbInfo->m_rlc = rlc;
            drbInfo->m_epsBearerIdentity = dtamIt->epsBearerIdentity;
            drbInfo->m_logicalChannelIdentity = dtamIt->logicalChannelIdentity;
            drbInfo->m_drbIdentity = dtamIt->drbIdentity;

            // we need PDCP only for real RLC, i.e., RLC/UM or RLC/AM
            // if we are using RLC/SM we don't care of anything above RLC
            if (rlcTypeId != LteRlcSm::GetTypeId())
            {
                Ptr<LtePdcp> pdcp = CreateObject<LtePdcp>();
                pdcp->SetRnti(m_rnti);
                pdcp->SetLcId(dtamIt->logicalChannelIdentity);
                pdcp->SetLtePdcpSapUser(m_drbPdcpSapUser);
                pdcp->SetLteRlcSapProvider(rlc->GetLteRlcSapProvider());
                rlc->SetLteRlcSapUser(pdcp->GetLteRlcSapUser());
                drbInfo->m_pdcp = pdcp;
            }

            m_bid2DrbidMap[dtamIt->epsBearerIdentity] = dtamIt->drbIdentity;

            m_drbMap.insert(
                std::pair<uint8_t, Ptr<LteDataRadioBearerInfo>>(dtamIt->drbIdentity, drbInfo));

            m_drbCreatedTrace(m_imsi, m_cellId, m_rnti, dtamIt->drbIdentity);

            LteUeCmacSapProvider::LogicalChannelConfig lcConfig;
            lcConfig.priority = dtamIt->logicalChannelConfig.priority;
            lcConfig.prioritizedBitRateKbps = dtamIt->logicalChannelConfig.prioritizedBitRateKbps;
            lcConfig.bucketSizeDurationMs = dtamIt->logicalChannelConfig.bucketSizeDurationMs;
            lcConfig.logicalChannelGroup = dtamIt->logicalChannelConfig.logicalChannelGroup;

            NS_LOG_DEBUG(this << " UE RRC RNTI " << m_rnti << " Number Of Component Carriers "
                              << m_numberOfComponentCarriers << " lcID "
                              << (uint16_t)dtamIt->logicalChannelIdentity);
            // Call AddLc of UE component carrier manager
            std::vector<LteUeCcmRrcSapProvider::LcsConfig> lcOnCcMapping =
                m_ccmRrcSapProvider->AddLc(dtamIt->logicalChannelIdentity,
                                           lcConfig,
                                           rlc->GetLteMacSapUser());

            NS_LOG_DEBUG("Size of lcOnCcMapping vector " << lcOnCcMapping.size());
            auto itLcOnCcMapping = lcOnCcMapping.begin();
            NS_ASSERT_MSG(itLcOnCcMapping != lcOnCcMapping.end(),
                          "Component carrier manager failed to add LC for data radio bearer");

            for (itLcOnCcMapping = lcOnCcMapping.begin(); itLcOnCcMapping != lcOnCcMapping.end();
                 ++itLcOnCcMapping)
            {
                NS_LOG_DEBUG("RNTI " << m_rnti << " LCG id "
                                     << (uint16_t)itLcOnCcMapping->lcConfig.logicalChannelGroup
                                     << " ComponentCarrierId "
                                     << (uint16_t)itLcOnCcMapping->componentCarrierId);
                uint8_t index = itLcOnCcMapping->componentCarrierId;
                LteUeCmacSapProvider::LogicalChannelConfig lcConfigFromCcm =
                    itLcOnCcMapping->lcConfig;
                LteMacSapUser* msu = itLcOnCcMapping->msu;
                m_cmacSapProvider.at(index)->AddLc(dtamIt->logicalChannelIdentity,
                                                   lcConfigFromCcm,
                                                   msu);
            }

            rlc->Initialize();
        }
        else
        {
            NS_LOG_INFO("request to modify existing DRBID");
            Ptr<LteDataRadioBearerInfo> drbInfo = drbMapIt->second;
            /// @todo currently not implemented. Would need to modify drbInfo, and then propagate
            /// changes to the MAC
        }
    }

    for (auto dtdmIt = rrcd.drbToReleaseList.begin(); dtdmIt != rrcd.drbToReleaseList.end();
         ++dtdmIt)
    {
        uint8_t drbid = *dtdmIt;
        NS_LOG_INFO(this << " IMSI " << m_imsi << " releasing DRB " << (uint32_t)drbid);
        auto it = m_drbMap.find(drbid);
        NS_ASSERT_MSG(it != m_drbMap.end(), "could not find bearer with given lcid");
        m_drbMap.erase(it);
        m_bid2DrbidMap.erase(drbid);
        // Remove LCID
        for (uint32_t i = 0; i < m_numberOfComponentCarriers; i++)
        {
            m_cmacSapProvider.at(i)->RemoveLc(drbid + 2);
        }
    }
}

void
LteUeRrc::ApplyMeasConfig(LteRrcSap::MeasConfig mc)
{
    NS_LOG_FUNCTION(this);

    // perform the actions specified in 3GPP TS 36.331 section 5.5.2.1

    // 3GPP TS 36.331 section 5.5.2.4 Measurement object removal
    for (auto it = mc.measObjectToRemoveList.begin(); it != mc.measObjectToRemoveList.end(); ++it)
    {
        uint8_t measObjectId = *it;
        NS_LOG_LOGIC(this << " deleting measObjectId " << (uint32_t)measObjectId);
        m_varMeasConfig.measObjectList.erase(measObjectId);
        auto measIdIt = m_varMeasConfig.measIdList.begin();
        while (measIdIt != m_varMeasConfig.measIdList.end())
        {
            if (measIdIt->second.measObjectId == measObjectId)
            {
                uint8_t measId = measIdIt->second.measId;
                NS_ASSERT(measId == measIdIt->first);
                NS_LOG_LOGIC(this << " deleting measId " << (uint32_t)measId
                                  << " because referring to measObjectId "
                                  << (uint32_t)measObjectId);
                // note: postfix operator preserves iterator validity
                m_varMeasConfig.measIdList.erase(measIdIt++);
                VarMeasReportListClear(measId);
            }
            else
            {
                ++measIdIt;
            }
        }
    }

    // 3GPP TS 36.331 section 5.5.2.5  Measurement object addition/ modification
    for (auto it = mc.measObjectToAddModList.begin(); it != mc.measObjectToAddModList.end(); ++it)
    {
        // simplifying assumptions
        NS_ASSERT_MSG(it->measObjectEutra.cellsToRemoveList.empty(),
                      "cellsToRemoveList not supported");
        NS_ASSERT_MSG(it->measObjectEutra.cellsToAddModList.empty(),
                      "cellsToAddModList not supported");
        NS_ASSERT_MSG(it->measObjectEutra.cellsToRemoveList.empty(),
                      "blackCellsToRemoveList not supported");
        NS_ASSERT_MSG(it->measObjectEutra.blackCellsToAddModList.empty(),
                      "blackCellsToAddModList not supported");
        NS_ASSERT_MSG(it->measObjectEutra.haveCellForWhichToReportCGI == false,
                      "cellForWhichToReportCGI is not supported");

        uint8_t measObjectId = it->measObjectId;
        auto measObjectIt = m_varMeasConfig.measObjectList.find(measObjectId);
        if (measObjectIt != m_varMeasConfig.measObjectList.end())
        {
            NS_LOG_LOGIC("measObjectId " << (uint32_t)measObjectId << " exists, updating entry");
            measObjectIt->second = *it;
            for (auto measIdIt = m_varMeasConfig.measIdList.begin();
                 measIdIt != m_varMeasConfig.measIdList.end();
                 ++measIdIt)
            {
                if (measIdIt->second.measObjectId == measObjectId)
                {
                    uint8_t measId = measIdIt->second.measId;
                    NS_LOG_LOGIC(this << " found measId " << (uint32_t)measId
                                      << " referring to measObjectId " << (uint32_t)measObjectId);
                    VarMeasReportListClear(measId);
                }
            }
        }
        else
        {
            NS_LOG_LOGIC("measObjectId " << (uint32_t)measObjectId << " is new, adding entry");
            m_varMeasConfig.measObjectList[measObjectId] = *it;
        }
    }

    // 3GPP TS 36.331 section 5.5.2.6 Reporting configuration removal
    for (auto it = mc.reportConfigToRemoveList.begin(); it != mc.reportConfigToRemoveList.end();
         ++it)
    {
        uint8_t reportConfigId = *it;
        NS_LOG_LOGIC(this << " deleting reportConfigId " << (uint32_t)reportConfigId);
        m_varMeasConfig.reportConfigList.erase(reportConfigId);
        auto measIdIt = m_varMeasConfig.measIdList.begin();
        while (measIdIt != m_varMeasConfig.measIdList.end())
        {
            if (measIdIt->second.reportConfigId == reportConfigId)
            {
                uint8_t measId = measIdIt->second.measId;
                NS_ASSERT(measId == measIdIt->first);
                NS_LOG_LOGIC(this << " deleting measId " << (uint32_t)measId
                                  << " because referring to reportConfigId "
                                  << (uint32_t)reportConfigId);
                // note: postfix operator preserves iterator validity
                m_varMeasConfig.measIdList.erase(measIdIt++);
                VarMeasReportListClear(measId);
            }
            else
            {
                ++measIdIt;
            }
        }
    }

    // 3GPP TS 36.331 section 5.5.2.7 Reporting configuration addition/ modification
    for (auto it = mc.reportConfigToAddModList.begin(); it != mc.reportConfigToAddModList.end();
         ++it)
    {
        // simplifying assumptions
        NS_ASSERT_MSG(it->reportConfigEutra.triggerType == LteRrcSap::ReportConfigEutra::EVENT,
                      "only trigger type EVENT is supported");

        uint8_t reportConfigId = it->reportConfigId;
        auto reportConfigIt = m_varMeasConfig.reportConfigList.find(reportConfigId);
        if (reportConfigIt != m_varMeasConfig.reportConfigList.end())
        {
            NS_LOG_LOGIC("reportConfigId " << (uint32_t)reportConfigId
                                           << " exists, updating entry");
            m_varMeasConfig.reportConfigList[reportConfigId] = *it;
            for (auto measIdIt = m_varMeasConfig.measIdList.begin();
                 measIdIt != m_varMeasConfig.measIdList.end();
                 ++measIdIt)
            {
                if (measIdIt->second.reportConfigId == reportConfigId)
                {
                    uint8_t measId = measIdIt->second.measId;
                    NS_LOG_LOGIC(this << " found measId " << (uint32_t)measId
                                      << " referring to reportConfigId "
                                      << (uint32_t)reportConfigId);
                    VarMeasReportListClear(measId);
                }
            }
        }
        else
        {
            NS_LOG_LOGIC("reportConfigId " << (uint32_t)reportConfigId << " is new, adding entry");
            m_varMeasConfig.reportConfigList[reportConfigId] = *it;
        }
    }

    // 3GPP TS 36.331 section 5.5.2.8 Quantity configuration
    if (mc.haveQuantityConfig)
    {
        NS_LOG_LOGIC(this << " setting quantityConfig");
        m_varMeasConfig.quantityConfig = mc.quantityConfig;
        // Convey the filter coefficient to PHY layer so it can configure the power control
        // parameter
        for (uint16_t i = 0; i < m_numberOfComponentCarriers; i++)
        {
            m_cphySapProvider.at(i)->SetRsrpFilterCoefficient(
                mc.quantityConfig.filterCoefficientRSRP);
        }
        // we calculate here the coefficient a used for Layer 3 filtering, see 3GPP TS 36.331
        // section 5.5.3.2
        m_varMeasConfig.aRsrp = std::pow(0.5, mc.quantityConfig.filterCoefficientRSRP / 4.0);
        m_varMeasConfig.aRsrq = std::pow(0.5, mc.quantityConfig.filterCoefficientRSRQ / 4.0);
        NS_LOG_LOGIC(this << " new filter coefficients: aRsrp=" << m_varMeasConfig.aRsrp
                          << ", aRsrq=" << m_varMeasConfig.aRsrq);

        for (auto measIdIt = m_varMeasConfig.measIdList.begin();
             measIdIt != m_varMeasConfig.measIdList.end();
             ++measIdIt)
        {
            VarMeasReportListClear(measIdIt->second.measId);
        }
    }

    // 3GPP TS 36.331 section 5.5.2.2 Measurement identity removal
    for (auto it = mc.measIdToRemoveList.begin(); it != mc.measIdToRemoveList.end(); ++it)
    {
        uint8_t measId = *it;
        NS_LOG_LOGIC(this << " deleting measId " << (uint32_t)measId);
        m_varMeasConfig.measIdList.erase(measId);
        VarMeasReportListClear(measId);

        // removing time-to-trigger queues
        m_enteringTriggerQueue.erase(measId);
        m_leavingTriggerQueue.erase(measId);
    }

    // 3GPP TS 36.331 section 5.5.2.3 Measurement identity addition/ modification
    for (auto it = mc.measIdToAddModList.begin(); it != mc.measIdToAddModList.end(); ++it)
    {
        NS_LOG_LOGIC(this << " measId " << (uint32_t)it->measId
                          << " (measObjectId=" << (uint32_t)it->measObjectId
                          << ", reportConfigId=" << (uint32_t)it->reportConfigId << ")");
        NS_ASSERT(m_varMeasConfig.measObjectList.find(it->measObjectId) !=
                  m_varMeasConfig.measObjectList.end());
        NS_ASSERT(m_varMeasConfig.reportConfigList.find(it->reportConfigId) !=
                  m_varMeasConfig.reportConfigList.end());
        m_varMeasConfig.measIdList[it->measId] = *it; // side effect: create new entry if not exists
        auto measReportIt = m_varMeasReportList.find(it->measId);
        if (measReportIt != m_varMeasReportList.end())
        {
            measReportIt->second.periodicReportTimer.Cancel();
            m_varMeasReportList.erase(measReportIt);
        }
        NS_ASSERT(m_varMeasConfig.reportConfigList.find(it->reportConfigId)
                      ->second.reportConfigEutra.triggerType !=
                  LteRrcSap::ReportConfigEutra::PERIODICAL);

        // new empty queues for time-to-trigger
        std::list<PendingTrigger_t> s;
        m_enteringTriggerQueue[it->measId] = s;
        m_leavingTriggerQueue[it->measId] = s;
    }

    if (mc.haveMeasGapConfig)
    {
        NS_FATAL_ERROR("measurement gaps are currently not supported");
    }

    if (mc.haveSmeasure)
    {
        NS_FATAL_ERROR("s-measure is currently not supported");
    }

    if (mc.haveSpeedStatePars)
    {
        NS_FATAL_ERROR("SpeedStatePars are currently not supported");
    }
}

void
LteUeRrc::SaveUeMeasurements(uint16_t cellId,
                             double rsrp,
                             double rsrq,
                             bool useLayer3Filtering,
                             uint8_t componentCarrierId)
{
    NS_LOG_FUNCTION(this << cellId << +componentCarrierId << rsrp << rsrq << useLayer3Filtering);

    auto storedMeasIt = m_storedMeasValues.find(cellId);

    if (storedMeasIt != m_storedMeasValues.end())
    {
        if (useLayer3Filtering)
        {
            // F_n = (1-a) F_{n-1} + a M_n
            storedMeasIt->second.rsrp = (1 - m_varMeasConfig.aRsrp) * storedMeasIt->second.rsrp +
                                        m_varMeasConfig.aRsrp * rsrp;

            if (std::isnan(storedMeasIt->second.rsrq))
            {
                // the previous RSRQ measurements provided UE PHY are invalid
                storedMeasIt->second.rsrq = rsrq; // replace it with unfiltered value
            }
            else
            {
                storedMeasIt->second.rsrq =
                    (1 - m_varMeasConfig.aRsrq) * storedMeasIt->second.rsrq +
                    m_varMeasConfig.aRsrq * rsrq;
            }
        }
        else
        {
            storedMeasIt->second.rsrp = rsrp;
            storedMeasIt->second.rsrq = rsrq;
        }
    }
    else
    {
        // first value is always unfiltered
        MeasValues v;
        v.rsrp = rsrp;
        v.rsrq = rsrq;
        v.carrierFreq = m_cphySapProvider.at(componentCarrierId)->GetDlEarfcn();

        std::pair<uint16_t, MeasValues> val(cellId, v);
        auto ret = m_storedMeasValues.insert(val);
        NS_ASSERT_MSG(ret.second == true, "element already existed");
        storedMeasIt = ret.first;
    }

    NS_LOG_DEBUG(this << " IMSI " << m_imsi << " state " << m_state << ", measured cell " << cellId
                      << ", carrier component Id " << componentCarrierId << ", new RSRP " << rsrp
                      << " stored " << storedMeasIt->second.rsrp << ", new RSRQ " << rsrq
                      << " stored " << storedMeasIt->second.rsrq);
}

void
LteUeRrc::MeasurementReportTriggering(uint8_t measId)
{
    NS_LOG_FUNCTION(this << (uint16_t)measId);

    auto measIdIt = m_varMeasConfig.measIdList.find(measId);
    NS_ASSERT(measIdIt != m_varMeasConfig.measIdList.end());
    NS_ASSERT(measIdIt->first == measIdIt->second.measId);

    auto reportConfigIt = m_varMeasConfig.reportConfigList.find(measIdIt->second.reportConfigId);
    NS_ASSERT(reportConfigIt != m_varMeasConfig.reportConfigList.end());
    LteRrcSap::ReportConfigEutra& reportConfigEutra = reportConfigIt->second.reportConfigEutra;

    auto measObjectIt = m_varMeasConfig.measObjectList.find(measIdIt->second.measObjectId);
    NS_ASSERT(measObjectIt != m_varMeasConfig.measObjectList.end());
    LteRrcSap::MeasObjectEutra& measObjectEutra = measObjectIt->second.measObjectEutra;

    auto measReportIt = m_varMeasReportList.find(measId);
    bool isMeasIdInReportList = (measReportIt != m_varMeasReportList.end());

    // we don't check the purpose field, as it is only included for
    // triggerType == periodical, which is not supported
    NS_ASSERT_MSG(reportConfigEutra.triggerType == LteRrcSap::ReportConfigEutra::EVENT,
                  "only triggerType == event is supported");
    // only EUTRA is supported, no need to check for it

    NS_LOG_LOGIC(this << " considering measId " << (uint32_t)measId);
    bool eventEntryCondApplicable = false;
    bool eventLeavingCondApplicable = false;
    ConcernedCells_t concernedCellsEntry;
    ConcernedCells_t concernedCellsLeaving;

    /*
     * Find which serving cell corresponds to measObjectEutra.carrierFreq
     * It is used, for example, by A1 event:
     * See TS 36.331 5.5.4.2: "for this measurement, consider the primary or
     * secondary cell that is configured on the frequency indicated in the
     * associated measObjectEUTRA to be the serving cell"
     */
    uint16_t servingCellId = 0;
    for (auto cphySapProvider : m_cphySapProvider)
    {
        if (cphySapProvider->GetDlEarfcn() == measObjectEutra.carrierFreq)
        {
            servingCellId = cphySapProvider->GetCellId();
        }
    }

    if (servingCellId == 0)
    {
        return;
    }

    switch (reportConfigEutra.eventId)
    {
    case LteRrcSap::ReportConfigEutra::EVENT_A1: {
        /*
         * Event A1 (Serving becomes better than threshold)
         * Please refer to 3GPP TS 36.331 Section 5.5.4.2
         */

        double ms;     // Ms, the measurement result of the serving cell
        double thresh; // Thresh, the threshold parameter for this event
        // Hys, the hysteresis parameter for this event.
        double hys =
            EutranMeasurementMapping::IeValue2ActualHysteresis(reportConfigEutra.hysteresis);

        switch (reportConfigEutra.triggerQuantity)
        {
        case LteRrcSap::ReportConfigEutra::RSRP:
            ms = m_storedMeasValues[servingCellId].rsrp;

            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRP);
            thresh = EutranMeasurementMapping::RsrpRange2Dbm(reportConfigEutra.threshold1.range);
            break;
        case LteRrcSap::ReportConfigEutra::RSRQ:
            ms = m_storedMeasValues[servingCellId].rsrq;
            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRQ);
            thresh = EutranMeasurementMapping::RsrqRange2Db(reportConfigEutra.threshold1.range);
            break;
        default:
            NS_FATAL_ERROR("unsupported triggerQuantity");
            break;
        }

        // Inequality A1-1 (Entering condition): Ms - Hys > Thresh
        bool entryCond = ms - hys > thresh;

        if (entryCond)
        {
            if (!isMeasIdInReportList)
            {
                concernedCellsEntry.push_back(servingCellId);
                eventEntryCondApplicable = true;
            }
            else
            {
                /*
                 * This is to check that the triggered cell recorded in the
                 * VarMeasReportList is the serving cell.
                 */
                NS_ASSERT(measReportIt->second.cellsTriggeredList.find(servingCellId) !=
                          measReportIt->second.cellsTriggeredList.end());
            }
        }
        else if (reportConfigEutra.timeToTrigger > 0)
        {
            CancelEnteringTrigger(measId);
        }

        // Inequality A1-2 (Leaving condition): Ms + Hys < Thresh
        bool leavingCond = ms + hys < thresh;

        if (leavingCond)
        {
            if (isMeasIdInReportList)
            {
                /*
                 * This is to check that the triggered cell recorded in the
                 * VarMeasReportList is the serving cell.
                 */
                NS_ASSERT(measReportIt->second.cellsTriggeredList.find(m_cellId) !=
                          measReportIt->second.cellsTriggeredList.end());
                concernedCellsLeaving.push_back(m_cellId);
                eventLeavingCondApplicable = true;
            }
        }
        else if (reportConfigEutra.timeToTrigger > 0)
        {
            CancelLeavingTrigger(measId);
        }

        NS_LOG_LOGIC(this << " event A1: serving cell " << servingCellId << " ms=" << ms
                          << " thresh=" << thresh << " entryCond=" << entryCond
                          << " leavingCond=" << leavingCond);
    }

    break;

    case LteRrcSap::ReportConfigEutra::EVENT_A2: {
        /*
         * Event A2 (Serving becomes worse than threshold)
         * Please refer to 3GPP TS 36.331 Section 5.5.4.3
         */

        double ms;     // Ms, the measurement result of the serving cell
        double thresh; // Thresh, the threshold parameter for this event
        // Hys, the hysteresis parameter for this event.
        double hys =
            EutranMeasurementMapping::IeValue2ActualHysteresis(reportConfigEutra.hysteresis);

        switch (reportConfigEutra.triggerQuantity)
        {
        case LteRrcSap::ReportConfigEutra::RSRP:
            ms = m_storedMeasValues[servingCellId].rsrp;
            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRP);
            thresh = EutranMeasurementMapping::RsrpRange2Dbm(reportConfigEutra.threshold1.range);
            break;
        case LteRrcSap::ReportConfigEutra::RSRQ:
            ms = m_storedMeasValues[servingCellId].rsrq;
            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRQ);
            thresh = EutranMeasurementMapping::RsrqRange2Db(reportConfigEutra.threshold1.range);
            break;
        default:
            NS_FATAL_ERROR("unsupported triggerQuantity");
            break;
        }

        // Inequality A2-1 (Entering condition): Ms + Hys < Thresh
        bool entryCond = ms + hys < thresh;

        if (entryCond)
        {
            if (!isMeasIdInReportList)
            {
                concernedCellsEntry.push_back(servingCellId);
                eventEntryCondApplicable = true;
            }
            else
            {
                /*
                 * This is to check that the triggered cell recorded in the
                 * VarMeasReportList is the serving cell.
                 */
                NS_ASSERT(measReportIt->second.cellsTriggeredList.find(servingCellId) !=
                          measReportIt->second.cellsTriggeredList.end());
            }
        }
        else if (reportConfigEutra.timeToTrigger > 0)
        {
            CancelEnteringTrigger(measId);
        }

        // Inequality A2-2 (Leaving condition): Ms - Hys > Thresh
        bool leavingCond = ms - hys > thresh;

        if (leavingCond)
        {
            if (isMeasIdInReportList)
            {
                /*
                 * This is to check that the triggered cell recorded in the
                 * VarMeasReportList is the serving cell.
                 */
                NS_ASSERT(measReportIt->second.cellsTriggeredList.find(servingCellId) !=
                          measReportIt->second.cellsTriggeredList.end());
                concernedCellsLeaving.push_back(servingCellId);
                eventLeavingCondApplicable = true;
            }
        }
        else if (reportConfigEutra.timeToTrigger > 0)
        {
            CancelLeavingTrigger(measId);
        }

        NS_LOG_LOGIC(this << " event A2: serving cell " << servingCellId << " ms=" << ms
                          << " thresh=" << thresh << " entryCond=" << entryCond
                          << " leavingCond=" << leavingCond);
    }

    break;

    case LteRrcSap::ReportConfigEutra::EVENT_A3: {
        /*
         * Event A3 (Neighbour becomes offset better than PCell)
         * Please refer to 3GPP TS 36.331 Section 5.5.4.4
         */

        double mn; // Mn, the measurement result of the neighbouring cell
        double ofn = measObjectEutra
                         .offsetFreq; // Ofn, the frequency specific offset of the frequency of the
        double ocn = 0.0;             // Ocn, the cell specific offset of the neighbour cell
        double mp;                    // Mp, the measurement result of the PCell
        double ofp = measObjectEutra
                         .offsetFreq; // Ofp, the frequency specific offset of the primary frequency
        double ocp = 0.0;             // Ocp, the cell specific offset of the PCell
        // Off, the offset parameter for this event.
        double off = EutranMeasurementMapping::IeValue2ActualA3Offset(reportConfigEutra.a3Offset);
        // Hys, the hysteresis parameter for this event.
        double hys =
            EutranMeasurementMapping::IeValue2ActualHysteresis(reportConfigEutra.hysteresis);

        switch (reportConfigEutra.triggerQuantity)
        {
        case LteRrcSap::ReportConfigEutra::RSRP:
            mp = m_storedMeasValues[m_cellId].rsrp;
            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRP);
            break;
        case LteRrcSap::ReportConfigEutra::RSRQ:
            mp = m_storedMeasValues[m_cellId].rsrq;
            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRQ);
            break;
        default:
            NS_FATAL_ERROR("unsupported triggerQuantity");
            break;
        }

        for (auto storedMeasIt = m_storedMeasValues.begin();
             storedMeasIt != m_storedMeasValues.end();
             ++storedMeasIt)
        {
            uint16_t cellId = storedMeasIt->first;
            if (cellId == m_cellId)
            {
                continue;
            }

            // Only cell(s) on the frequency indicated in the associated measObject can trigger
            // event.
            if (m_storedMeasValues.at(cellId).carrierFreq != measObjectEutra.carrierFreq)
            {
                continue;
            }

            switch (reportConfigEutra.triggerQuantity)
            {
            case LteRrcSap::ReportConfigEutra::RSRP:
                mn = storedMeasIt->second.rsrp;
                break;
            case LteRrcSap::ReportConfigEutra::RSRQ:
                mn = storedMeasIt->second.rsrq;
                break;
            default:
                NS_FATAL_ERROR("unsupported triggerQuantity");
                break;
            }

            bool hasTriggered =
                isMeasIdInReportList && (measReportIt->second.cellsTriggeredList.find(cellId) !=
                                         measReportIt->second.cellsTriggeredList.end());

            // Inequality A3-1 (Entering condition): Mn + Ofn + Ocn - Hys > Mp + Ofp + Ocp + Off
            bool entryCond = mn + ofn + ocn - hys > mp + ofp + ocp + off;

            if (entryCond)
            {
                if (!hasTriggered)
                {
                    concernedCellsEntry.push_back(cellId);
                    eventEntryCondApplicable = true;
                }
            }
            else if (reportConfigEutra.timeToTrigger > 0)
            {
                CancelEnteringTrigger(measId, cellId);
            }

            // Inequality A3-2 (Leaving condition): Mn + Ofn + Ocn + Hys < Mp + Ofp + Ocp + Off
            bool leavingCond = mn + ofn + ocn + hys < mp + ofp + ocp + off;

            if (leavingCond)
            {
                if (hasTriggered)
                {
                    concernedCellsLeaving.push_back(cellId);
                    eventLeavingCondApplicable = true;
                }
            }
            else if (reportConfigEutra.timeToTrigger > 0)
            {
                CancelLeavingTrigger(measId, cellId);
            }

            NS_LOG_LOGIC(this << " event A3: neighbor cell " << cellId << " mn=" << mn
                              << " mp=" << mp << " offset=" << off << " entryCond=" << entryCond
                              << " leavingCond=" << leavingCond);
        }
    }

    break;

    case LteRrcSap::ReportConfigEutra::EVENT_A4: {
        /*
         * Event A4 (Neighbour becomes better than threshold)
         * Please refer to 3GPP TS 36.331 Section 5.5.4.5
         */

        double mn; // Mn, the measurement result of the neighbouring cell
        double ofn = measObjectEutra
                         .offsetFreq; // Ofn, the frequency specific offset of the frequency of the
        double ocn = 0.0;             // Ocn, the cell specific offset of the neighbour cell
        double thresh;                // Thresh, the threshold parameter for this event
        // Hys, the hysteresis parameter for this event.
        double hys =
            EutranMeasurementMapping::IeValue2ActualHysteresis(reportConfigEutra.hysteresis);

        switch (reportConfigEutra.triggerQuantity)
        {
        case LteRrcSap::ReportConfigEutra::RSRP:
            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRP);
            thresh = EutranMeasurementMapping::RsrpRange2Dbm(reportConfigEutra.threshold1.range);
            break;
        case LteRrcSap::ReportConfigEutra::RSRQ:
            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRQ);
            thresh = EutranMeasurementMapping::RsrqRange2Db(reportConfigEutra.threshold1.range);
            break;
        default:
            NS_FATAL_ERROR("unsupported triggerQuantity");
            break;
        }

        for (auto storedMeasIt = m_storedMeasValues.begin();
             storedMeasIt != m_storedMeasValues.end();
             ++storedMeasIt)
        {
            uint16_t cellId = storedMeasIt->first;
            if (cellId == m_cellId)
            {
                continue;
            }

            switch (reportConfigEutra.triggerQuantity)
            {
            case LteRrcSap::ReportConfigEutra::RSRP:
                mn = storedMeasIt->second.rsrp;
                break;
            case LteRrcSap::ReportConfigEutra::RSRQ:
                mn = storedMeasIt->second.rsrq;
                break;
            default:
                NS_FATAL_ERROR("unsupported triggerQuantity");
                break;
            }

            bool hasTriggered =
                isMeasIdInReportList && (measReportIt->second.cellsTriggeredList.find(cellId) !=
                                         measReportIt->second.cellsTriggeredList.end());

            // Inequality A4-1 (Entering condition): Mn + Ofn + Ocn - Hys > Thresh
            bool entryCond = mn + ofn + ocn - hys > thresh;

            if (entryCond)
            {
                if (!hasTriggered)
                {
                    concernedCellsEntry.push_back(cellId);
                    eventEntryCondApplicable = true;
                }
            }
            else if (reportConfigEutra.timeToTrigger > 0)
            {
                CancelEnteringTrigger(measId, cellId);
            }

            // Inequality A4-2 (Leaving condition): Mn + Ofn + Ocn + Hys < Thresh
            bool leavingCond = mn + ofn + ocn + hys < thresh;

            if (leavingCond)
            {
                if (hasTriggered)
                {
                    concernedCellsLeaving.push_back(cellId);
                    eventLeavingCondApplicable = true;
                }
            }
            else if (reportConfigEutra.timeToTrigger > 0)
            {
                CancelLeavingTrigger(measId, cellId);
            }

            NS_LOG_LOGIC(this << " event A4: neighbor cell " << cellId << " mn=" << mn
                              << " thresh=" << thresh << " entryCond=" << entryCond
                              << " leavingCond=" << leavingCond);
        }
    }

    break;

    case LteRrcSap::ReportConfigEutra::EVENT_A5: {
        /*
         * Event A5 (PCell becomes worse than threshold1 and neighbour
         * becomes better than threshold2)
         * Please refer to 3GPP TS 36.331 Section 5.5.4.6
         */

        double mp; // Mp, the measurement result of the PCell
        double mn; // Mn, the measurement result of the neighbouring cell
        double ofn = measObjectEutra
                         .offsetFreq; // Ofn, the frequency specific offset of the frequency of the
        double ocn = 0.0;             // Ocn, the cell specific offset of the neighbour cell
        double thresh1;               // Thresh1, the threshold parameter for this event
        double thresh2;               // Thresh2, the threshold parameter for this event
        // Hys, the hysteresis parameter for this event.
        double hys =
            EutranMeasurementMapping::IeValue2ActualHysteresis(reportConfigEutra.hysteresis);

        switch (reportConfigEutra.triggerQuantity)
        {
        case LteRrcSap::ReportConfigEutra::RSRP:
            mp = m_storedMeasValues[m_cellId].rsrp;
            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRP);
            NS_ASSERT(reportConfigEutra.threshold2.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRP);
            thresh1 = EutranMeasurementMapping::RsrpRange2Dbm(reportConfigEutra.threshold1.range);
            thresh2 = EutranMeasurementMapping::RsrpRange2Dbm(reportConfigEutra.threshold2.range);
            break;
        case LteRrcSap::ReportConfigEutra::RSRQ:
            mp = m_storedMeasValues[m_cellId].rsrq;
            NS_ASSERT(reportConfigEutra.threshold1.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRQ);
            NS_ASSERT(reportConfigEutra.threshold2.choice ==
                      LteRrcSap::ThresholdEutra::THRESHOLD_RSRQ);
            thresh1 = EutranMeasurementMapping::RsrqRange2Db(reportConfigEutra.threshold1.range);
            thresh2 = EutranMeasurementMapping::RsrqRange2Db(reportConfigEutra.threshold2.range);
            break;
        default:
            NS_FATAL_ERROR("unsupported triggerQuantity");
            break;
        }

        // Inequality A5-1 (Entering condition 1): Mp + Hys < Thresh1
        bool entryCond = mp + hys < thresh1;

        if (entryCond)
        {
            for (auto storedMeasIt = m_storedMeasValues.begin();
                 storedMeasIt != m_storedMeasValues.end();
                 ++storedMeasIt)
            {
                uint16_t cellId = storedMeasIt->first;
                if (cellId == m_cellId)
                {
                    continue;
                }

                switch (reportConfigEutra.triggerQuantity)
                {
                case LteRrcSap::ReportConfigEutra::RSRP:
                    mn = storedMeasIt->second.rsrp;
                    break;
                case LteRrcSap::ReportConfigEutra::RSRQ:
                    mn = storedMeasIt->second.rsrq;
                    break;
                default:
                    NS_FATAL_ERROR("unsupported triggerQuantity");
                    break;
                }

                bool hasTriggered =
                    isMeasIdInReportList && (measReportIt->second.cellsTriggeredList.find(cellId) !=
                                             measReportIt->second.cellsTriggeredList.end());

                // Inequality A5-2 (Entering condition 2): Mn + Ofn + Ocn - Hys > Thresh2

                entryCond = mn + ofn + ocn - hys > thresh2;

                if (entryCond)
                {
                    if (!hasTriggered)
                    {
                        concernedCellsEntry.push_back(cellId);
                        eventEntryCondApplicable = true;
                    }
                }
                else if (reportConfigEutra.timeToTrigger > 0)
                {
                    CancelEnteringTrigger(measId, cellId);
                }

                NS_LOG_LOGIC(this << " event A5: neighbor cell " << cellId << " mn=" << mn
                                  << " mp=" << mp << " thresh2=" << thresh2
                                  << " thresh1=" << thresh1 << " entryCond=" << entryCond);
            }
        }
        else
        {
            NS_LOG_LOGIC(this << " event A5: serving cell " << m_cellId << " mp=" << mp
                              << " thresh1=" << thresh1 << " entryCond=" << entryCond);

            if (reportConfigEutra.timeToTrigger > 0)
            {
                CancelEnteringTrigger(measId);
            }
        }

        if (isMeasIdInReportList)
        {
            // Inequality A5-3 (Leaving condition 1): Mp - Hys > Thresh1
            bool leavingCond = mp - hys > thresh1;

            if (leavingCond)
            {
                if (reportConfigEutra.timeToTrigger == 0)
                {
                    // leaving condition #2 does not have to be checked

                    for (auto storedMeasIt = m_storedMeasValues.begin();
                         storedMeasIt != m_storedMeasValues.end();
                         ++storedMeasIt)
                    {
                        uint16_t cellId = storedMeasIt->first;
                        if (cellId == m_cellId)
                        {
                            continue;
                        }

                        if (measReportIt->second.cellsTriggeredList.find(cellId) !=
                            measReportIt->second.cellsTriggeredList.end())
                        {
                            concernedCellsLeaving.push_back(cellId);
                            eventLeavingCondApplicable = true;
                        }
                    }
                }
                else
                {
                    // leaving condition #2 has to be checked to cancel time-to-trigger

                    for (auto storedMeasIt = m_storedMeasValues.begin();
                         storedMeasIt != m_storedMeasValues.end();
                         ++storedMeasIt)
                    {
                        uint16_t cellId = storedMeasIt->first;
                        if (cellId == m_cellId)
                        {
                            continue;
                        }

                        if (measReportIt->second.cellsTriggeredList.find(cellId) !=
                            measReportIt->second.cellsTriggeredList.end())
                        {
                            switch (reportConfigEutra.triggerQuantity)
                            {
                            case LteRrcSap::ReportConfigEutra::RSRP:
                                mn = storedMeasIt->second.rsrp;
                                break;
                            case LteRrcSap::ReportConfigEutra::RSRQ:
                                mn = storedMeasIt->second.rsrq;
                                break;
                            default:
                                NS_FATAL_ERROR("unsupported triggerQuantity");
                                break;
                            }

                            // Inequality A5-4 (Leaving condition 2): Mn + Ofn + Ocn + Hys < Thresh2

                            leavingCond = mn + ofn + ocn + hys < thresh2;

                            if (!leavingCond)
                            {
                                CancelLeavingTrigger(measId, cellId);
                            }

                            /*
                             * Whatever the result of leaving condition #2, this
                             * cell is still "in", because leaving condition #1
                             * is already true.
                             */
                            concernedCellsLeaving.push_back(cellId);
                            eventLeavingCondApplicable = true;

                            NS_LOG_LOGIC(this << " event A5: neighbor cell " << cellId
                                              << " mn=" << mn << " mp=" << mp
                                              << " thresh2=" << thresh2 << " thresh1=" << thresh1
                                              << " leavingCond=" << leavingCond);
                        }
                    }
                }

                NS_LOG_LOGIC(this << " event A5: serving cell " << m_cellId << " mp=" << mp
                                  << " thresh1=" << thresh1 << " leavingCond=" << leavingCond);
            }
            else
            {
                if (reportConfigEutra.timeToTrigger > 0)
                {
                    CancelLeavingTrigger(measId);
                }

                // check leaving condition #2

                for (auto storedMeasIt = m_storedMeasValues.begin();
                     storedMeasIt != m_storedMeasValues.end();
                     ++storedMeasIt)
                {
                    uint16_t cellId = storedMeasIt->first;
                    if (cellId == m_cellId)
                    {
                        continue;
                    }

                    if (measReportIt->second.cellsTriggeredList.find(cellId) !=
                        measReportIt->second.cellsTriggeredList.end())
                    {
                        switch (reportConfigEutra.triggerQuantity)
                        {
                        case LteRrcSap::ReportConfigEutra::RSRP:
                            mn = storedMeasIt->second.rsrp;
                            break;
                        case LteRrcSap::ReportConfigEutra::RSRQ:
                            mn = storedMeasIt->second.rsrq;
                            break;
                        default:
                            NS_FATAL_ERROR("unsupported triggerQuantity");
                            break;
                        }

                        // Inequality A5-4 (Leaving condition 2): Mn + Ofn + Ocn + Hys < Thresh2
                        leavingCond = mn + ofn + ocn + hys < thresh2;

                        if (leavingCond)
                        {
                            concernedCellsLeaving.push_back(cellId);
                            eventLeavingCondApplicable = true;
                        }

                        NS_LOG_LOGIC(this << " event A5: neighbor cell " << cellId << " mn=" << mn
                                          << " mp=" << mp << " thresh2=" << thresh2 << " thresh1="
                                          << thresh1 << " leavingCond=" << leavingCond);
                    }
                }
            }
        }
    }

    break;

    default:
        NS_FATAL_ERROR("unsupported eventId " << reportConfigEutra.eventId);
        break;
    }

    NS_LOG_LOGIC(this << " eventEntryCondApplicable=" << eventEntryCondApplicable
                      << " eventLeavingCondApplicable=" << eventLeavingCondApplicable);

    if (eventEntryCondApplicable)
    {
        if (reportConfigEutra.timeToTrigger == 0)
        {
            VarMeasReportListAdd(measId, concernedCellsEntry);
        }
        else
        {
            PendingTrigger_t t;
            t.measId = measId;
            t.concernedCells = concernedCellsEntry;
            t.timer = Simulator::Schedule(MilliSeconds(reportConfigEutra.timeToTrigger),
                                          &LteUeRrc::VarMeasReportListAdd,
                                          this,
                                          measId,
                                          concernedCellsEntry);
            auto enteringTriggerIt = m_enteringTriggerQueue.find(measId);
            NS_ASSERT(enteringTriggerIt != m_enteringTriggerQueue.end());
            enteringTriggerIt->second.push_back(t);
        }
    }

    if (eventLeavingCondApplicable)
    {
        // reportOnLeave will only be set when eventId = eventA3
        bool reportOnLeave =
            (reportConfigEutra.eventId == LteRrcSap::ReportConfigEutra::EVENT_A3) &&
            reportConfigEutra.reportOnLeave;

        if (reportConfigEutra.timeToTrigger == 0)
        {
            VarMeasReportListErase(measId, concernedCellsLeaving, reportOnLeave);
        }
        else
        {
            PendingTrigger_t t;
            t.measId = measId;
            t.concernedCells = concernedCellsLeaving;
            t.timer = Simulator::Schedule(MilliSeconds(reportConfigEutra.timeToTrigger),
                                          &LteUeRrc::VarMeasReportListErase,
                                          this,
                                          measId,
                                          concernedCellsLeaving,
                                          reportOnLeave);
            auto leavingTriggerIt = m_leavingTriggerQueue.find(measId);
            NS_ASSERT(leavingTriggerIt != m_leavingTriggerQueue.end());
            leavingTriggerIt->second.push_back(t);
        }
    }
}

void
LteUeRrc::CancelEnteringTrigger(uint8_t measId)
{
    NS_LOG_FUNCTION(this << (uint16_t)measId);

    auto it1 = m_enteringTriggerQueue.find(measId);
    NS_ASSERT(it1 != m_enteringTriggerQueue.end());

    if (!it1->second.empty())
    {
        for (auto it2 = it1->second.begin(); it2 != it1->second.end(); ++it2)
        {
            NS_ASSERT(it2->measId == measId);
            NS_LOG_LOGIC(this << " canceling entering time-to-trigger event at "
                              << Simulator::GetDelayLeft(it2->timer).GetSeconds());
            Simulator::Cancel(it2->timer);
        }

        it1->second.clear();
    }
}

void
LteUeRrc::CancelEnteringTrigger(uint8_t measId, uint16_t cellId)
{
    NS_LOG_FUNCTION(this << (uint16_t)measId << cellId);

    auto it1 = m_enteringTriggerQueue.find(measId);
    NS_ASSERT(it1 != m_enteringTriggerQueue.end());

    auto it2 = it1->second.begin();
    while (it2 != it1->second.end())
    {
        NS_ASSERT(it2->measId == measId);

        it2->concernedCells.remove_if([cellId](auto cell) { return cellId == cell; });

        if (it2->concernedCells.empty())
        {
            NS_LOG_LOGIC(this << " canceling entering time-to-trigger event at "
                              << Simulator::GetDelayLeft(it2->timer).GetSeconds());
            Simulator::Cancel(it2->timer);
            it2 = it1->second.erase(it2);
        }
        else
        {
            it2++;
        }
    }
}

void
LteUeRrc::CancelLeavingTrigger(uint8_t measId)
{
    NS_LOG_FUNCTION(this << (uint16_t)measId);

    auto it1 = m_leavingTriggerQueue.find(measId);
    NS_ASSERT(it1 != m_leavingTriggerQueue.end());

    if (!it1->second.empty())
    {
        for (auto it2 = it1->second.begin(); it2 != it1->second.end(); ++it2)
        {
            NS_ASSERT(it2->measId == measId);
            NS_LOG_LOGIC(this << " canceling leaving time-to-trigger event at "
                              << Simulator::GetDelayLeft(it2->timer).GetSeconds());
            Simulator::Cancel(it2->timer);
        }

        it1->second.clear();
    }
}

void
LteUeRrc::CancelLeavingTrigger(uint8_t measId, uint16_t cellId)
{
    NS_LOG_FUNCTION(this << (uint16_t)measId << cellId);

    auto it1 = m_leavingTriggerQueue.find(measId);
    NS_ASSERT(it1 != m_leavingTriggerQueue.end());

    auto it2 = it1->second.begin();
    while (it2 != it1->second.end())
    {
        NS_ASSERT(it2->measId == measId);

        it2->concernedCells.remove_if([cellId](auto cell) { return cellId == cell; });

        if (it2->concernedCells.empty())
        {
            NS_LOG_LOGIC(this << " canceling leaving time-to-trigger event at "
                              << Simulator::GetDelayLeft(it2->timer).GetSeconds());
            Simulator::Cancel(it2->timer);
            it2 = it1->second.erase(it2);
        }
        else
        {
            it2++;
        }
    }
}

void
LteUeRrc::VarMeasReportListAdd(uint8_t measId, ConcernedCells_t enteringCells)
{
    NS_LOG_FUNCTION(this << (uint16_t)measId);
    NS_ASSERT(!enteringCells.empty());

    auto measReportIt = m_varMeasReportList.find(measId);

    if (measReportIt == m_varMeasReportList.end())
    {
        VarMeasReport r;
        r.measId = measId;
        std::pair<uint8_t, VarMeasReport> val(measId, r);
        auto ret = m_varMeasReportList.insert(val);
        NS_ASSERT_MSG(ret.second == true, "element already existed");
        measReportIt = ret.first;
    }

    NS_ASSERT(measReportIt != m_varMeasReportList.end());

    for (auto it = enteringCells.begin(); it != enteringCells.end(); ++it)
    {
        measReportIt->second.cellsTriggeredList.insert(*it);
    }

    NS_ASSERT(!measReportIt->second.cellsTriggeredList.empty());

    // #issue 224, schedule only when there is no periodic event scheduled already
    if (!measReportIt->second.periodicReportTimer.IsPending())
    {
        measReportIt->second.numberOfReportsSent = 0;
        measReportIt->second.periodicReportTimer =
            Simulator::Schedule(UE_MEASUREMENT_REPORT_DELAY,
                                &LteUeRrc::SendMeasurementReport,
                                this,
                                measId);
    }

    auto enteringTriggerIt = m_enteringTriggerQueue.find(measId);
    NS_ASSERT(enteringTriggerIt != m_enteringTriggerQueue.end());
    if (!enteringTriggerIt->second.empty())
    {
        /*
         * Assumptions at this point:
         *  - the call to this function was delayed by time-to-trigger;
         *  - the time-to-trigger delay is fixed (not adaptive/dynamic); and
         *  - the first element in the list is associated with this function call.
         */
        enteringTriggerIt->second.pop_front();

        if (!enteringTriggerIt->second.empty())
        {
            /*
             * To prevent the same set of cells triggering again in the future,
             * we clean up the time-to-trigger queue. This case might occur when
             * time-to-trigger > 200 ms.
             */
            for (auto it = enteringCells.begin(); it != enteringCells.end(); ++it)
            {
                CancelEnteringTrigger(measId, *it);
            }
        }
    }
}

void
LteUeRrc::VarMeasReportListErase(uint8_t measId, ConcernedCells_t leavingCells, bool reportOnLeave)
{
    NS_LOG_FUNCTION(this << (uint16_t)measId);
    NS_ASSERT(!leavingCells.empty());

    auto measReportIt = m_varMeasReportList.find(measId);
    NS_ASSERT(measReportIt != m_varMeasReportList.end());

    for (auto it = leavingCells.begin(); it != leavingCells.end(); ++it)
    {
        measReportIt->second.cellsTriggeredList.erase(*it);
    }

    if (reportOnLeave)
    {
        // runs immediately without UE_MEASUREMENT_REPORT_DELAY
        SendMeasurementReport(measId);
    }

    if (measReportIt->second.cellsTriggeredList.empty())
    {
        measReportIt->second.periodicReportTimer.Cancel();
        m_varMeasReportList.erase(measReportIt);
    }

    auto leavingTriggerIt = m_leavingTriggerQueue.find(measId);
    NS_ASSERT(leavingTriggerIt != m_leavingTriggerQueue.end());
    if (!leavingTriggerIt->second.empty())
    {
        /*
         * Assumptions at this point:
         *  - the call to this function was delayed by time-to-trigger; and
         *  - the time-to-trigger delay is fixed (not adaptive/dynamic); and
         *  - the first element in the list is associated with this function call.
         */
        leavingTriggerIt->second.pop_front();

        if (!leavingTriggerIt->second.empty())
        {
            /*
             * To prevent the same set of cells triggering again in the future,
             * we clean up the time-to-trigger queue. This case might occur when
             * time-to-trigger > 200 ms.
             */
            for (auto it = leavingCells.begin(); it != leavingCells.end(); ++it)
            {
                CancelLeavingTrigger(measId, *it);
            }
        }
    }
}

void
LteUeRrc::VarMeasReportListClear(uint8_t measId)
{
    NS_LOG_FUNCTION(this << (uint16_t)measId);

    // remove the measurement reporting entry for this measId from the VarMeasReportList
    auto measReportIt = m_varMeasReportList.find(measId);
    if (measReportIt != m_varMeasReportList.end())
    {
        NS_LOG_LOGIC(this << " deleting existing report for measId " << (uint16_t)measId);
        measReportIt->second.periodicReportTimer.Cancel();
        m_varMeasReportList.erase(measReportIt);
    }

    CancelEnteringTrigger(measId);
    CancelLeavingTrigger(measId);
}

void
LteUeRrc::SendMeasurementReport(uint8_t measId)
{
    NS_LOG_FUNCTION(this << (uint16_t)measId);
    //  3GPP TS 36.331 section 5.5.5 Measurement reporting

    auto measIdIt = m_varMeasConfig.measIdList.find(measId);
    NS_ASSERT(measIdIt != m_varMeasConfig.measIdList.end());

    auto reportConfigIt = m_varMeasConfig.reportConfigList.find(measIdIt->second.reportConfigId);
    NS_ASSERT(reportConfigIt != m_varMeasConfig.reportConfigList.end());
    LteRrcSap::ReportConfigEutra& reportConfigEutra = reportConfigIt->second.reportConfigEutra;

    LteRrcSap::MeasurementReport measurementReport;
    LteRrcSap::MeasResults& measResults = measurementReport.measResults;
    measResults.measId = measId;

    auto measReportIt = m_varMeasReportList.find(measId);
    if (measReportIt == m_varMeasReportList.end())
    {
        NS_LOG_ERROR("no entry found in m_varMeasReportList for measId " << (uint32_t)measId);
    }
    else
    {
        auto servingMeasIt = m_storedMeasValues.find(m_cellId);
        NS_ASSERT(servingMeasIt != m_storedMeasValues.end());
        measResults.measResultPCell.rsrpResult =
            EutranMeasurementMapping::Dbm2RsrpRange(servingMeasIt->second.rsrp);
        measResults.measResultPCell.rsrqResult =
            EutranMeasurementMapping::Db2RsrqRange(servingMeasIt->second.rsrq);
        NS_LOG_INFO(this << " reporting serving cell "
                            "RSRP "
                         << +measResults.measResultPCell.rsrpResult << " ("
                         << servingMeasIt->second.rsrp
                         << " dBm) "
                            "RSRQ "
                         << +measResults.measResultPCell.rsrqResult << " ("
                         << servingMeasIt->second.rsrq << " dB)");

        measResults.haveMeasResultServFreqList = false;
        for (uint16_t componentCarrierId = 1; componentCarrierId < m_numberOfComponentCarriers;
             componentCarrierId++)
        {
            const uint16_t cellId = m_cphySapProvider.at(componentCarrierId)->GetCellId();
            auto measValuesIt = m_storedMeasValues.find(cellId);
            if (measValuesIt != m_storedMeasValues.end())
            {
                measResults.haveMeasResultServFreqList = true;
                LteRrcSap::MeasResultServFreq measResultServFreq;
                measResultServFreq.servFreqId = componentCarrierId;
                measResultServFreq.haveMeasResultSCell = true;
                measResultServFreq.measResultSCell.rsrpResult =
                    EutranMeasurementMapping::Dbm2RsrpRange(measValuesIt->second.rsrp);
                measResultServFreq.measResultSCell.rsrqResult =
                    EutranMeasurementMapping::Db2RsrqRange(measValuesIt->second.rsrq);
                measResultServFreq.haveMeasResultBestNeighCell = false;
                measResults.measResultServFreqList.push_back(measResultServFreq);
            }
        }

        measResults.haveMeasResultNeighCells = false;

        if (!(measReportIt->second.cellsTriggeredList.empty()))
        {
            std::multimap<double, uint16_t> sortedNeighCells;
            for (auto cellsTriggeredIt = measReportIt->second.cellsTriggeredList.begin();
                 cellsTriggeredIt != measReportIt->second.cellsTriggeredList.end();
                 ++cellsTriggeredIt)
            {
                uint16_t cellId = *cellsTriggeredIt;
                if (cellId != m_cellId)
                {
                    auto neighborMeasIt = m_storedMeasValues.find(cellId);
                    double triggerValue;
                    switch (reportConfigEutra.triggerQuantity)
                    {
                    case LteRrcSap::ReportConfigEutra::RSRP:
                        triggerValue = neighborMeasIt->second.rsrp;
                        break;
                    case LteRrcSap::ReportConfigEutra::RSRQ:
                        triggerValue = neighborMeasIt->second.rsrq;
                        break;
                    default:
                        NS_FATAL_ERROR("unsupported triggerQuantity");
                        break;
                    }
                    sortedNeighCells.insert(std::pair<double, uint16_t>(triggerValue, cellId));
                }
            }

            std::multimap<double, uint16_t>::reverse_iterator sortedNeighCellsIt;
            uint32_t count;
            for (sortedNeighCellsIt = sortedNeighCells.rbegin(), count = 0;
                 sortedNeighCellsIt != sortedNeighCells.rend() &&
                 count < reportConfigEutra.maxReportCells;
                 ++sortedNeighCellsIt, ++count)
            {
                uint16_t cellId = sortedNeighCellsIt->second;
                auto neighborMeasIt = m_storedMeasValues.find(cellId);
                NS_ASSERT(neighborMeasIt != m_storedMeasValues.end());
                LteRrcSap::MeasResultEutra measResultEutra;
                measResultEutra.physCellId = cellId;
                measResultEutra.haveCgiInfo = false;
                measResultEutra.haveRsrpResult = true;
                measResultEutra.rsrpResult =
                    EutranMeasurementMapping::Dbm2RsrpRange(neighborMeasIt->second.rsrp);
                measResultEutra.haveRsrqResult = true;
                measResultEutra.rsrqResult =
                    EutranMeasurementMapping::Db2RsrqRange(neighborMeasIt->second.rsrq);
                NS_LOG_INFO(this << " reporting neighbor cell "
                                 << (uint32_t)measResultEutra.physCellId << " RSRP "
                                 << (uint32_t)measResultEutra.rsrpResult << " ("
                                 << neighborMeasIt->second.rsrp << " dBm)"
                                 << " RSRQ " << (uint32_t)measResultEutra.rsrqResult << " ("
                                 << neighborMeasIt->second.rsrq << " dB)");
                measResults.measResultListEutra.push_back(measResultEutra);
                measResults.haveMeasResultNeighCells = true;
            }
        }
        else
        {
            NS_LOG_WARN(this << " cellsTriggeredList is empty");
        }

        /*
         * The current LteRrcSap implementation is broken in that it does not
         * allow for infinite values of reportAmount, which is probably the most
         * reasonable setting. So we just always assume infinite reportAmount.
         */
        measReportIt->second.numberOfReportsSent++;
        measReportIt->second.periodicReportTimer.Cancel();

        Time reportInterval;
        switch (reportConfigEutra.reportInterval)
        {
        case LteRrcSap::ReportConfigEutra::MS120:
            reportInterval = MilliSeconds(120);
            break;
        case LteRrcSap::ReportConfigEutra::MS240:
            reportInterval = MilliSeconds(240);
            break;
        case LteRrcSap::ReportConfigEutra::MS480:
            reportInterval = MilliSeconds(480);
            break;
        case LteRrcSap::ReportConfigEutra::MS640:
            reportInterval = MilliSeconds(640);
            break;
        case LteRrcSap::ReportConfigEutra::MS1024:
            reportInterval = MilliSeconds(1024);
            break;
        case LteRrcSap::ReportConfigEutra::MS2048:
            reportInterval = MilliSeconds(2048);
            break;
        case LteRrcSap::ReportConfigEutra::MS5120:
            reportInterval = MilliSeconds(5120);
            break;
        case LteRrcSap::ReportConfigEutra::MS10240:
            reportInterval = MilliSeconds(10240);
            break;
        case LteRrcSap::ReportConfigEutra::MIN1:
            reportInterval = Seconds(60);
            break;
        case LteRrcSap::ReportConfigEutra::MIN6:
            reportInterval = Seconds(360);
            break;
        case LteRrcSap::ReportConfigEutra::MIN12:
            reportInterval = Seconds(720);
            break;
        case LteRrcSap::ReportConfigEutra::MIN30:
            reportInterval = Seconds(1800);
            break;
        case LteRrcSap::ReportConfigEutra::MIN60:
            reportInterval = Seconds(3600);
            break;
        default:
            NS_FATAL_ERROR("Unsupported reportInterval "
                           << (uint16_t)reportConfigEutra.reportInterval);
            break;
        }

        // schedule the next measurement reporting
        measReportIt->second.periodicReportTimer =
            Simulator::Schedule(reportInterval, &LteUeRrc::SendMeasurementReport, this, measId);

        // send the measurement report to eNodeB
        m_rrcSapUser->SendMeasurementReport(measurementReport);
    }
}

void
LteUeRrc::StartConnection()
{
    NS_LOG_FUNCTION(this << m_imsi);
    NS_ASSERT(m_hasReceivedMib);
    NS_ASSERT(m_hasReceivedSib2);
    m_connectionPending = false; // reset the flag
    SwitchToState(IDLE_RANDOM_ACCESS);
    m_cmacSapProvider.at(0)->StartContentionBasedRandomAccessProcedure();
}

void
LteUeRrc::LeaveConnectedMode()
{
    NS_LOG_FUNCTION(this << m_imsi);
    m_leaveConnectedMode = true;
    m_storedMeasValues.clear();
    ResetRlfParams();

    for (auto measIdIt = m_varMeasConfig.measIdList.begin();
         measIdIt != m_varMeasConfig.measIdList.end();
         ++measIdIt)
    {
        VarMeasReportListClear(measIdIt->second.measId);
    }
    m_varMeasConfig.measIdList.clear();

    m_ccmRrcSapProvider->Reset();

    for (uint32_t i = 0; i < m_numberOfComponentCarriers; i++)
    {
        m_cmacSapProvider.at(i)->Reset(); // reset the MAC
    }

    m_drbMap.clear();
    m_bid2DrbidMap.clear();
    m_srb1 = nullptr;
    m_hasReceivedMib = false;
    m_hasReceivedSib1 = false;
    m_hasReceivedSib2 = false;

    for (uint32_t i = 0; i < m_numberOfComponentCarriers; i++)
    {
        m_cphySapProvider.at(i)->ResetPhyAfterRlf(); // reset the PHY
    }
    SwitchToState(IDLE_START);
    DoStartCellSelection(m_dlEarfcn);
    // Save the cell id UE was attached to
    StorePreviousCellId(m_cellId);
    m_cellId = 0;
    m_rnti = 0;
    m_srb0->m_rlc->SetRnti(m_rnti);
}

void
LteUeRrc::ConnectionTimeout()
{
    NS_LOG_FUNCTION(this << m_imsi);
    ++m_connEstFailCount;
    if (m_connEstFailCount >= m_connEstFailCountLimit)
    {
        m_connectionTimeoutTrace(m_imsi, m_cellId, m_rnti, m_connEstFailCount);
        SwitchToState(CONNECTED_PHY_PROBLEM);
        // Assumption: The eNB connection request timer would expire
        // before the expiration of T300 at UE. Upon which, the eNB deletes
        // the UE context. Therefore, here we don't need to send the UE context
        // deletion request to the eNB.
        m_asSapUser->NotifyConnectionReleased();
        m_connEstFailCount = 0;
    }
    else
    {
        for (uint16_t i = 0; i < m_numberOfComponentCarriers; i++)
        {
            m_cmacSapProvider.at(i)->Reset(); // reset the MAC
        }
        m_hasReceivedSib2 = false; // invalidate the previously received SIB2
        SwitchToState(IDLE_CAMPED_NORMALLY);
        m_connectionTimeoutTrace(m_imsi, m_cellId, m_rnti, m_connEstFailCount);
        // Following call to UE NAS will force the UE to immediately
        // perform the random access to the same cell again.
        m_asSapUser->NotifyConnectionFailed(); // inform upper layer
    }
}

void
LteUeRrc::DisposeOldSrb1()
{
    NS_LOG_FUNCTION(this);
    m_srb1Old = nullptr;
}

uint8_t
LteUeRrc::Bid2Drbid(uint8_t bid)
{
    auto it = m_bid2DrbidMap.find(bid);
    // NS_ASSERT_MSG (it != m_bid2DrbidMap.end (), "could not find BID " << bid);
    if (it == m_bid2DrbidMap.end())
    {
        return 0;
    }
    else
    {
        return it->second;
    }
}

void
LteUeRrc::SwitchToState(State newState)
{
    NS_LOG_FUNCTION(this << newState);
    State oldState = m_state;
    m_state = newState;
    NS_LOG_INFO(this << " IMSI " << m_imsi << " RNTI " << m_rnti << " UeRrc " << oldState << " --> "
                     << newState);
    m_stateTransitionTrace(m_imsi, m_cellId, m_rnti, oldState, newState);

    switch (newState)
    {
    case IDLE_START:
        if (m_leaveConnectedMode)
        {
            NS_LOG_INFO("Starting initial cell selection after RLF");
        }
        else
        {
            NS_FATAL_ERROR("cannot switch to an initial state");
        }
        break;

    case IDLE_CELL_SEARCH:
    case IDLE_WAIT_MIB_SIB1:
    case IDLE_WAIT_MIB:
    case IDLE_WAIT_SIB1:
        break;

    case IDLE_CAMPED_NORMALLY:
        if (m_connectionPending)
        {
            SwitchToState(IDLE_WAIT_SIB2);
        }
        break;

    case IDLE_WAIT_SIB2:
        if (m_hasReceivedSib2)
        {
            NS_ASSERT(m_connectionPending);
            StartConnection();
        }
        break;

    case IDLE_RANDOM_ACCESS:
    case IDLE_CONNECTING:
    case CONNECTED_NORMALLY:
    case CONNECTED_HANDOVER:
    case CONNECTED_PHY_PROBLEM:
    case CONNECTED_REESTABLISHING:
    default:
        break;
    }
}

void
LteUeRrc::RadioLinkFailureDetected()
{
    NS_LOG_FUNCTION(this << m_imsi << m_rnti);
    m_radioLinkFailureTrace(m_imsi, m_cellId, m_rnti);
    SwitchToState(CONNECTED_PHY_PROBLEM);
    m_rrcSapUser->SendIdealUeContextRemoveRequest(m_rnti);
    m_asSapUser->NotifyConnectionReleased();
}

void
LteUeRrc::DoNotifyInSync()
{
    NS_LOG_FUNCTION(this << m_imsi);
    m_noOfSyncIndications++;
    NS_LOG_INFO("noOfSyncIndications " << (uint16_t)m_noOfSyncIndications);
    m_phySyncDetectionTrace(m_imsi, m_rnti, m_cellId, "Notify in sync", m_noOfSyncIndications);
    if (m_noOfSyncIndications == m_n311)
    {
        ResetRlfParams();
    }
}

void
LteUeRrc::DoNotifyOutOfSync()
{
    NS_LOG_FUNCTION(this << m_imsi);
    m_noOfSyncIndications++;
    NS_LOG_INFO(this << " Total Number of Sync indications from PHY "
                     << (uint16_t)m_noOfSyncIndications << "N310 value : " << (uint16_t)m_n310);
    m_phySyncDetectionTrace(m_imsi, m_rnti, m_cellId, "Notify out of sync", m_noOfSyncIndications);
    if (m_noOfSyncIndications == m_n310)
    {
        m_radioLinkFailureDetected =
            Simulator::Schedule(m_t310, &LteUeRrc::RadioLinkFailureDetected, this);
        if (m_radioLinkFailureDetected.IsPending())
        {
            NS_LOG_INFO("t310 started");
        }
        m_cphySapProvider.at(0)->StartInSyncDetection();
        m_noOfSyncIndications = 0;
    }
}

void
LteUeRrc::DoResetSyncIndicationCounter()
{
    NS_LOG_FUNCTION(this << m_imsi);

    NS_LOG_DEBUG("The number of sync indication received by RRC from PHY: "
                 << (uint16_t)m_noOfSyncIndications);
    m_noOfSyncIndications = 0;
}

void
LteUeRrc::ResetRlfParams()
{
    NS_LOG_FUNCTION(this << m_imsi);
    m_radioLinkFailureDetected.Cancel();
    m_noOfSyncIndications = 0;
    m_cphySapProvider.at(0)->ResetRlfParams();
}

const std::string
LteUeRrc::ToString(LteUeRrc::State s)
{
    std::ostringstream ss;
    ss << s;
    return ss.str();
}

std::ostream&
operator<<(std::ostream& os, LteUeRrc::State state)
{
    switch (state)
    {
    case LteUeRrc::State::IDLE_START:
        return os << "IDLE_START";
    case LteUeRrc::State::IDLE_CELL_SEARCH:
        return os << "IDLE_CELL_SEARCH";
    case LteUeRrc::State::IDLE_WAIT_MIB_SIB1:
        return os << "IDLE_WAIT_MIB_SIB1";
    case LteUeRrc::State::IDLE_WAIT_MIB:
        return os << "IDLE_WAIT_MIB";
    case LteUeRrc::State::IDLE_WAIT_SIB1:
        return os << "IDLE_WAIT_SIB1";
    case LteUeRrc::State::IDLE_CAMPED_NORMALLY:
        return os << "IDLE_CAMPED_NORMALLY";
    case LteUeRrc::State::IDLE_WAIT_SIB2:
        return os << "IDLE_WAIT_SIB2";
    case LteUeRrc::State::IDLE_RANDOM_ACCESS:
        return os << "IDLE_RANDOM_ACCESS";
    case LteUeRrc::State::IDLE_CONNECTING:
        return os << "IDLE_CONNECTING";
    case LteUeRrc::State::CONNECTED_NORMALLY:
        return os << "CONNECTED_NORMALLY";
    case LteUeRrc::State::CONNECTED_HANDOVER:
        return os << "CONNECTED_HANDOVER";
    case LteUeRrc::State::CONNECTED_PHY_PROBLEM:
        return os << "CONNECTED_PHY_PROBLEM";
    case LteUeRrc::State::CONNECTED_REESTABLISHING:
        return os << "CONNECTED_REESTABLISHING";
    case LteUeRrc::State::NUM_STATES:
        return os << "NUM_STATES";
    };
    return os << "UNKNOWN(" << static_cast<uint32_t>(state) << ")";
}

} // namespace ns3
