/*
 * Copyright (c) 2005,2006 INRIA
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 *          Sébastien Deronne <sebastien.deronne@gmail.com>
 */

#include "interference-helper.h"

#include "error-rate-model.h"
#include "wifi-phy.h"
#include "wifi-psdu.h"
#include "wifi-utils.h"

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

#include <algorithm>
#include <numeric>

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("InterferenceHelper");

NS_OBJECT_ENSURE_REGISTERED(InterferenceHelper);

/****************************************************************
 *       PHY event class
 ****************************************************************/

Event::Event(Ptr<const WifiPpdu> ppdu,
             const WifiTxVector& txVector,
             Time duration,
             RxPowerWattPerChannelBand&& rxPower)
    : m_ppdu(ppdu),
      m_txVector(txVector),
      m_startTime(Simulator::Now()),
      m_endTime(m_startTime + duration),
      m_rxPowerW(std::move(rxPower))
{
}

Event::~Event()
{
    m_ppdu = nullptr;
    m_rxPowerW.clear();
}

Ptr<const WifiPpdu>
Event::GetPpdu() const
{
    return m_ppdu;
}

Time
Event::GetStartTime() const
{
    return m_startTime;
}

Time
Event::GetEndTime() const
{
    return m_endTime;
}

Time
Event::GetDuration() const
{
    return m_endTime - m_startTime;
}

double
Event::GetRxPowerW() const
{
    NS_ASSERT(m_rxPowerW.size() > 0);
    // The total RX power corresponds to the maximum over all the bands
    auto it = std::max_element(
        m_rxPowerW.begin(),
        m_rxPowerW.end(),
        [](const std::pair<WifiSpectrumBand, double>& p1,
           const std::pair<WifiSpectrumBand, double>& p2) { return p1.second < p2.second; });
    return it->second;
}

double
Event::GetRxPowerW(WifiSpectrumBand band) const
{
    auto it = m_rxPowerW.find(band);
    NS_ASSERT(it != m_rxPowerW.end());
    return it->second;
}

const RxPowerWattPerChannelBand&
Event::GetRxPowerWPerBand() const
{
    return m_rxPowerW;
}

const WifiTxVector&
Event::GetTxVector() const
{
    return m_txVector;
}

void
Event::UpdateRxPowerW(const RxPowerWattPerChannelBand& rxPower)
{
    NS_ASSERT(rxPower.size() == m_rxPowerW.size());
    // Update power band per band
    for (auto& currentRxPowerW : m_rxPowerW)
    {
        auto band = currentRxPowerW.first;
        auto it = rxPower.find(band);
        if (it != rxPower.end())
        {
            currentRxPowerW.second += it->second;
        }
    }
}

std::ostream&
operator<<(std::ostream& os, const Event& event)
{
    os << "start=" << event.GetStartTime() << ", end=" << event.GetEndTime()
       << ", TXVECTOR=" << event.GetTxVector() << ", power=" << event.GetRxPowerW() << "W"
       << ", PPDU=" << event.GetPpdu();
    return os;
}

/****************************************************************
 *       Class which records SNIR change events for a
 *       short period of time.
 ****************************************************************/

InterferenceHelper::NiChange::NiChange(double power, Ptr<Event> event)
    : m_power(power),
      m_event(event)
{
}

InterferenceHelper::NiChange::~NiChange()
{
    m_event = nullptr;
}

double
InterferenceHelper::NiChange::GetPower() const
{
    return m_power;
}

void
InterferenceHelper::NiChange::AddPower(double power)
{
    m_power += power;
}

Ptr<Event>
InterferenceHelper::NiChange::GetEvent() const
{
    return m_event;
}

/****************************************************************
 *       The actual InterferenceHelper
 ****************************************************************/

InterferenceHelper::InterferenceHelper()
    : m_errorRateModel(nullptr),
      m_numRxAntennas(1),
      m_rxing(false)
{
    NS_LOG_FUNCTION(this);
}

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

TypeId
InterferenceHelper::GetTypeId()
{
    static TypeId tid = TypeId("ns3::InterferenceHelper")
                            .SetParent<ns3::Object>()
                            .SetGroupName("Wifi")
                            .AddConstructor<InterferenceHelper>();
    return tid;
}

