LEFT | RIGHT |
1 /* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */ | 1 /* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */ |
2 /* | 2 /* |
3 * Copyright (c) 2011 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC) | 3 * Copyright (c) 2011 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC) |
4 * | 4 * |
5 * This program is free software; you can redistribute it and/or modify | 5 * This program is free software; you can redistribute it and/or modify |
6 * it under the terms of the GNU General Public License version 2 as | 6 * it under the terms of the GNU General Public License version 2 as |
7 * published by the Free Software Foundation; | 7 * published by the Free Software Foundation; |
8 * | 8 * |
9 * This program is distributed in the hope that it will be useful, | 9 * This program is distributed in the hope that it will be useful, |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
182 | 182 |
183 | 183 |
184 /** | 184 /** |
185 * MAC SAP | 185 * MAC SAP |
186 */ | 186 */ |
187 | 187 |
188 void | 188 void |
189 LteRlcAm::DoNotifyTxOpportunity (uint32_t bytes, uint8_t layer, uint8_t harqId,
uint8_t componentCarrierId, uint16_t rnti, uint8_t lcid) | 189 LteRlcAm::DoNotifyTxOpportunity (uint32_t bytes, uint8_t layer, uint8_t harqId,
uint8_t componentCarrierId, uint16_t rnti, uint8_t lcid) |
190 { | 190 { |
191 NS_LOG_FUNCTION (this << m_rnti << (uint32_t) m_lcid << bytes); | 191 NS_LOG_FUNCTION (this << m_rnti << (uint32_t) m_lcid << bytes); |
192 NS_UNUSED(rnti); | 192 NS_UNUSED (rnti); |
193 NS_UNUSED(lcid); | 193 NS_UNUSED (lcid); |
194 | 194 |
195 if (bytes < 4) | 195 if (bytes < 4) |
196 { | 196 { |
197 // Stingy MAC: In general, we need more bytes. | 197 // Stingy MAC: In general, we need more bytes. |
198 // There are a more restrictive test for each particular case | 198 // There are a more restrictive test for each particular case |
199 NS_LOG_LOGIC ("TxOpportunity (size = " << bytes << ") too small"); | 199 NS_LOG_LOGIC ("TxOpportunity (size = " << bytes << ") too small"); |
200 NS_ASSERT_MSG (false, "TxOpportunity (size = " << bytes << ") too small.\n
" | 200 NS_ASSERT_MSG (false, "TxOpportunity (size = " << bytes << ") too small.\n
" |
201 << "Your MAC scheduler is assigned too few resource blo
cks."); | 201 << "Your MAC scheduler is assigned too few resource blo
cks."); |
202 return; | 202 return; |
203 } | 203 } |
(...skipping 15 matching lines...) Expand all Loading... |
219 LteRlcAmHeader rlcAmHeader; | 219 LteRlcAmHeader rlcAmHeader; |
220 rlcAmHeader.SetControlPdu (LteRlcAmHeader::STATUS_PDU); | 220 rlcAmHeader.SetControlPdu (LteRlcAmHeader::STATUS_PDU); |
221 ····· | 221 ····· |
222 NS_LOG_LOGIC ("Check for SNs to NACK from " << m_vrR.GetValue() << " to "
<< m_vrMs.GetValue()); | 222 NS_LOG_LOGIC ("Check for SNs to NACK from " << m_vrR.GetValue() << " to "
<< m_vrMs.GetValue()); |
223 SequenceNumber10 sn; | 223 SequenceNumber10 sn; |
224 sn.SetModulusBase (m_vrR); | 224 sn.SetModulusBase (m_vrR); |
225 std::map<uint16_t, PduBuffer>::iterator pduIt; | 225 std::map<uint16_t, PduBuffer>::iterator pduIt; |
226 for (sn = m_vrR; sn < m_vrMs; sn++)· | 226 for (sn = m_vrR; sn < m_vrMs; sn++)· |
227 { | 227 { |
228 NS_LOG_LOGIC ("SN = " << sn);·········· | 228 NS_LOG_LOGIC ("SN = " << sn);·········· |
229 if (!rlcAmHeader.OneMoreNackWouldFitIn (static_cast<uint16_t>(bytes))) | 229 if (!rlcAmHeader.OneMoreNackWouldFitIn (static_cast<uint16_t> (bytes))
) |
230 { | 230 { |
231 NS_LOG_LOGIC ("Can't fit more NACKs in STATUS PDU"); | 231 NS_LOG_LOGIC ("Can't fit more NACKs in STATUS PDU"); |
232 break; | 232 break; |
233 }·········· | 233 }·········· |
234 pduIt = m_rxonBuffer.find (sn.GetValue ()); | 234 pduIt = m_rxonBuffer.find (sn.GetValue ()); |
235 if (pduIt == m_rxonBuffer.end () || (!(pduIt->second.m_pduComplete))) | 235 if (pduIt == m_rxonBuffer.end () || (!(pduIt->second.m_pduComplete))) |
236 { | 236 { |
237 NS_LOG_LOGIC ("adding NACK_SN " << sn.GetValue ()); | 237 NS_LOG_LOGIC ("adding NACK_SN " << sn.GetValue ()); |
238 rlcAmHeader.PushNack (sn.GetValue ());·············· | 238 rlcAmHeader.PushNack (sn.GetValue ());·············· |
239 }·········· | 239 }·········· |
(...skipping 340 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
580 NS_LOG_LOGIC (" IF firstSegment < NextSegmentSize && txonBuffer.siz
e > 0"); | 580 NS_LOG_LOGIC (" IF firstSegment < NextSegmentSize && txonBuffer.siz
e > 0"); |
581 // Add txBuffer.FirstBuffer to DataField | 581 // Add txBuffer.FirstBuffer to DataField |
582 dataFieldAddedSize = firstSegment->GetSize (); | 582 dataFieldAddedSize = firstSegment->GetSize (); |
583 dataFieldTotalSize += dataFieldAddedSize; | 583 dataFieldTotalSize += dataFieldAddedSize; |
584 dataField.push_back (firstSegment); | 584 dataField.push_back (firstSegment); |
585 | 585 |
586 // ExtensionBit (Next_Segment - 1) = 1 | 586 // ExtensionBit (Next_Segment - 1) = 1 |
587 rlcAmHeader.PushExtensionBit (LteRlcAmHeader::E_LI_FIELDS_FOLLOWS); | 587 rlcAmHeader.PushExtensionBit (LteRlcAmHeader::E_LI_FIELDS_FOLLOWS); |
588 | 588 |
589 // LengthIndicator (Next_Segment) = txBuffer.FirstBuffer.length() | 589 // LengthIndicator (Next_Segment) = txBuffer.FirstBuffer.length() |
590 rlcAmHeader.PushLengthIndicator (static_cast<uint16_t>(firstSegment->G
etSize ())); | 590 rlcAmHeader.PushLengthIndicator (static_cast<uint16_t> (firstSegment->
GetSize ())); |
591 | 591 |
592 nextSegmentSize -= ((nextSegmentId % 2) ? (2) : (1)) + dataFieldAddedS
ize; | 592 nextSegmentSize -= ((nextSegmentId % 2) ? (2) : (1)) + dataFieldAddedS
ize; |
593 nextSegmentId++; | 593 nextSegmentId++; |
594 | 594 |
595 NS_LOG_LOGIC (" SDUs in TxBuffer = " << m_txonBuffer.size ()); | 595 NS_LOG_LOGIC (" SDUs in TxBuffer = " << m_txonBuffer.size ()); |
596 if (m_txonBuffer.size () > 0) | 596 if (m_txonBuffer.size () > 0) |
597 { | 597 { |
598 NS_LOG_LOGIC (" First SDU buffer = " << *(m_txonBuffer.beg
in())); | 598 NS_LOG_LOGIC (" First SDU buffer = " << *(m_txonBuffer.beg
in())); |
599 NS_LOG_LOGIC (" First SDU size = " << (*(m_txonBuffer.be
gin()))->GetSize ()); | 599 NS_LOG_LOGIC (" First SDU size = " << (*(m_txonBuffer.be
gin()))->GetSize ()); |
600 } | 600 } |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
747 LteRlcAm::DoNotifyHarqDeliveryFailure () | 747 LteRlcAm::DoNotifyHarqDeliveryFailure () |
748 { | 748 { |
749 NS_LOG_FUNCTION (this); | 749 NS_LOG_FUNCTION (this); |
750 } | 750 } |
751 | 751 |
752 | 752 |
753 void | 753 void |
754 LteRlcAm::DoReceivePdu (Ptr<Packet> p, uint16_t rnti, uint8_t lcid) | 754 LteRlcAm::DoReceivePdu (Ptr<Packet> p, uint16_t rnti, uint8_t lcid) |
755 { | 755 { |
756 NS_LOG_FUNCTION (this << m_rnti << (uint32_t) m_lcid << p->GetSize ()); | 756 NS_LOG_FUNCTION (this << m_rnti << (uint32_t) m_lcid << p->GetSize ()); |
757 NS_UNUSED(rnti); | 757 NS_UNUSED (rnti); |
758 NS_UNUSED(lcid); | 758 NS_UNUSED (lcid); |
759 | 759 |
760 // Receiver timestamp | 760 // Receiver timestamp |
761 RlcTag rlcTag; | 761 RlcTag rlcTag; |
762 Time delay; | 762 Time delay; |
763 NS_ASSERT_MSG (p->PeekPacketTag (rlcTag), "RlcTag is missing"); | 763 NS_ASSERT_MSG (p->PeekPacketTag (rlcTag), "RlcTag is missing"); |
764 p->RemovePacketTag (rlcTag); | 764 p->RemovePacketTag (rlcTag); |
765 delay = Simulator::Now() - rlcTag.GetSenderTimestamp (); | 765 delay = Simulator::Now() - rlcTag.GetSenderTimestamp (); |
766 m_rxPdu (m_rnti, m_lcid, p->GetSize (), delay.GetNanoSeconds ()); | 766 m_rxPdu (m_rnti, m_lcid, p->GetSize (), delay.GetNanoSeconds ()); |
767 | 767 |
768 // Get RLC header parameters | 768 // Get RLC header parameters |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
889 NS_ASSERT (it->second.m_byteSegments.size () > 0); | 889 NS_ASSERT (it->second.m_byteSegments.size () > 0); |
890 NS_ASSERT_MSG (it->second.m_byteSegments.size () == 1, "re-segment
ation not supported"); | 890 NS_ASSERT_MSG (it->second.m_byteSegments.size () == 1, "re-segment
ation not supported"); |
891 NS_LOG_LOGIC ("PDU segment already received, discarded"); | 891 NS_LOG_LOGIC ("PDU segment already received, discarded"); |
892 } | 892 } |
893 else | 893 else |
894 { | 894 { |
895 NS_LOG_LOGIC ("Place PDU in the reception buffer ( SN = " << seqNu
mber << " )"); | 895 NS_LOG_LOGIC ("Place PDU in the reception buffer ( SN = " << seqNu
mber << " )"); |
896 m_rxonBuffer[ seqNumber.GetValue () ].m_byteSegments.push_back (p)
; | 896 m_rxonBuffer[ seqNumber.GetValue () ].m_byteSegments.push_back (p)
; |
897 m_rxonBuffer[ seqNumber.GetValue () ].m_pduComplete = true; | 897 m_rxonBuffer[ seqNumber.GetValue () ].m_pduComplete = true; |
898 } | 898 } |
| 899 |
| 900 |
899 } | 901 } |
900 | 902 |
901 // 5.1.3.2.3 Actions when a RLC data PDU is placed in the reception buffer | 903 // 5.1.3.2.3 Actions when a RLC data PDU is placed in the reception buffer |
902 // When a RLC data PDU with SN = x is placed in the reception buffer, | 904 // When a RLC data PDU with SN = x is placed in the reception buffer, |
903 // the receiving side of an AM RLC entity shall: | 905 // the receiving side of an AM RLC entity shall: |
904 | 906 |
905 // - if x >= VR(H) | 907 // - if x >= VR(H) |
906 // - update VR(H) to x+ 1; | 908 // - update VR(H) to x+ 1; |
907 | 909 |
908 if ( seqNumber >= m_vrH ) | 910 if ( seqNumber >= m_vrH ) |
(...skipping 25 matching lines...) Expand all Loading... |
934 } | 936 } |
935 | 937 |
936 // - if x = VR(R): | 938 // - if x = VR(R): |
937 // - if all byte segments of the AMD PDU with SN = VR(R) are received: | 939 // - if all byte segments of the AMD PDU with SN = VR(R) are received: |
938 // - update VR(R) to the SN of the first AMD PDU with SN > current
VR(R) for which not all byte segments have been received; | 940 // - update VR(R) to the SN of the first AMD PDU with SN > current
VR(R) for which not all byte segments have been received; |
939 // - update VR(MR) to the updated VR(R) + AM_Window_Size; | 941 // - update VR(MR) to the updated VR(R) + AM_Window_Size; |
940 // - reassemble RLC SDUs from any byte segments of AMD PDUs with SN th
at falls outside of the receiving window and in-sequence byte segments of the AM
D PDU with SN = VR(R), remove RLC headers when doing so and deliver the reassemb
led RLC SDUs to upper layer in sequence if not delivered before; | 942 // - reassemble RLC SDUs from any byte segments of AMD PDUs with SN th
at falls outside of the receiving window and in-sequence byte segments of the AM
D PDU with SN = VR(R), remove RLC headers when doing so and deliver the reassemb
led RLC SDUs to upper layer in sequence if not delivered before; |
941 | 943 |
942 if ( seqNumber == m_vrR ) | 944 if ( seqNumber == m_vrR ) |
943 { | 945 { |
944 std::map <uint16_t, PduBuffer>::iterator it1 = m_rxonBuffer.find (seqN
umber.GetValue ()); | 946 it = m_rxonBuffer.find (seqNumber.GetValue ()); |
945 if ( it1 != m_rxonBuffer.end () && | 947 if ( it != m_rxonBuffer.end () && |
946 it1->second.m_pduComplete ) | 948 it->second.m_pduComplete ) |
947 { | 949 { |
948 it1 = m_rxonBuffer.find (m_vrR.GetValue ()); | 950 it = m_rxonBuffer.find (m_vrR.GetValue ()); |
949 int firstVrR = m_vrR.GetValue (); | 951 int firstVrR = m_vrR.GetValue (); |
950 while ( it1 != m_rxonBuffer.end () && | 952 while ( it != m_rxonBuffer.end () && |
951 it1->second.m_pduComplete ) | 953 it->second.m_pduComplete ) |
952 { | 954 { |
953 NS_LOG_LOGIC ("Reassemble and Deliver ( SN = " << m_vrR << " )
"); | 955 NS_LOG_LOGIC ("Reassemble and Deliver ( SN = " << m_vrR << " )
"); |
954 NS_ASSERT_MSG (it1->second.m_byteSegments.size () == 1, | 956 NS_ASSERT_MSG (it->second.m_byteSegments.size () == 1, |
955 "Too many segments. PDU Reassembly process didn'
t work"); | 957 "Too many segments. PDU Reassembly process didn'
t work"); |
956 ReassembleAndDeliver (it1->second.m_byteSegments.front ()); | 958 ReassembleAndDeliver (it->second.m_byteSegments.front ()); |
957 m_rxonBuffer.erase (m_vrR.GetValue ()); | 959 m_rxonBuffer.erase (m_vrR.GetValue ()); |
958 | 960 |
959 m_vrR++; | 961 m_vrR++; |
960 m_vrR.SetModulusBase (m_vrR); | 962 m_vrR.SetModulusBase (m_vrR); |
961 m_vrX.SetModulusBase (m_vrR); | 963 m_vrX.SetModulusBase (m_vrR); |
962 m_vrMs.SetModulusBase (m_vrR); | 964 m_vrMs.SetModulusBase (m_vrR); |
963 m_vrH.SetModulusBase (m_vrR); | 965 m_vrH.SetModulusBase (m_vrR); |
964 it1 = m_rxonBuffer.find (m_vrR.GetValue ()); | 966 it = m_rxonBuffer.find (m_vrR.GetValue ()); |
965 | 967 |
966 NS_ASSERT_MSG (firstVrR != m_vrR.GetValue (), "Infinite loop i
n RxonBuffer"); | 968 NS_ASSERT_MSG (firstVrR != m_vrR.GetValue (), "Infinite loop i
n RxonBuffer"); |
967 (void)firstVrR; // make compiler happy | 969 (void)firstVrR; // make compiler happy |
968 } | 970 } |
969 NS_LOG_LOGIC ("New VR(R) = " << m_vrR); | 971 NS_LOG_LOGIC ("New VR(R) = " << m_vrR); |
970 m_vrMr = m_vrR + m_windowSize; | 972 m_vrMr = m_vrR + m_windowSize; |
971 | 973 |
972 NS_LOG_LOGIC ("New VR(MR) = " << m_vrMr); | 974 NS_LOG_LOGIC ("New VR(MR) = " << m_vrMr); |
973 } | 975 } |
| 976 |
974 } | 977 } |
975 | 978 |
976 // - if t-Reordering is running: | 979 // - if t-Reordering is running: |
977 // - if VR(X) = VR(R); or | 980 // - if VR(X) = VR(R); or |
978 // - if VR(X) falls outside of the receiving window and VR(X) is not e
qual to VR(MR): | 981 // - if VR(X) falls outside of the receiving window and VR(X) is not e
qual to VR(MR): |
979 // - stop and reset t-Reordering; | 982 // - stop and reset t-Reordering; |
980 | 983 |
981 if ( m_reorderingTimer.IsRunning () ) | 984 if ( m_reorderingTimer.IsRunning () ) |
982 { | 985 { |
983 NS_LOG_LOGIC ("Reordering timer is running"); | 986 NS_LOG_LOGIC ("Reordering timer is running"); |
(...skipping 612 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1596 } | 1599 } |
1597 else· | 1600 else· |
1598 {······ | 1601 {······ |
1599 retxQueueHolDelay = Seconds (0); | 1602 retxQueueHolDelay = Seconds (0); |
1600 } | 1603 } |
1601 | 1604 |
1602 LteMacSapProvider::ReportBufferStatusParameters r; | 1605 LteMacSapProvider::ReportBufferStatusParameters r; |
1603 r.rnti = m_rnti; | 1606 r.rnti = m_rnti; |
1604 r.lcid = m_lcid; | 1607 r.lcid = m_lcid; |
1605 r.txQueueSize = m_txonBufferSize; | 1608 r.txQueueSize = m_txonBufferSize; |
1606 r.txQueueHolDelay = static_cast<uint16_t>(txonQueueHolDelay.GetMilliSeconds ()
); | 1609 r.txQueueHolDelay = static_cast<uint16_t> (txonQueueHolDelay.GetMilliSeconds (
)); |
1607 r.retxQueueSize = m_retxBufferSize + m_txedBufferSize; | 1610 r.retxQueueSize = m_retxBufferSize + m_txedBufferSize; |
1608 r.retxQueueHolDelay = static_cast<uint16_t>(retxQueueHolDelay.GetMilliSeconds
()); | 1611 r.retxQueueHolDelay = static_cast<uint16_t> (retxQueueHolDelay.GetMilliSeconds
()); |
1609 | 1612 |
1610 if ( m_statusPduRequested && ! m_statusProhibitTimer.IsRunning () ) | 1613 if ( m_statusPduRequested && ! m_statusProhibitTimer.IsRunning () ) |
1611 { | 1614 { |
1612 r.statusPduSize = static_cast<uint16_t>(m_statusPduBufferSize); | 1615 r.statusPduSize = static_cast<uint16_t> (m_statusPduBufferSize); |
1613 } | 1616 } |
1614 else | 1617 else |
1615 { | 1618 { |
1616 r.statusPduSize = 0; | 1619 r.statusPduSize = 0; |
1617 } | 1620 } |
1618 | 1621 |
1619 if ( r.txQueueSize != 0 || r.retxQueueSize != 0 || r.statusPduSize != 0 ) | 1622 if ( r.txQueueSize != 0 || r.retxQueueSize != 0 || r.statusPduSize != 0 ) |
1620 { | 1623 { |
1621 NS_LOG_INFO ("Send ReportBufferStatus: " << r.txQueueSize << ", " << r.txQ
ueueHolDelay << ", "· | 1624 NS_LOG_INFO ("Send ReportBufferStatus: " << r.txQueueSize << ", " << r.txQ
ueueHolDelay << ", "· |
1622 << r.retxQueueSize << ", " << r.r
etxQueueHolDelay << ", "· | 1625 << r.retxQueueSize << ", " << r.r
etxQueueHolDelay << ", "· |
(...skipping 24 matching lines...) Expand all Loading... |
1647 m_vrMs = m_vrX; | 1650 m_vrMs = m_vrX; |
1648 int firstVrMs = m_vrMs.GetValue (); | 1651 int firstVrMs = m_vrMs.GetValue (); |
1649 std::map <uint16_t, PduBuffer>::iterator it = m_rxonBuffer.find (m_vrMs.GetVal
ue ()); | 1652 std::map <uint16_t, PduBuffer>::iterator it = m_rxonBuffer.find (m_vrMs.GetVal
ue ()); |
1650 while ( it != m_rxonBuffer.end () && | 1653 while ( it != m_rxonBuffer.end () && |
1651 it->second.m_pduComplete ) | 1654 it->second.m_pduComplete ) |
1652 { | 1655 { |
1653 m_vrMs++; | 1656 m_vrMs++; |
1654 it = m_rxonBuffer.find (m_vrMs.GetValue ()); | 1657 it = m_rxonBuffer.find (m_vrMs.GetValue ()); |
1655 | 1658 |
1656 NS_ASSERT_MSG (firstVrMs != m_vrMs.GetValue (), "Infinite loop in ExpireRe
orderingTimer"); | 1659 NS_ASSERT_MSG (firstVrMs != m_vrMs.GetValue (), "Infinite loop in ExpireRe
orderingTimer"); |
1657 (void)firstVrMs; // make compier happy | 1660 (void)firstVrMs; // make compier happy |
1658 } | 1661 } |
1659 NS_LOG_LOGIC ("New VR(MS) = " << m_vrMs); | 1662 NS_LOG_LOGIC ("New VR(MS) = " << m_vrMs); |
1660 | 1663 |
1661 if ( m_vrH > m_vrMs ) | 1664 if ( m_vrH > m_vrMs ) |
1662 { | 1665 { |
1663 NS_LOG_LOGIC ("Start reordering timer"); | 1666 NS_LOG_LOGIC ("Start reordering timer"); |
1664 m_reorderingTimer = Simulator::Schedule (m_reorderingTimerValue, | 1667 m_reorderingTimer = Simulator::Schedule (m_reorderingTimerValue, |
1665 &LteRlcAm::ExpireReorderingTimer ,
this); | 1668 &LteRlcAm::ExpireReorderingTimer ,
this); |
1666 m_vrX = m_vrH; | 1669 m_vrX = m_vrH; |
1667 NS_LOG_LOGIC ("New VR(MS) = " << m_vrMs); | 1670 NS_LOG_LOGIC ("New VR(MS) = " << m_vrMs); |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1728 NS_LOG_LOGIC ("RBS Timer expires"); | 1731 NS_LOG_LOGIC ("RBS Timer expires"); |
1729 | 1732 |
1730 if (m_txonBufferSize + m_txedBufferSize + m_retxBufferSize > 0) | 1733 if (m_txonBufferSize + m_txedBufferSize + m_retxBufferSize > 0) |
1731 { | 1734 { |
1732 DoReportBufferStatus (); | 1735 DoReportBufferStatus (); |
1733 m_rbsTimer = Simulator::Schedule (m_rbsTimerValue, &LteRlcAm::ExpireRbsTim
er, this); | 1736 m_rbsTimer = Simulator::Schedule (m_rbsTimerValue, &LteRlcAm::ExpireRbsTim
er, this); |
1734 } | 1737 } |
1735 } | 1738 } |
1736 | 1739 |
1737 } // namespace ns3 | 1740 } // namespace ns3 |
LEFT | RIGHT |