#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() .SetGroupName("Tsn") .AddConstructor() .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(); m_tas->SetTsnNetDevice(this); } TsnNetDevice::~TsnNetDevice() { NS_LOG_FUNCTION(this); } void TsnNetDevice::SetNode(Ptr node) { m_node = node; } void TsnNetDevice::SetQueue(Ptr> q) { NS_LOG_FUNCTION(this << q); Ptr tsa = CreateObject(); tsa->SetQueue(q); tsa->SetTsnNetDevice(this); SetQueue(q, tsa); } void TsnNetDevice::SetQueue(Ptr> q, Ptr 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> q, Ptr 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); 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 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, const Address& source, const Address& dest, uint16_t ethertype) { NS_LOG_FUNCTION(this); Time hardwareLatency = Time(0); // Stream identification in-facing output std::vector> 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 #"<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> 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> seqRcvyTable = GetNode()->GetSequenceRecoveryTable(); for (uint32_t i = 0; i < seqRcvyTable.size(); i++) { bool IsApplicable(uint32_t streamHandle, Ptr 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 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 #"<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 #"<GetUid() << " OK !"); return true; } // Enqueue may fail (overflow) m_macTxDropTrace(packet); return false; } bool TsnNetDevice::SendWithSpecificFIFO(Ptr 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, 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 " <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 #"<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 #"<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 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 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 #"<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 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) { 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 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 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> 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> 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 fmids = streamFilterInstanceTable[i]->GetFlowMeterIds(); std::vector> 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 #"<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> 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> 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 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 TsnNetDevice::GetTas() { NS_LOG_FUNCTION(this); return m_tas; } }