void
InterferenceHelper::DoDispose()
{
    NS_LOG_FUNCTION(this);
    RemoveBands();
    m_errorRateModel = nullptr;
}

Ptr<Event>
InterferenceHelper::Add(Ptr<const WifiPpdu> ppdu,
                        const WifiTxVector& txVector,
                        Time duration,
                        RxPowerWattPerChannelBand& rxPowerW,
                        bool isStartOfdmaRxing)
{
    Ptr<Event> event = Create<Event>(ppdu, txVector, duration, std::move(rxPowerW));
    AppendEvent(event, isStartOfdmaRxing);
    return event;
}

void
InterferenceHelper::AddForeignSignal(Time duration, RxPowerWattPerChannelBand& rxPowerW)
{
    // Parameters other than duration and rxPowerW are unused for this type
    // of signal, so we provide dummy versions
    WifiMacHeader hdr;
    hdr.SetType(WIFI_MAC_QOSDATA);
    hdr.SetQosTid(0);
    Ptr<WifiPpdu> fakePpdu =
        Create<WifiPpdu>(Create<WifiPsdu>(Create<Packet>(0), hdr), WifiTxVector(), 0);
    Add(fakePpdu, WifiTxVector(), duration, rxPowerW);
}

void
InterferenceHelper::RemoveBands()
{
    NS_LOG_FUNCTION(this);
    for (auto it : m_niChangesPerBand)
    {
        it.second.clear();
    }
    m_niChangesPerBand.clear();
    m_firstPowerPerBand.clear();
}

void
InterferenceHelper::AddBand(WifiSpectrumBand band)
{
    NS_LOG_FUNCTION(this << band.first << band.second);
    NS_ASSERT(m_niChangesPerBand.find(band) == m_niChangesPerBand.end());
    NiChanges niChanges;
    auto result = m_niChangesPerBand.insert({band, niChanges});
    NS_ASSERT(result.second);
    // Always have a zero power noise event in the list
    AddNiChangeEvent(Time(0), NiChange(0.0, nullptr), result.first);
    m_firstPowerPerBand.insert({band, 0.0});
}

void
InterferenceHelper::SetNoiseFigure(double value)
{
    m_noiseFigure = value;
}

void
InterferenceHelper::SetErrorRateModel(const Ptr<ErrorRateModel> rate)
{
    m_errorRateModel = rate;
}

Ptr<ErrorRateModel>
InterferenceHelper::GetErrorRateModel() const
{
    return m_errorRateModel;
}

void
InterferenceHelper::SetNumberOfReceiveAntennas(uint8_t rx)
{
    m_numRxAntennas = rx;
}

Time
InterferenceHelper::GetEnergyDuration(double energyW, WifiSpectrumBand band)
{
    Time now = Simulator::Now();
    auto niIt = m_niChangesPerBand.find(band);
    NS_ASSERT(niIt != m_niChangesPerBand.end());
    auto i = GetPreviousPosition(now, niIt);
    Time end = i->first;
    for (; i != niIt->second.end(); ++i)
    {
        double noiseInterferenceW = i->second.GetPower();
        end = i->first;
        if (noiseInterferenceW < energyW)
        {
            break;
        }
    }
    return end > now ? end - now : MicroSeconds(0);
}

