Files
eden-sim/contrib/tsn/model/tsn-net-device.cc

701 lines
23 KiB
C++
Raw Normal View History

2025-12-01 15:56:02 +01:00
#include "tsn-net-device.h"
#include "ns3/log.h"
#include "ns3/packet.h"
#include "ns3/queue.h"
#include "ns3/ethernet-trailer.h"
#include "ns3/names.h"
#include "ns3/timestamp-tag.h"
#include "ns3/simulator.h"
#include "ns3/callback.h"
#include "ns3/error-model.h"
#include "ns3/ethernet-net-device.h"
#include "ns3/ethernet-channel.h"
#include "ns3/ethernet-header2.h"
#include "ns3/tsn-transmission-selection-algo.h"
#include "ns3/tas.h"
#include "ns3/transmission-gate.h"
#include "psfp-stream-filter-instance.h"
#include "psfp-flow-meter-instance.h"
namespace ns3
{
NS_LOG_COMPONENT_DEFINE("TsnNetDevice");
NS_OBJECT_ENSURE_REGISTERED(TsnNetDevice);
TypeId
TsnNetDevice::GetTypeId()
{
static TypeId tid =
TypeId("ns3::TsnNetDevice")
.SetParent<EthernetNetDevice>()
.SetGroupName("Tsn")
.AddConstructor<TsnNetDevice>()
.AddTraceSource("FrerDrop",
"Trace source indicating a packet was drop "
"by FRER recovery function",
MakeTraceSourceAccessor(&TsnNetDevice::m_frerDropTrace),
"ns3::Packet::TracedCallback")
.AddTraceSource("MaxSDUSizeFilterDrop",
"Trace source indicating a packet was drop "
"by the maxSDUSize filter",
MakeTraceSourceAccessor(&TsnNetDevice::m_maxSDUSizeFilterDropTrace),
"ns3::Packet::TracedCallback")
.AddTraceSource("REDFrameDrop",
"Trace source indicating a packet was drop "
"by the flow meter",
MakeTraceSourceAccessor(&TsnNetDevice::m_REDFrameDropTrace),
"ns3::Packet::TracedCallback");
return tid;
}
TsnNetDevice::TsnNetDevice(): EthernetNetDevice()
{
NS_LOG_FUNCTION(this);
m_tas = CreateObject<Tas>();
m_tas->SetTsnNetDevice(this);
}
TsnNetDevice::~TsnNetDevice()
{
NS_LOG_FUNCTION(this);
}
void
TsnNetDevice::SetNode(Ptr<TsnNode> node)
{
m_node = node;
}
void
TsnNetDevice::SetQueue(Ptr<Queue<Packet>> q)
{
NS_LOG_FUNCTION(this << q);
Ptr<TsnTransmissionSelectionAlgo> tsa = CreateObject<TsnTransmissionSelectionAlgo>();
tsa->SetQueue(q);
tsa->SetTsnNetDevice(this);
SetQueue(q, tsa);
}
void
TsnNetDevice::SetQueue(Ptr<Queue<Packet>> q, Ptr<TsnTransmissionSelectionAlgo> tsa)
{
NS_LOG_FUNCTION(this << q);
tsa->SetQueue(q);
m_queues.insert(m_queues.end(),q);
m_tas->AddTransmissionGate();
m_tas->AddTsa(tsa);
m_transmissionSelectionAlgos.insert(m_transmissionSelectionAlgos.end(), tsa);
}
void
TsnNetDevice::UpdateQueue(uint32_t queue_id, Ptr<Queue<Packet>> q, Ptr<TsnTransmissionSelectionAlgo> tsa)
{
NS_LOG_FUNCTION(this << q);
NS_ASSERT(queue_id < m_queues.size());
NS_ASSERT(queue_id < m_transmissionSelectionAlgos.size());
tsa->SetQueue(q);
m_tas->AddTransmissionGate();
m_tas->AddTsa(tsa);
m_queues[queue_id] = q;
m_transmissionSelectionAlgos[queue_id] = tsa;
}
int
TsnNetDevice::TransmitSelection()
{
NS_LOG_FUNCTION(this);
2025-12-02 11:43:04 +01:00
//Udpate Transmission selection algo states (e.g update cbs credit)
for (int i = m_queues.size(); i --> 0; )
{
m_transmissionSelectionAlgos[i]->Update();
}
2025-12-01 15:56:02 +01:00
for (int i = m_queues.size(); i --> 0; )
{
if(!m_queues[i]->IsEmpty() && m_tas->IsGateOpen(i))
{
if (m_transmissionSelectionAlgos[i]->IsReadyToTransmit())
{
if(m_tas->IsEnable())
{
Ptr<const Packet> p = m_queues[i]->Peek();
if(m_tas->IsSendable(p, m_bps, m_preambleAndSFDGap, m_interframeGap, m_mtu))
{
return(i);
}
}
else
{
return(i);
}
}
}
}
return(-1);
}
bool
TsnNetDevice::SendFrom(Ptr<Packet> packet,
const Address& source,
const Address& dest,
uint16_t ethertype)
{
NS_LOG_FUNCTION(this);
Time hardwareLatency = Time(0);
// Stream identification in-facing output
std::vector<Ptr<StreamIdEntry>> streamIdentityTable = GetNode()->GetStreamIdentityTable();
for (uint16_t i = 0; i < streamIdentityTable.size(); i++){
if (streamIdentityTable[i]->Match(this, true, false, packet)){
uint32_t streamHandle = streamIdentityTable[i]->GetStreamHandle();
hardwareLatency += streamIdentityTable[i]->GetHardwareLatency();
//FRER
// NS_LOG_INFO( "Stream handle of Pkt #"<<packet->GetUid() << " on output : " << streamHandle);
bool isSeqNumber = false;
uint16_t seqNumber = 0;
//Sequence decode => extract RTAG if this function is enable for this stream handle
std::vector<Ptr<SequenceEncodeDecodeFunction>> seqEncTable = GetNode()->GetSequenceEncodeDecodeTable();
for (uint32_t i = 0; i < seqEncTable.size(); i++)
{
if(seqEncTable[i]->IsApplicable(streamHandle, this, false))
{
isSeqNumber = true;
seqNumber = seqEncTable[i]->Decode(packet);
break;
}
}
//Individual Recovery if this function is enable for this stream handle
//TODO
//Sequence Recovery if this function is enable for this stream handle
std::vector<Ptr<SequenceRecoveryFunction>> seqRcvyTable = GetNode()->GetSequenceRecoveryTable();
for (uint32_t i = 0; i < seqRcvyTable.size(); i++)
{
bool IsApplicable(uint32_t streamHandle, Ptr<TsnNetDevice> port, bool direction, bool hasSeqNumber, bool individualRecovery);
if(seqRcvyTable[i]->IsApplicable(streamHandle, this, false, isSeqNumber, false))
{
if(!seqRcvyTable[i]->Recovery(seqNumber))
{
//Duplicate FRER packet detected ! ==> Drop
m_frerDropTrace(packet);
return false;
}
hardwareLatency += seqRcvyTable[i]->GetHardwareLatency();
break;
}
}
//Stream Transfert Function
//Nothing to implement in the simulator
//Sequence Generation if this function is enable for this stream handle
//TODO
//Stream splitting if this function is enable for this stream handle
//TODO
//Sequence encode => encode RTAG if this function is enable for this stream handle
//TODO
break;
}
}
//Stream identification out-facing output (active)
for (uint16_t i = 0; i < streamIdentityTable.size(); i++){
if (streamIdentityTable[i]->Match(this, false, false, packet)){
streamIdentityTable[i]->DoActiveUpdate(packet);
hardwareLatency += streamIdentityTable[i]->GetHardwareLatency();
break;
}
}
//
// If IsLinkUp() is false it means there is no channel to send any packet
// over so we just hit the drop trace on the packet and return an error.
//
if (!IsLinkUp())
{
m_macTxDropTrace(packet);
return false;
}
//Add timestamp if first emission
TimestampTag tag(Simulator::Now());
if (!packet->FindFirstMatchingByteTag(tag))
{
packet->AddByteTag(tag);
}
m_macTxTrace(packet);
m_macTxAnimationTrace(packet, this);
Ptr<Packet> pCopy = packet->Copy();
EthernetHeader2 header;
pCopy->RemoveHeader(header);
uint8_t pcp = header.GetPcp();
m_FIFOStateSnifferTrace(m_queues, m_txMachineState==BUSY, packet);
// NS_LOG_INFO(Names::FindName(this) << " "<< Simulator::Now().GetNanoSeconds()<<" Enqueue pkt #"<<packet->GetUid() << " (Vid : " << header.GetVid() << ") in FIFO #" << unsigned(pcp));
//
// We should enqueue and dequeue the packet to hit the tracing hooks.
//
if (m_queues[pcp]->Enqueue(packet))
{
//Add the event after all the already present at this exact instant to avoid
//the beginning of a transmission if a packet will be ready at the event just
//after but at the same time instant.
Simulator::Schedule(hardwareLatency, &TsnNetDevice::CheckForReadyPacket, this);
//NS_LOG_INFO(Names::FindName(this) << " Enqueue pkt #"<<packet->GetUid() << " OK !");
return true;
}
// Enqueue may fail (overflow)
m_macTxDropTrace(packet);
return false;
}
bool
TsnNetDevice::SendWithSpecificFIFO(Ptr<Packet> packet, const Address& dest, uint16_t ethertype, uint8_t pcp)
{
NS_LOG_FUNCTION(this);
return SendFrom(packet, GetAddress(), dest, ethertype, pcp);
}
bool
TsnNetDevice::SendFrom(Ptr<Packet> packet,
const Address& source,
const Address& dest,
uint16_t ethertype,
uint8_t pcp)
{
// This function is use to put GPTP frame in a specific FIFO without using QTAG
NS_LOG_FUNCTION(this);
NS_ASSERT_MSG(m_queues.size() > pcp, "Not enough fifos ( 0 to "<< m_queues.size()-1 <<") to handle this pcp priority ("<< unsigned(pcp) <<") on Node " <<Names::FindName(GetNode()));
AddHeader(packet, Mac48Address::ConvertFrom(dest), Mac48Address::ConvertFrom(source), ethertype);
EthernetTrailer trailer;
trailer.EnableFcs(true);
trailer.CalcFcs(packet);
packet->AddTrailer(trailer);
//
// If IsLinkUp() is false it means there is no channel to send any packet
// over so we just hit the drop trace on the packet and return an error.
//
if (!IsLinkUp())
{
m_macTxDropTrace(packet);
return false;
}
//Add timestamp if first emission
TimestampTag tag(Simulator::Now());
if (!packet->FindFirstMatchingByteTag(tag))
{
packet->AddByteTag(tag);
}
m_macTxTrace(packet);
m_FIFOStateSnifferTrace(m_queues, m_txMachineState==BUSY, packet);
// NS_LOG_INFO(Names::FindName(this) << " "<< Simulator::Now().GetNanoSeconds()<<" Enqueue pkt #"<<packet->GetUid() << " in FIFO #" << unsigned(pcp));
//
// We should enqueue and dequeue the packet to hit the tracing hooks.
//
if (m_queues[pcp]->Enqueue(packet))
{
//NS_LOG_INFO(Names::FindName(this) << " Enqueue pkt #"<<packet->GetUid() << " OK !");
//Add the event after all the already present at this exact instant to avoid
//the beginning of a transmission if a packet will be ready at the event just
//after but at the same time instant.
Simulator::Schedule(Time(0), &TsnNetDevice::CheckForReadyPacket, this);
return true;
}
// Enqueue may fail (overflow)
m_macTxDropTrace(packet);
return false;
}
bool
TsnNetDevice::TransmitStart(Ptr<Packet> p, int queue_id)
{
NS_LOG_FUNCTION(this << p);
NS_LOG_LOGIC("UID is " << p->GetUid() << ")");
//Timestamping at last bits emission
NS_ASSERT_MSG(m_node != nullptr, "m_node was not set using void TsnNetDevice::SetNode(Ptr<TsnNode> node)");
Time txTimestamp = m_node->GetTime();
// NS_LOG_INFO("On " << Names::FindName(this)<< " Tx start at " << Simulator::Now().GetNanoSeconds() << " recording : " << txTimestamp.GetNanoSeconds());
//Add a tag to compute delay between reception(or FIFO enter if first
//emission port) and transmission start
TimestampTag tag(Simulator::Now());
p->AddByteTag(tag);
//
// This function is called to start the process of transmitting a packet.
// We need to tell the channel that we've started wiggling the wire and
// schedule an event that will be executed when the transmission is complete.
//
NS_ASSERT_MSG(m_txMachineState == READY, "Must be READY to transmit");
m_txMachineState = BUSY;
m_txHardwareLatencyExperienced = false;
m_currentPkt = p;
m_phyTxBeginTrace(m_currentPkt);
Time preambleAndSFDGapTime = m_bps.CalculateBytesTxTime(m_preambleAndSFDGap);
Time txTime = m_bps.CalculateBytesTxTime(p->GetSize());
Time interpacketGapTime = m_bps.CalculateBytesTxTime(m_interframeGap);
Time txCompleteTime = preambleAndSFDGapTime + txTime + interpacketGapTime;
//NS_LOG_INFO(Names::FindName(this) << " "<< Simulator::Now().GetNanoSeconds()<<" Transmit pkt #"<<p->GetUid());
Simulator::Schedule(txCompleteTime, &TsnTransmissionSelectionAlgo::TransmitComplete, m_transmissionSelectionAlgos[queue_id], p);
m_transmissionSelectionAlgos[queue_id]->TransmitStart(p, txTime);
NS_LOG_LOGIC("Schedule TransmitCompleteEvent in " << txCompleteTime.As(Time::S));
Simulator::Schedule(txCompleteTime, &TsnNetDevice::TransmitComplete, this, queue_id);
bool result = m_channel->TransmitStart(p, this, txCompleteTime-interpacketGapTime); //Pkt don't need to wait for the end of Interpacket gap to be considered as receive on the other end of the link
if (!result)
{
m_phyTxDropTrace(p);
}
if(!m_rxCallbackWithTimestamp.IsNull()){
m_txCallbackWithTimestamp(p, txTimestamp);
}
return result;
}
void
TsnNetDevice::TransmitComplete(int queue_id)
{
NS_LOG_FUNCTION(this);
// This function is called to when we're all done transmitting a packet.
// We try and pull another packet off of the transmit queue. If the queue
// is empty, we are done, otherwise we need to start transmitting the
// next packet.
//
NS_ASSERT_MSG(m_txMachineState == BUSY, "Must be BUSY if transmitting");
m_txMachineState = READY;
NS_ASSERT_MSG(m_currentPkt, "TsnNetDevice::TransmitComplete(): m_currentPkt zero");
m_phyTxEndTrace(m_currentPkt);
//m_transmissionSelectionAlgos[queue_id]->TransmitComplete(m_currentPkt);
m_currentPkt = nullptr;
//Add the event after all the already present at this exact instant to avoid
//the beginning of a transmission if a packet will be ready at the event just
//after but at the same time instant.
Simulator::Schedule(Time(0), &TsnNetDevice::CheckForReadyPacket, this);
}
void
TsnNetDevice::CheckForReadyPacket()
{
NS_LOG_FUNCTION(this);
if (m_txMachineState != READY || m_txHardwareLatencyExperienced)
{
return;
}
int next_queue_id = TransmitSelection();
if(next_queue_id==-1)
{
NS_LOG_LOGIC("No pending packets in device queue after tx complete");
return;
}
//
// Got another packet off of the queue, so start the transmit process again.
//
Ptr<Packet> p = m_queues[next_queue_id]->Dequeue();
m_snifferTrace(p);
m_promiscSnifferTrace(p);
m_txHardwareLatencyExperienced = true;
Time hardwareLatency = m_transmissionSelectionAlgos[next_queue_id]->GetHardwareLatency() + m_tas->GetHardwareLatency();
Simulator::Schedule(hardwareLatency, &TsnNetDevice::TransmitStart, this, p, next_queue_id);
}
void
TsnNetDevice::Receive(Ptr<Packet> packet)
{
NS_LOG_FUNCTION(this << packet);
NS_LOG_LOGIC("UID is " << packet->GetUid());
//Timestamping at last bits receiption thus compensate for the duration between first bit and last
NS_ASSERT_MSG(m_node != nullptr, "m_node was not set using void TsnNetDevice::SetNode(Ptr<TsnNode> node)");
Time rxTimestamp = m_node->GetTime() - (m_bps.CalculateBytesTxTime(m_preambleAndSFDGap) + m_bps.CalculateBytesTxTime(packet->GetSize()));
Time hardwareLatency = Time(0);
// NS_LOG_INFO("On " << Names::FindName(this)<< " Rx end at " << (Simulator::Now()- (m_bps.CalculateBytesTxTime(m_preambleAndSFDGap) + m_bps.CalculateBytesTxTime(packet->GetSize()))).GetNanoSeconds() << " recording : " << rxTimestamp.GetNanoSeconds());
if (m_receiveErrorModel && m_receiveErrorModel->IsCorrupt(packet))
{
//
// If we have an error model and it indicates that it is time to lose a
// corrupted packet, don't forward this packet up, let it go.
//
NS_LOG_LOGIC("Dropping pkt due to error model ");
m_phyRxDropTrace(packet);
}
else
{
//Add timestamp to measure delay in the switches
TimestampTag tag(Simulator::Now());
packet->AddByteTag(tag);
//
// Hit the trace hooks. All of these hooks are in the same place in this
// device because it is so simple, but this is not usually the case in
// more complicated devices.
m_snifferTrace(packet);
m_promiscSnifferTrace(packet);
m_phyRxEndTrace(packet);
//
// Trace sinks will expect complete packets, not packets without some of the
// headers.
//
Ptr<Packet> originalPacket = packet->Copy();
//Check checksum
EthernetTrailer trailer;
packet->RemoveTrailer(trailer);
if (Node::ChecksumEnabled())
{
trailer.EnableFcs(true);
}
bool crcGood = trailer.CheckFcs(packet);
if (!crcGood)
{
NS_LOG_INFO("CRC error on Packet " << packet);
m_phyRxDropTrace(packet);
return;
}
//
// Strip off the ethernet protocol header and forward this packet
// up the protocol stack. Since this is a simple point-to-point link,
// there is no difference in what the promisc callback sees and what the
// normal receive callback sees.
//
EthernetHeader2 header;
packet->RemoveHeader(header);
uint16_t protocol = header.GetEthertype();
uint8_t priority = header.GetPcp();
// Stream identification out-facing input
std::vector<Ptr<StreamIdEntry>> streamIdentityTable = GetNode()->GetStreamIdentityTable();
for (uint32_t i = 0; i < streamIdentityTable.size(); i++){
if (streamIdentityTable[i]->Match(this, false, true, originalPacket)){
uint32_t streamHandle = streamIdentityTable[i]->GetStreamHandle();
hardwareLatency += streamIdentityTable[i]->GetHardwareLatency();
//PSFP
std::vector<Ptr<StreamFilterInstance>> streamFilterInstanceTable = GetNode()->GetStreamFilterInstanceTable();
for (uint16_t i = 0; i < streamFilterInstanceTable.size(); i++){
if (streamFilterInstanceTable[i]->Match(streamHandle, priority)){
//MaxSDUSizeFilter
if (streamFilterInstanceTable[i]->MaxSDUSizeFilter(originalPacket)){
m_maxSDUSizeFilterDropTrace(originalPacket);
return;
}
//TODO Stream Gate
//Flow Meter
std::vector<uint16_t> fmids = streamFilterInstanceTable[i]->GetFlowMeterIds();
std::vector<Ptr<FlowMeterInstance>> flowMeterInstanceTable = GetNode()->GetFlowMeterInstanceTable();
bool pass = true;
for (uint16_t j = 0; j < fmids.size(); j++){
if(flowMeterInstanceTable[fmids[j]]->Test(originalPacket)){
pass = false;
}
}
if (not pass){
streamFilterInstanceTable[i]->increaseRedFrameCount();
m_REDFrameDropTrace(originalPacket);
return;
}
break;
}
}
//FRER
// NS_LOG_INFO( "Stream handle of Pkt #"<<packet->GetUid() << " on input : " << streamHandle);
bool isSeqNumber = false;
uint16_t seqNumber = 0;
//Sequence decode => extract RTAG if this function is enable for this stream handle
//TODO
//Individual Recovery if this function is enable for this stream handle
//TODO
//Sequence Recovery if this function is enable for this stream handle
//TODO
//Stream Transfert Function
//Nothing to implement in the simulator
//Sequence Generation if this function is enable for this stream handle
std::vector<Ptr<SequenceGenerationFunction>> seqGenTable = GetNode()->GetSequenceGenerationTable();
for (uint32_t i = 0; i < seqGenTable.size(); i++)
{
if(seqGenTable[i]->IsApplicable(streamHandle))
{
isSeqNumber = true;
seqNumber = seqGenTable[i]->GetSequenceNumber();
break;
}
}
//Stream splitting if this function is enable for this stream handle
//TODO
//Sequence encode => encode RTAG if this function is enable for this stream handle
if (isSeqNumber)
{
std::vector<Ptr<SequenceEncodeDecodeFunction>> seqEncTable = GetNode()->GetSequenceEncodeDecodeTable();
for (uint32_t i = 0; i < seqEncTable.size(); i++)
{
if(seqEncTable[i]->IsApplicable(streamHandle, this, false))
{
seqEncTable[i]->Encode(originalPacket, seqNumber);
break;
}
}
}
break;
}
}
//
// Classify the packet based on its destination.
//
PacketType packetType;
if (header.GetDest() == m_address)
{
packetType = PACKET_HOST;
}
else if (header.GetDest().IsBroadcast())
{
packetType = PACKET_BROADCAST;
}
else if (header.GetDest().IsGroup())
{
packetType = PACKET_MULTICAST;
}
else
{
packetType = PACKET_OTHERHOST;
}
if (!m_promiscCallback.IsNull())
{
Simulator::Schedule(hardwareLatency,
&TsnNetDevice::m_macPromiscRxTrace,
this,
originalPacket->Copy());
Simulator::Schedule(hardwareLatency,
&TsnNetDevice::m_promiscCallback,
this,
this,
originalPacket,
protocol,
header.GetSrc(),
header.GetDest(),
packetType);
}
//
// If this packet is not destined for some other host, it must be for us
// as either a broadcast, multicast or unicast. We need to hit the mac
// packet received trace hook and forward the packet up the stack.
//
if (packetType != PACKET_OTHERHOST)
{
//NS_LOG_INFO((Simulator::Now()).As(Time::S) << " \t" << Names::FindName(m_node) << "/" << Names::FindName(this) <<" : Pkt #" << originalPacket->GetUid() <<" received by the netDevice ! " << originalPacket->ToString());
m_macRxTrace(originalPacket);
m_rxCallback(this, packet, protocol, header.GetSrc());
if(!m_rxCallbackWithTimestamp.IsNull())
{
m_rxCallbackWithTimestamp(this, packet, protocol, header.GetSrc(), rxTimestamp);
}
m_latencyTrace(originalPacket);
}
}
}
Ptr<TsnNode>
TsnNetDevice::GetNode()
{
return m_node;
}
void
TsnNetDevice::SetReceiveCallbackWithTimestamp(ReceiveCallbackWithTimestamp cb)
{
m_rxCallbackWithTimestamp = cb;
}
void
TsnNetDevice::SetTransmitCallbackWithTimestamp(TransmitCallbackWithTimestamp cb)
{
m_txCallbackWithTimestamp = cb;
}
void
TsnNetDevice::AddGclEntry(Time duration, uint8_t states)
{
NS_LOG_FUNCTION(this);
m_tas->AddGclEntry(duration, states);
}
void
TsnNetDevice::StartTas()
{
NS_LOG_FUNCTION(this);
StartTas(Time(0));
}
void
TsnNetDevice::StartTas(Time startTime)
{
NS_LOG_FUNCTION(this);
Simulator::Schedule(startTime, &Tas::Start, m_tas);
}
Ptr<Tas>
TsnNetDevice::GetTas()
{
NS_LOG_FUNCTION(this);
return m_tas;
}
}