void
InterferenceHelper::AppendEvent(Ptr<Event> event, bool isStartOfdmaRxing)
{
    NS_LOG_FUNCTION(this << event << isStartOfdmaRxing);
    for (const auto& it : event->GetRxPowerWPerBand())
    {
        WifiSpectrumBand band = it.first;
        auto niIt = m_niChangesPerBand.find(band);
        NS_ASSERT(niIt != m_niChangesPerBand.end());
        double previousPowerStart = 0;
        double previousPowerEnd = 0;
        auto previousPowerPosition = GetPreviousPosition(event->GetStartTime(), niIt);
        previousPowerStart = previousPowerPosition->second.GetPower();
        previousPowerEnd = GetPreviousPosition(event->GetEndTime(), niIt)->second.GetPower();
        if (!m_rxing)
        {
            m_firstPowerPerBand.find(band)->second = previousPowerStart;
            // Always leave the first zero power noise event in the list
            niIt->second.erase(++(niIt->second.begin()), ++previousPowerPosition);
        }
        else if (isStartOfdmaRxing)
        {
            // When the first UL-OFDMA payload is received, we need to set m_firstPowerPerBand
            // so that it takes into account interferences that arrived between the start of the
            // UL MU transmission and the start of UL-OFDMA payload.
            m_firstPowerPerBand.find(band)->second = previousPowerStart;
        }
        auto first =
            AddNiChangeEvent(event->GetStartTime(), NiChange(previousPowerStart, event), niIt);
        auto last = AddNiChangeEvent(event->GetEndTime(), NiChange(previousPowerEnd, event), niIt);
        for (auto i = first; i != last; ++i)
        {
            i->second.AddPower(it.second);
        }
    }
}

void
InterferenceHelper::UpdateEvent(Ptr<Event> event, const RxPowerWattPerChannelBand& rxPower)
{
    NS_LOG_FUNCTION(this << event);
    // This is called for UL MU events, in order to scale power as long as UL MU PPDUs arrive
    for (const auto& it : rxPower)
    {
        WifiSpectrumBand band = it.first;
        auto niIt = m_niChangesPerBand.find(band);
        NS_ASSERT(niIt != m_niChangesPerBand.end());
        auto first = GetPreviousPosition(event->GetStartTime(), niIt);
        auto last = GetPreviousPosition(event->GetEndTime(), niIt);
        for (auto i = first; i != last; ++i)
        {
            i->second.AddPower(it.second);
        }
    }
    event->UpdateRxPowerW(rxPower);
}

double
InterferenceHelper::CalculateSnr(double signal,
                                 double noiseInterference,
                                 uint16_t channelWidth,
                                 uint8_t nss) const
{
    NS_LOG_FUNCTION(this << signal << noiseInterference << channelWidth << +nss);
    // thermal noise at 290K in J/s = W
    static const double BOLTZMANN = 1.3803e-23;
    // Nt is the power of thermal noise in W
    double Nt = BOLTZMANN * 290 * channelWidth * 1e6;
    // receiver noise Floor (W) which accounts for thermal noise and non-idealities of the receiver
    double noiseFloor = m_noiseFigure * Nt;
    double noise = noiseFloor + noiseInterference;
    double snr = signal / noise; // linear scale
    NS_LOG_DEBUG("bandwidth(MHz)=" << channelWidth << ", signal(W)= " << signal << ", noise(W)="
                                   << noiseFloor << ", interference(W)=" << noiseInterference
                                   << ", snr=" << RatioToDb(snr) << "dB");
    if (m_errorRateModel->IsAwgn())
    {
        double gain = 1;
        if (m_numRxAntennas > nss)
        {
            gain = static_cast<double>(m_numRxAntennas) /
                   nss; // compute gain offered by diversity for AWGN
        }
        NS_LOG_DEBUG("SNR improvement thanks to diversity: " << 10 * std::log10(gain) << "dB");
        snr *= gain;
    }
    return snr;
}

double
InterferenceHelper::CalculateNoiseInterferenceW(Ptr<Event> event,
                                                NiChangesPerBand* nis,
                                                WifiSpectrumBand band) const
{
    NS_LOG_FUNCTION(this << band.first << band.second);
    auto firstPower_it = m_firstPowerPerBand.find(band);
    NS_ASSERT(firstPower_it != m_firstPowerPerBand.end());
    double noiseInterferenceW = firstPower_it->second;
    auto niIt = m_niChangesPerBand.find(band);
    NS_ASSERT(niIt != m_niChangesPerBand.end());
    auto it = niIt->second.find(event->GetStartTime());
    for (; it != niIt->second.end() && it->first < Simulator::Now(); ++it)
    {
        noiseInterferenceW = it->second.GetPower() - event->GetRxPowerW(band);
    }
    it = niIt->second.find(event->GetStartTime());
    NS_ASSERT(it != niIt->second.end());
    for (; it != niIt->second.end() && it->second.GetEvent() != event; ++it)
    {
        ;
    }
    NiChanges ni;
    ni.emplace(event->GetStartTime(), NiChange(0, event));
    while (++it != niIt->second.end() && it->second.GetEvent() != event)
    {
        ni.insert(*it);
    }
    ni.emplace(event->GetEndTime(), NiChange(0, event));
    nis->insert({band, ni});
    NS_ASSERT_MSG(noiseInterferenceW >= 0,
                  "CalculateNoiseInterferenceW returns negative value " << noiseInterferenceW);
    return noiseInterferenceW;
}

double
InterferenceHelper::CalculateChunkSuccessRate(double snir,
                                              Time duration,
                                              WifiMode mode,
                                              const WifiTxVector& txVector,
                                              WifiPpduField field) const
{
    if (duration.IsZero())
    {
        return 1.0;
    }
    uint64_t rate = mode.GetDataRate(txVector.GetChannelWidth());
    uint64_t nbits = static_cast<uint64_t>(rate * duration.GetSeconds());
    double csr =
        m_errorRateModel->GetChunkSuccessRate(mode, txVector, snir, nbits, m_numRxAntennas, field);
    return csr;
}

double
InterferenceHelper::CalculatePayloadChunkSuccessRate(double snir,
                                                     Time duration,
                                                     const WifiTxVector& txVector,
                                                     uint16_t staId) const
{
    if (duration.IsZero())
    {
        return 1.0;
    }
    WifiMode mode = txVector.GetMode(staId);
    uint64_t rate = mode.GetDataRate(txVector, staId);
    uint64_t nbits = static_cast<uint64_t>(rate * duration.GetSeconds());
    nbits /= txVector.GetNss(staId); // divide effective number of bits by NSS to achieve same chunk
                                     // error rate as SISO for AWGN
    double csr = m_errorRateModel->GetChunkSuccessRate(mode,
                                                       txVector,
                                                       snir,
                                                       nbits,
                                                       m_numRxAntennas,
                                                       WIFI_PPDU_FIELD_DATA,
                                                       staId);
    return csr;
}

double
InterferenceHelper::CalculatePayloadPer(Ptr<const Event> event,
                                        uint16_t channelWidth,
                                        NiChangesPerBand* nis,
                                        WifiSpectrumBand band,
                                        uint16_t staId,
                                        std::pair<Time, Time> window) const
{
    NS_LOG_FUNCTION(this << channelWidth << band.first << band.second << staId << window.first
                         << window.second);
    double psr = 1.0; /* Packet Success Rate */
    const auto& niIt = nis->find(band)->second;
    auto j = niIt.cbegin();
    Time previous = j->first;
    WifiMode payloadMode = event->GetTxVector().GetMode(staId);
    Time phyPayloadStart = j->first;
    if (event->GetPpdu()->GetType() != WIFI_PPDU_TYPE_UL_MU &&
        event->GetPpdu()->GetType() !=
            WIFI_PPDU_TYPE_DL_MU) // j->first corresponds to the start of the OFDMA payload
    {
        phyPayloadStart =
            j->first + WifiPhy::CalculatePhyPreambleAndHeaderDuration(event->GetTxVector());
    }
    Time windowStart = phyPayloadStart + window.first;
    Time windowEnd = phyPayloadStart + window.second;
    double noiseInterferenceW = m_firstPowerPerBand.find(band)->second;
    double powerW = event->GetRxPowerW(band);
    while (++j != niIt.cend())
    {
        Time current = j->first;
        NS_LOG_DEBUG("previous= " << previous << ", current=" << current);
        NS_ASSERT(current >= previous);
        double snr = CalculateSnr(powerW,
                                  noiseInterferenceW,
                                  channelWidth,
                                  event->GetTxVector().GetNss(staId));
        // Case 1: Both previous and current point to the windowed payload
        if (previous >= windowStart)
        {
            psr *= CalculatePayloadChunkSuccessRate(snr,
                                                    Min(windowEnd, current) - previous,
                                                    event->GetTxVector(),
                                                    staId);
            NS_LOG_DEBUG("Both previous and current point to the windowed payload: mode="
                         << payloadMode << ", psr=" << psr);
        }
        // Case 2: previous is before windowed payload and current is in the windowed payload
        else if (current >= windowStart)
        {
            psr *= CalculatePayloadChunkSuccessRate(snr,
                                                    Min(windowEnd, current) - windowStart,
                                                    event->GetTxVector(),
                                                    staId);
            NS_LOG_DEBUG(
                "previous is before windowed payload and current is in the windowed payload: mode="
                << payloadMode << ", psr=" << psr);
        }
        noiseInterferenceW = j->second.GetPower() - powerW;
        previous = j->first;
        if (previous > windowEnd)
        {
            NS_LOG_DEBUG("Stop: new previous=" << previous
                                               << " after time window end=" << windowEnd);
            break;
        }
    }
    double per = 1 - psr;
    return per;
}

double
InterferenceHelper::CalculatePhyHeaderSectionPsr(
    Ptr<const Event> event,
    NiChangesPerBand* nis,
    uint16_t channelWidth,
    WifiSpectrumBand band,
    PhyEntity::PhyHeaderSections phyHeaderSections) const
{
    NS_LOG_FUNCTION(this << band.first << band.second);
    double psr = 1.0; /* Packet Success Rate */
    auto niIt = nis->find(band)->second;
    auto j = niIt.begin();

    NS_ASSERT(!phyHeaderSections.empty());
    Time stopLastSection = Seconds(0);
    for (const auto& section : phyHeaderSections)
    {
        stopLastSection = Max(stopLastSection, section.second.first.second);
    }

    Time previous = j->first;
    double noiseInterferenceW = m_firstPowerPerBand.find(band)->second;
    double powerW = event->GetRxPowerW(band);
    while (++j != niIt.end())
    {
        Time current = j->first;
        NS_LOG_DEBUG("previous= " << previous << ", current=" << current);
        NS_ASSERT(current >= previous);
        double snr = CalculateSnr(powerW, noiseInterferenceW, channelWidth, 1);
        for (const auto& section : phyHeaderSections)
        {
            Time start = section.second.first.first;
            Time stop = section.second.first.second;

            if (previous <= stop || current >= start)
            {
                Time duration = Min(stop, current) - Max(start, previous);
                if (duration.IsStrictlyPositive())
                {
                    psr *= CalculateChunkSuccessRate(snr,
                                                     duration,
                                                     section.second.second,
                                                     event->GetTxVector(),
                                                     section.first);
                    NS_LOG_DEBUG("Current NI change in "
                                 << section.first << " [" << start << ", " << stop << "] for "
                                 << duration.As(Time::NS) << ": mode=" << section.second.second
                                 << ", psr=" << psr);
                }
            }
        }
        noiseInterferenceW = j->second.GetPower() - powerW;
        previous = j->first;
        if (previous > stopLastSection)
        {
            NS_LOG_DEBUG("Stop: new previous=" << previous << " after stop of last section="
                                               << stopLastSection);
            break;
        }
    }
    return psr;
}

double
InterferenceHelper::CalculatePhyHeaderPer(Ptr<const Event> event,
                                          NiChangesPerBand* nis,
                                          uint16_t channelWidth,
                                          WifiSpectrumBand band,
                                          WifiPpduField header) const
{
    NS_LOG_FUNCTION(this << band.first << band.second << header);
    auto niIt = nis->find(band)->second;
    auto phyEntity = WifiPhy::GetStaticPhyEntity(event->GetTxVector().GetModulationClass());

    PhyEntity::PhyHeaderSections sections;
    for (const auto& section :
         phyEntity->GetPhyHeaderSections(event->GetTxVector(), niIt.begin()->first))
    {
        if (section.first == header)
        {
            sections[header] = section.second;
        }
    }

    double psr = 1.0;
    if (!sections.empty() > 0)
    {
        psr = CalculatePhyHeaderSectionPsr(event, nis, channelWidth, band, sections);
    }
    return 1 - psr;
}

struct PhyEntity::SnrPer
InterferenceHelper::CalculatePayloadSnrPer(Ptr<Event> event,
                                           uint16_t channelWidth,
                                           WifiSpectrumBand band,
                                           uint16_t staId,
                                           std::pair<Time, Time> relativeMpduStartStop) const
{
    NS_LOG_FUNCTION(this << channelWidth << band.first << band.second << staId
                         << relativeMpduStartStop.first << relativeMpduStartStop.second);
    NiChangesPerBand ni;
    double noiseInterferenceW = CalculateNoiseInterferenceW(event, &ni, band);
    double snr = CalculateSnr(event->GetRxPowerW(band),
                              noiseInterferenceW,
                              channelWidth,
                              event->GetTxVector().GetNss(staId));

    /* calculate the SNIR at the start of the MPDU (located through windowing) and accumulate
     * all SNIR changes in the SNIR vector.
     */
    double per = CalculatePayloadPer(event, channelWidth, &ni, band, staId, relativeMpduStartStop);

    return PhyEntity::SnrPer(snr, per);
}

double
InterferenceHelper::CalculateSnr(Ptr<Event> event,
                                 uint16_t channelWidth,
                                 uint8_t nss,
                                 WifiSpectrumBand band) const
{
    NiChangesPerBand ni;
    double noiseInterferenceW = CalculateNoiseInterferenceW(event, &ni, band);
    double snr = CalculateSnr(event->GetRxPowerW(band), noiseInterferenceW, channelWidth, nss);
    return snr;
}

struct PhyEntity::SnrPer
InterferenceHelper::CalculatePhyHeaderSnrPer(Ptr<Event> event,
                                             uint16_t channelWidth,
                                             WifiSpectrumBand band,
                                             WifiPpduField header) const
{
    NS_LOG_FUNCTION(this << band.first << band.second << header);
    NiChangesPerBand ni;
    double noiseInterferenceW = CalculateNoiseInterferenceW(event, &ni, band);
    double snr = CalculateSnr(event->GetRxPowerW(band), noiseInterferenceW, channelWidth, 1);

    /* calculate the SNIR at the start of the PHY header and accumulate
     * all SNIR changes in the SNIR vector.
     */
    double per = CalculatePhyHeaderPer(event, &ni, channelWidth, band, header);

    return PhyEntity::SnrPer(snr, per);
}

void
InterferenceHelper::EraseEvents()
{
    for (auto niIt = m_niChangesPerBand.begin(); niIt != m_niChangesPerBand.end(); ++niIt)
    {
        niIt->second.clear();
        // Always have a zero power noise event in the list
        AddNiChangeEvent(Time(0), NiChange(0.0, nullptr), niIt);
        m_firstPowerPerBand.at(niIt->first) = 0.0;
    }
    m_rxing = false;
}

InterferenceHelper::NiChanges::iterator
InterferenceHelper::GetNextPosition(Time moment, NiChangesPerBand::iterator niIt)
{
    return niIt->second.upper_bound(moment);
}

InterferenceHelper::NiChanges::iterator
InterferenceHelper::GetPreviousPosition(Time moment, NiChangesPerBand::iterator niIt)
{
    auto it = GetNextPosition(moment, niIt);
    // This is safe since there is always an NiChange at time 0,
    // before moment.
    --it;
    return it;
}

InterferenceHelper::NiChanges::iterator
InterferenceHelper::AddNiChangeEvent(Time moment, NiChange change, NiChangesPerBand::iterator niIt)
{
    return niIt->second.insert(GetNextPosition(moment, niIt), std::make_pair(moment, change));
}

void
InterferenceHelper::NotifyRxStart()
{
    NS_LOG_FUNCTION(this);
    m_rxing = true;
}

void
InterferenceHelper::NotifyRxEnd(Time endTime)
{
    NS_LOG_FUNCTION(this << endTime);
    m_rxing = false;
    // Update m_firstPowerPerBand for frame capture
    for (auto niIt = m_niChangesPerBand.begin(); niIt != m_niChangesPerBand.end(); ++niIt)
    {
        NS_ASSERT(niIt->second.size() > 1);
        auto it = GetPreviousPosition(endTime, niIt);
        it--;
        m_firstPowerPerBand.find(niIt->first)->second = it->second.GetPower();
    }
}

} // namespace ns3
