3.2.1. Variables and structures
3.3.1. Variables and structures
5.2.1. Variables and Structures
5.3.1. Variables and Structures
6.1.1. Variables and Structures
6.2.1. Variables and Structures
7.3.1. Cell Search Procedure (Setup Procedure)
7.3.2. Connection Set-up and Paging Procedures
7.4.1. Variables and Structures
7.5.1. Variables and Structures
The main aim of this project is to construct a simulator able to emulate a UMTS network but behind of that there are some requirements that must be fulfilled:
1. The simulator must be able to emulate a UMTS system allowing investigation in depth of IP protocols, including UTRAN and Core Network entities and their respective protocols.
2. Flexibility of configuration.
3. Several applications running on the same node to simulate multi-connections.
4. Detailed simulation output for having results.
5. Friendly user interface.
Apart of the requirements mentioned above, one important point was that this project had to model and implement a simulator on top of a system that has got the enough flexibility to be continuously extended and improved. Network Simulator –ns2- presented several advantages for IP networks, as there is much exiting IPR and it is easily extensible for our project, researchers and future improvements. Furthermore, the way to obtain ns simulation results is by one or more text-based output files that contain detailed data to being analyzed afterwards and can be displayed by Network Animator (NAM). All these reasons made us to choose ns as the ideal baseline for extending it to support UMTS simulation.
Ns presents some work already implemented related to the work this project involves, so a previous analysis is fundamental for the later design and implementation. Basically, this simulator handles nodes as simple routing entities with agents and applications on top, which gives ns great configuration flexibility for simulations. Related to wireless communications, ns has got a basic mobile nodes that allow ad-hoc routing. Our conclusions are that we need to extend the simulator to support UTRAN (new mobile nodes, layers and protocols), non-ad-hoc communication and routing for UE mobility. Then, as UMTS can be modeled as an all-IP network whereby all the transactions are based on IP protocols, with all these modifications one can simulate a whole UMTS network by putting agents and applications on top of the nodes (obtaining the different entities such as RNC, SGSN, GGSN, VLR, etc.).
For implementing the necessary objects in our project, we started to plan the hierarchy from the object hierarchy already developed in ns. Figure 1 shows the main new elements added for our project and from which objects they inherit. Then, we can distinguish the addition of a new UE node and NodeB node, inherited from the MobileNode. All the layers have been inherited from Connector, but specifically, MAC layer from the MAC already implemented and the rest of the layers from LinkDelay due to its facility to emulate delays. The routing agent for NodeB is inherited from Agent (as every routing agents), but was already implemented. For the implementation of two IFQ queues (as will be seen later), we used the DropTail as the base class. We needed also a new classifier that handles packets properly for UMTS purposes, so we implemented one by inheriting from Classifier. Finally, the new routing module that was needed was done from the class RoutingModule.

Figure 1. New Object Hierarchy
Having the class MobileNode as background, the layer structure for nodes that we need in our project to support UTRAN does not differentiate very much. With the layer structure for UTRAN, the main changes are the introduction of the RLC layer and physical layer, the substitution of the routing agent and to leave out the ARP module. These changes drove us to adopt the layer structure shown in Figure 2:

Figure
2. Node structure.
With the election of this configuration we got a skeleton valid for starting developing the requirements studied in the first part of our project. Furthermore, if future investigators want to extend the simulator, they have got a very flexible model to work in, so the implementation of UTRAN functionalities can be done as acute as wanted. In principle, the functionalities that we have implemented in each layer are:
1. Radio Resource Control layer (RRC): Paging procedure, establishment, maintenance and release of an RRC connection between the UE and the NodeB, RRC connection mobility functions (turn on/off procedure, handover, etc.) and mapping into logical channels.
2. IFQ: acts as a part of the RLC; this is a queue of packets that manages flow control functions and rate matching.
3. Radio Link Layer (RLC): Segmentation and reassembly of higher layer PDUs, in sequence delivery of higher layer PDUs.
4. Medium Access Control (MAC): mapping between logical channels and transport channels, traffic volume monitoring.
5. Physical Layer: mapping between transport channels and physical channels, error detection and indication, uplink and downlink mux/demux and spreading, measurement and assignment of radio resources, power control, mux/demux of transport channels (DTDCH, DTCCH), Cell Selection procedure, Paging procedure and handover related functions.
Next we will describe the main changes that had to be adopted to support this scheme. First of all, to be able to create a node with a structure as shown in Figure 2, a new Tcl class is needed. We also had to consider the needing of a special node to be used as NodeB. Actually, in our project the UEs and NodeBs do not differ, related to architecture, very much but other reasons such as give maximum flexibility for future extensions or other secondary reasons (that will be explained later) made us to create two parallel new nodes with the same structure.
There were two types of nodes available: class Node and class MobileNode; our two new classes had quite a lot in common with the second one, so we inherited from it as shown below:
Class
Node/MobileNode/UE -superclass Node/MobileNode
Class
Node/MobileNode/NodeB -superclass Node/MobileNode
The whole implementation can be found in the code section under the names ns-uenode.tcl and ns-nodeb.tcl. However it is worth to see the procedures that we introduced; nodetype is a common method for the classes Node, Node/MobileNode, Node/MobileNode/UE and Node/MobileNode/NodeB that returns an integer for distinguish which type of node it is being handled from outside of the class:
Node/MobileNode/UE instproc
nodetype {} {
return 1
}
Similarly to MobileNode, the procedure add-interface (only shown some fragments of it in the code bellow) configures the layer structure for the node; after creating an instance of the class Simulator all the objects that represent the layers are created according to the arguments passed and configured later:
Node/MobileNode/UE instproc
add-interface { channel pmodel lltype rlctype mactype \
phylayer qtype qlen iftype anttype inerrproc outerrproc
fecproc} {
$self
instvar arptable_ nifs_ netif_ phy_ mac_ ifq_ rlc_ ll_ imep_ inerr_ outerr_
fec_
set ns [Simulator instance]
set imepflag [$ns imep-support]
set t $nifs_
incr nifs_
set netif_($t) [new
$iftype] ;# interface
set phy_($t) [new
$phylayer] ;# phy layer
set mac_($t) [new
$mactype] ;# mac layer
set ifq_($t) [new
$qtype] ;# interface queue
set ll_($t) [new $lltype] ;# link layer
set rlc_($t) [new
$rlctype] ;# rlc layer
set ant_($t) [new
$anttype]
#
# Local Variables
#
set nullAgent_ [$ns set nullAgent_]
set netif $netif_($t)
set phy $phy_($t)
set mac $mac_($t)
set ifq $ifq_($t)
set ll $ll_($t)
set rlc $rlc_($t)
set inerr $inerr_($t)
set outerr $outerr_($t)
set fec $fec_($t)
#
# Link Layer
#
$ll arptable $arptable_
$ll rlc $rlc
$ll ifq $ifq ; #rj
$ll mac $mac
$ll phy $phy
$ll down-target $ifq
#
# Interface Queue
#
$ifq target $rlc
$ifq set limit_ $qlen
$ifq drop-target $drpT
#
# rlc
#
$rlc up-target $ll
$rlc down-target $mac
#
# Mac Layer
#
$mac phy $phy
$mac up-target $rlc
$mac down-target $phy
#
#
Physical Layer
#
$phy netif $netif
$phy ll $ll
$phy up-target $mac
$phy down-target $netif
#
# Network Interface
#
$netif channel $channel
$netif propagation $pmodel ;#
Propagation Model
$netif node $self ;#
Bind node <---> interface
$netif antenna $ant_($t)
$self ll $ll
#
# Physical Channel
#
$channel addif $netif
# ============================================================
Now, one can ask how to do to create a Node/MobileNode/UE or a Node/MobileNode/NodeB. To get that it is necessary to change some configuration functions, above all in ns-lib.tcl; the first thing to do was changing the global node configuration through the procedure node-config, where these variables were added: rlcType, phyLayer and umtsType (for defining the rlc layer, the physical layer and the type of UMTS node [UE of NodeB] respectively). For creating a node of this type in the script, you only need to call the node-config method as is shown in this example extracted from an example in the code section and then the method node:
#
configure for base-station node
$ns_
node-config -adhocRouting $opt(adhocRouting) \
-llType $opt(ll) \
-rlcType $opt(rlc) \
-macType $opt(mac) \
-phyLayer $opt(phylayer) \
-ifqType $opt(ifq) \
-ifqLen $opt(ifqlen) \
-antType $opt(ant) \
-propType $opt(prop) \
-phyType $opt(netif) \
-topoInstance $topo \
-wiredRouting ON \
-agentTrace ON \
-routerTrace OFF \
-phyTrace OFF \
-movementTrace ON \
-channel $chan1 \
-umtsType $opt(umtsType)
set
BS [ $ns_ node ]
The reason of giving the NodeB the same layer structure instead of having only the physical layer (as the specifications says) is due to the lack of capacity for modifying fixed nodes. In theory, the MAC layer, RLC layer and RRC layer must be implemented in the RNC entity, but the structure of fix nodes in ns does not allow to modify the handling of packets. This is why we decided to give the class Node/MobileNode/NodeB the functionalities of a mixed NodeB and RNC. In this way, all the entities of a whole UTMS network can be implemented by adding agents and applications on top of fixed nodes.
Having this in mind, one can emulate a whole UMTS network thanks to the facilities offered by ns simulator and our extensions). For instance, for implementing the HSS entity a new application on top of a fixed node has to be implemented, thus all the rest of the nodes can access the HSS to consult or modify its internal registers.
The aim of this layer is to perform the basic functionalities that the physical layer has to do in UTRAN. When we designed the stack layer, it was found that there was a kind of physical layer used by the class MobileNode, but only implemented basic transmission features. It was the Network Interface layer, which serves as a hardware interface that is used by mobilenode to access the channel. The wireless shared media interface is implemented as class Phy/WirelessPhy (see ~ns/phy.{cc,h} and ~ns/wireless-phy.{cc,h}). The interface stamps each transmitted packet with the meta-data related to the transmitting interface like the transmission power, wavelength etc. This meta-data in pkt header is used by the propagation model in receiving network interface to determine if the packet has minimum power to be received and/or captured and/or detected (carrier sense) by the receiving node interface implementations.
Unlike the actual implementation of mobile nodes in ns, our project needed to have two different classes for the same layer (corresponding to UE and NodeB entities): class PhyUmts and class PhyUmtsNodeb.
The implementation of these two layers has been done in the files phy-umts.{cc,h}, phy-umts-nodeb.{cc.h} phy-timers.{cc.h}. The first thing to consider is a new header for packets related to the physical layer. This is implemented in phy-umts.h (other modifications were done in ns-packet.tcl and packet.h, consult the code section):
struct
hdr_phy {
// physical addressing
int sa_; //
source address
int da_; //
destination address
double data_size_; // user data size
int first_access_; // for initial access to a cell
// codification information
Chan_Coding c_coding_; // channel coding
int scrambling_c_; // scrambling code
int sf_; //
spreading factor
int k_; //
k for the channelisation code
int f_; //
freq
// information in the packet
double rx_power_; // tx power
int signature_; // signature
int p_scrambling_c_; // primary scrambling code (only SCH)
int pi_; //
paging indicator
int used_rach_; // rach used
int ue_sf_; // SF given to the UE
int freq_; // freq given to the UE
int bofsec_; // begin of sequence
int seqno_; // sequence number
int eofsec_; // end of sequence
int paging_group_; // for paging procedure
// Used in the Handover Procedure to inform the new
NodeB about the
// current aplications
int* flows_[MAX_NUM_FLOWS][3];
// for broadcast
int free_resources_[MAX_NUM_SCRAM][MAX_NUM_SIG +
1]; // array with // the available ul
scrambling codes and signatures
int rach_[MAX_NUM_RACH]; //
available RACH
// for DPCCH
dpcch_contr dpcch_control_; // control information in DPCCH
// for DPDCH
PacketQueue* dpdch_control_; // control
information in DPDCH;
int n_dpdch_; // number id
of DPDCH
inline int& sa() { return sa_; }
inline int& da() { return da_; }
inline double& size() { return
data_size_; }
inline double& rx_power() { return
rx_power_; }
inline int& scrambling_c() { return
scrambling_c_; }
inline int& p_scrambling_c() { return
p_scrambling_c_; }
inline int& sf() { return sf_; }
inline int& f() { return f_; }
inline int& freq() { return freq_; }
inline int& ue_sf() { return ue_sf_;
}
inline int& k() { return k_; }
inline int& signature() { return
signature_; }
inline int& pi() { return pi_; }
inline int& used_rach() { return
used_rach_; }
inline dpcch_contr& dpcch_control() {
return dpcch_control_; }
inline Chan_Coding& c_coding() {
return c_coding_; }
// Header access methods
static int offset_;
inline static int& offset() { return
offset_; }
inline static hdr_phy* access(const
Packet* p) {
return (hdr_phy*)
p->access(offset_);
}
};
We can distinguish a first group of fields used for physical addressing (own of the project), other with modulation information such as the SF, frequency or scrambling code used, other with different signaling information, other for broadcasting information (NodeB only), other for DPCCH, other for DPDCH and other for the handover procedure. To understand the exact meaning of all of them it is recommendable to have a look to the implementation of the classes. However, it is interesting to explain how we model the DPCCH and DPDCH channels. The first uses the structure dpcch_contr:
struct
dpcch_contr {
int n_dpdch_;
// number of DPDCHs
multiplexed with
// DPCCH
double
dpdch_rate_; // rate tx in DPDCHs
};
And the second one has got a pointer to a PacketQueue called dpdch_control_ that stores all the packets that are multiplexed in one DPDCH. With this configuration, a receptor knows how many DPDCH are multiplexed with one DPCCH and is able to extract the packets that have been multiplexed within it.
The class PhyUmts represents the physical layer in the UE and inherits from the class LinkDelay because it presents all the basic needing for our purpose. The needing of being able to support several simultaneous applications made us to consider a parameter called MAX_NUM_FLOWS that means the maximum number of applications generated in a UE allowed. First we are going to describe the main variables of the class and their meaning:
public:
int ue_address_; // physical address of UE
int nodeb_address_; // physical address of NodeB
nsaddr_t ip_nodeb_; // ip address of NodeB
nsaddr_t ip_ue_; // ip address of this UE
int ue_state_; // UE state, 0 OFF, 1 waiting to turn on, 2 ON
int handover_; // 1 if is in the handover procedure, else 0
int dlfreq; //
DownLink frequence
int ulfreq; //
UpLink frequence
Phy *netif_; // network
interface
double bytes_slot; // bytes per slot
protected:
LL* ll_; //
LL
NsObject* downtarget_; // for outgoing packet
NsObject* uptarget_; // for incoming packet
PacketQueue* prach_temp_; //temporary
storage for a PRACH pkt while
// waiting
for AICH
int num_preambles_; // number of RACH tx. retries
int wait_; // wait to tx the next preamble
// user defined variables
static int slot_packet_len_; // max pkt size
that can be tx in one slot
static int max_num_ms_; // user defined max num of MS in cell
static int max_num_freq_; // num of frequencies to be permitted in
cell
wcdma_control w_control; // control parameters for WCDMA coding
int listen_sccpch_; // for detecting if a paging procedure has started
int listen_sch_; // for cell selection (handover & initial access)
int selected_sc_; // primary scrambling code selected
int selected_p_; // physical address of new NodeB selected
// for monitoring the transmission/reception
power
int tx_power_; // power to tx
double rx_power_; // power in rx
double threshold_; // for power reception (for set-up and handover
// procedure)
double power_sch_; // power received in channel SCH
dpcch_contr d_control; // control parameters for DPDCH multiplexing
PacketQueue* pkttoTx_; // packets to tx in the current slot
PacketQueue* rachtoTx_; // RACH to tx
Packet * rach_recv_; // packet RACH received from RRC
PacketQueue* data_temp_; // packet received in the current TTI from MAC
Packet * rx_dpdch[MAX_NUM_DPDCH];
// DPDCH packets in a slot
double tti_; //
Time that correspond a TTI
int tx_rate; //
rate for transmission
int d_sc_; //
ul scrambling code for in dedicated channels, 1 frame
flow_rate flow_rates_[MAX_NUM_FLOWS]; // flows and their SF's and rates
// Interference Model
unsigned long dl_ErrorRate; // 1 / BLER per frequency
double lastRate; // last rate arrived
double Prx; //
Power received from NodeB
double Ioth; //
Interference power received from other NodeB
int noIoth; //
Number of samples of Ioth
BlerTable* BTable; // Table of BLER against Eb/No
int up_slot_; // number of upslot, 0 beginning of frame
int in_seq_; //
counter for decoding
int hin_seq_; // counter for decoding during handover procedure
int hnodeb_; //
last nodeb before handover
private:
// time variables:
static double slot_time_; // The duration of each WCDMA slot
static double rach_slot_time_; // The duration of each RACH slot
// indicates whether the radio is active or
not.
int radio_active_;
// Timers
PreambleTimer pPreamble_;
UpSlotUmtsTimer pUpSlot_;
RachUpSlotUmtsTimer pRachUpSlot_;
TxPktUmtsTimer pTxPkt_;
RxPktUmtsTimer pRxPkt_;
CellTimer pCell_;
BlerTimer pBler_;
There is worth mentioning the variable w_control, whose type is a structure called wcdma_control and has got all the needed parameters for the performance of WCDMA:
struct wcdma_control {
int p_scrambling_; // primary scrambling code in actual cell
int signature_; // signature used in the last RACH access
int scram_tx_; //
scrambling code used in the last RACH access
int rach_tx_; // RACH
used
int free_res_[MAX_NUM_SCRAM][MAX_NUM_SIG + 1]; // array with
//
the available ul scrambling codes (0 available)
//
and signatures
int free_rach_[MAX_NUM_RACH]; //
available RACHs
};
The main methods that this class implements are:
void
recv(Packet *p, Handler *h); // Reception
of any packet from // netif and mac
// Timer handlers
void PreambleHandler (void); //
Handler for PreambleTimer
void upslotHandler(Event *e); //
Handler for UpSlotUmtsTimer
void rachupslotHandler(Event *e); // Handler for
RachSlotUmtsTimer
void recvHandler(Event *e); //
Handler for RxPktUmtsTimer
void sendHandler(Event *e); //
Handler for TxPktUmtsTimer
void cellHandler(void); //
Handler for CellTimer
void blerHandler(Event *e); //
Handler for BlerTimer
// Radio transmission and reception
void radioSwitch(int i); //
active the radio
// Packet Transmission Functions.
void recv_from_phy(Packet* p, Handler *h); // Reception from
netif
void tx_preamble(void); //
Transmission of preamble for RACH
//
procedure
void c_coding(Packet* p);
// Apply channel coding
void c_decoding(Packet* p); // Decode channel coding
void ul_mux(Packet* p); //
1st part UL multiplexing chain
void dl_demux(Packet* p); //
demultiplexing chain
void mk_dpxch(void); //
2nd part of DL multiplexing chain,
//
coding and modulation of user data
//
into dedicated channels DPDCHs/DPCCH
void decode(void); //
undo the mk_dpxch()
void send_msg(Packet* p, int scram_c, int sf, int k); //
contructor
//
for dedicated channels, called from mk_dpxch
void
mk_msg_rach(void); // Construction of
RACH message
//
(message part and control) and tx
//
in a PRACH procedure with preamble
void mk_msg_rach(Packet* p); //
Contruction of RACH message
// (1 slot only) without preamble
void send_to_rrc(Packet* p); //
To send directly to RRC (LL)
int scrambling_allot(void); // Allocation of scrambling code
int n_rx_dpdch(void); //
Check number of DPDCH received
void tx_to_nodeb(Packet* p); //
Transmission to the netif
void cell_selection(Packet* p); // For the Cell search procedure. // Selects
the appropriate cell to attach with
// to calculate tx time for a packet
double TX_Time(Packet *p);
The timers used in the class PhyUmts are implemented in file phy-timers.{cc,h}. This is a list of the main classes (timers) implemented there:
PreambleTimer: the amount of time to wait for a AICH.
UpSlotUmtsTimer: clocks slots on the Uplink.
RachUpSlotUmtsTimer: clocks slots on the Uplink ALOHA.
TxPktUmtsTimer: times the transmission of a packet.
RxPktUmtsTimer: times the reception of a packet.
CellTimer: the amount of time to wait until deciding the new
NodeB
to
attach with.
BlerTimer:
interval between calculations of BLER (interference).
WCDMA slot structure
There are two types of slot structure in the uplink depending on the channel: the DPDCH and DPCCH go in a disposition of a 10ms frame, 15 slots, 2560 chips per slot and a number of bits per slot that depends on the SF, which are controlled by the timer pUpSlot_ (class UpSlotUmtsTimer), whereas for the PRACH the timer instance pRachUpSlot_ (class RachUpSlotUmtsTimer) is the one that controls it. The variable pUpSlot_ has got the control of the current slot by means of up_slot_.
Both timers are initiated in the initialization of the class PhyUmts and are called periodically each 667 ms and 1,25 ms respectively. So when the timers detect the end of the period, invoke the method handle() and this calls the methods upslotHandler() and rachslotHandler().
The general working of the timers is based on the management of two PaketQueue for sending packets through the air interface. These queues are pkttoTx_ (controlled by pUpSlot_) and rachtoTx_ (controlled by pRachUpSlot_).
Packet transmission and reception
For the air transmission we need to model the transmission and reception of packets in terms of delays. In the transmission, the system has to be able to simulate how long the radio transmission should take and in the reception the operation is the same but simulating the reception time. So we want to transmit a packet, we switch on the radio (in upslotHandler() and rachslotHandler()) and then call the method tx_to_nodeb(), which initiates the timer pTxPkt_ (class TxPktUmtsTimer) to emulate the transmission time and send the packet to downtarget_. When the timer expires the method sendHandler() simply switches off the radio.
In the reception, the process begins when a packet arrives to the method recv(). Then, we switch on the radio and initiate the timer pRxPkt_ (class RxPktUmtsTimer) passing the received packet as argument. When this timer expires, the method recvHandler() is invoked so the radio is switched off and the packet is passed to the proc recv_from_phy().
Physical Addressing
The system we are trying to implement involves that it is going to be possible to make multi-hop communications, (UE-UE in the same cell, UE-UE in different cells, UE-fixed node and vice versa). The IP directions that the ip header hdr_ip provides are valid only end-to-end, but we need to control addressing in each wireless link. Likewise there are directions for MAC layer already implemented in ns (consult manual) we need physical directions. For that we need an internal variable called ue_address_ that stores the address of the UE and a couple of fields that express source address and destination address in the physical header hdr_phy (sa_ and da_). The variable ue_address_ is filled in the initialization of the class PhyUmts, taking its value from a static variable called PhyIndex_ (that increments itself in each initialization of the class).
Spreading and scrambling code handling
The codification and modulation information in a packet is carried in some fields of the physical header hdr_phy: c_coding (TURBO, CONV_HALF, CONV_THIRD), scramb_c, sf_, k_ and f_, which correspond to the channel coding, scrambling code, spreading factor, k, and frecuency (0-11 in uplink and 12-23 in downlink).
The allocation of the frequency the UE has to use is done in the RRC layer of the NodeB and it is stored in the variable ulfreq_, being updated each time the UE changes its location (cell). Likewise the NodeB transmits to the UE always in the same frequency; the UE detects it when receives packets and stores this value in dlfreq_ (for using it in the interference model, later explained). This variable is updated each time that there is a resource allocation (a paging procedure) by the RRC layer of the UE.
The spreading factor needed to transmit the dedicated channels DPDCH is calculated (in the method mk_dpxch()) in base of the value of the variable bytes_slot_. This variable is set by the RRC and represents the maximum amount of information that the physical layer is allowed to transmit through the air interface per slot. The DPCCH has got a fixed SF of 256. For the dedicated channels, the scrambling code used to codify them changes from frame to frame. Therefore, we take the scrambling code from the variable d_sc_, which is updated once in each frame inside mk_dpxch() (when up_slot_ = 0) by means of the method scrambling_allot(). This function chose randomly one of the free scrambling codes from the variable w_control.free_res_[][].
For the PRACH (preamble and message part) the election of the spreading factor is not carried out because they have assigned fixed SF, whereas the process with the scrambling code is the same as for dedicated channels. The main difference is that we need to transmit in a free PRACH channel, so we chose among all the free ones stored in the variable w_control.free_rach_[] in the method tx_preamble(). After the election of the rach that is going to be used, it is stored in the variable w_control.rach_tx_. For the message part, simply we have got to use the rach channel already used by the preamble (w_control.rach_tx_).
DCH handling
When a packet arrives in a DCH channel from the MAC layer, recv() calls the method ul_mux(); this procedure does the first part of the uplink chain (see UTRAN) in a general way and finally stores the packet in the PacketQueue data_temp. As was commented before, the upslotHandler() is executed in each slot and the first thing this does is to invoke mk_dpxch(). This procedure is in charge of doing the last part of the uplink chain such as channel mapping, spreading, etc. Therefore, inside mk_dpxch() we calculate the number of DPDCH necessary (and their SF depending on bytes_slot) for each flow and start de-queuing packets from data_temp to form the DPDCHs. The way we store the packets inside a DPDCH is by means of the PacketQueue dpdch_control_ in the physical header hdr_phy. It could be necessary some times to fragment the packets, stamping the packets with the fields seqno_ and eoseq_ of the hdr_phy. When the programmed DPDCHs have been filled, a DPCCH is constructed to inform the NodeB about the number of DPDCH multiplexed and the tx_rate we are transmitting (for interference module). Once all the DPDCH/DPCCH are ready to transmit, they are queued in pkttoTx_ and then upslotHandler() will send them.
DPDCH/DPCCH handling
By the way the NodeB transmits the dedicated channels, the UE should receive all the DPDCH before the reception of the DPCCH. Therefore, as the DPDCH arrive they are stored in rx_dpdch[]. When the DPCCH arrives, the UE checks if has received all the DPDCH in the function n_rx_dpdch() and if affirmative, the method decode() is called. This function takes DPDCH from the variable rx_dpdch[] and extract the packets multiplexed within it. At the same time, the function checks if the packets have arrived in sequence by consulting the fields seqno_ and eoseq_. If all the fragments of a packet have been received correctly, the procedure dl_demux() is called. If not, the packet is dropped. dl_demux() is the dual of ul_mux() and ends with the instruction uptarget_->recv(), that sends the packet towards the MAC layer.
PRACH with preamble
When a packet in the RACH channel arrives and the method recv() detects that it has to do an access to the shared channel with a preamble, we store the packet in the variable rach_recv and put the variable wait_ to 1 to avoid new rach access until the current one has finished (success of failure). After that, we call the procedure tx_preamble() that constructs a preamble with a determinate power and a randomly elected signature from the variable w_control.free_res[][]. This preamble is queued in the variable rachtoTx_. The timer rachUpSlotTimer, through rachupslotHandler(), will transmit it and initiate the timer pPreamble_ (class PreambleTimer) to a value between 4 and 12 slots. Then, if the UE has not received a AICH as answer from the NodeB before the timer expires the preambleHandler() calls tx_preamble() again.
If after having done this MAX_NUM_PREAMBLE times we have not received response, the UE generates a failure message that will be sent to the RRC by means of the method send_to_rrc(). The answer the UE is waiting is a AICH with the same signature it sent in the preamble before. Once received, the procedure mk_msg_rach() is called, which sends the RACH message for the same time than the preamble (a whole ALOHA frame, 8 slots, 10 ms) and puts wait_ to 0 to allow other RACH access.
PRACH without preamble
It is used to send ACKs from the RLC layer or a release request from the RRC layer. This procedure uses the method mk_msg_rach(p) is a similar way than the previous case. The only difference is that there is not preamble and the message part is sent for only 1 ALOHA slot.
Paging procedure
The process is initiated when the UE receives a PICH through the air interface. Then, the field pi_ of the hdr_phy is checked to know if is the same than the variable paging_group_, stored when we make a cell search procedure (next point). If they are equal we put the variable listen_sccpch_ to 1, so we start listening the channel S-CCPCH for detecting a PCH in this channel. When we detect it, the PCH is sent to the RRC by means of sent_to_rrc() and listen_sccpch_ is set to 0. The RRC layer will control the whole procedure.
Cell search procedure
This procedure starts when the RRC layer sends a setup request (to switch on the UE or for handovers) in the RACH channel and sets the variable listen_sch_ to 1. Then, the pCell_ timer is initiated and we started to listen to every SCH received from every NodeB. Therefore, for each SCH the method cell_selection() is called. For this procedure we use the variables power_sch_, selected_sc_ and selected_p_ (maximum power received in a sch from the beginning of the cell search procedure, scrambling code selected and primary scrambling code selected). The aim of this function is to determine which cell the UE has got to attach with. For doing that we compare the received power in a SCH with power_sch_ and if higher we update this variable and set selected_sc_ = hdr_phy->p_scrambling_c_ and selected_p_= hdr_phy->sa_. When the timer pCell_ expires the method cellHandler() is called to put the variable listen_sch_ to 0 and to initiate a RACH procedure as we saw. The whole procedure finishes when the NodeB answers the request of attaching by a LL_SETUP_REPLY. Then, the UE takes the paging_group_ from the field in the hdr_phy paging_group_ and update other variables as the w_control.p_scrambling_, nodeb_address_ or ip_nodeb_.
Handover procedure
The UE is constantly taking measurements of the received power by updating the variable power_sch_ with the channel P-CCPCH. When this power falls under the constant power defined in threshold_ and we detect that the UE is moving away from the NodeB we were attached, we send a handover message to the RRC (send_to_rrc()). The RRC layer then will consider is initiate or not a cell search procedure.
Power control
The power control function is already implemented in the network interface with the support of the radio propagation model (wireless-phy.{cc,h} and tworayground.{cc,h}). Basically it is based on the Two ray Ground model at far distances (1/r4) and the Friss space attenuation at near distances (1/r2). Refer to the code section for a further understanding.
Interference model
UTRAN is an interference-limited system. To have control about the interference we need to emulate it. The first step is to be able to take measurements of the power in the physical layer. The network interface worked with an energy model but it did not pass the measurements to the physical layer, so we had to extend the class Phy/WirelessPhy to store the received energy in the field rx_power_ of the hdr_phy:
if(propagation_) {
s.stamp((MobileNode*)node(),
ant_, 0, lambda_);
Pr = propagation_->Pr(&p->txinfo_, &s,
this);
hdr_phy *ph = HDR_PHY_UMTS(p);
ph->rx_power() = Pr;
:
With this extension, any packet that gets the physical layer has got the reception power stamped. The aim of the interference model is to calculate the probability of receive a transport block correctly or badly. Therefore, we need to get Eb/N0; it means simply bit energy divided by noise spectral density. However, over time the expression has acquired an additional meaning. One reason is the fact that in CDMA the interference spectral density is added to the noise spectral density, since the interference is noise, due, for example, to spreading. Thus, N0 can usually be replaced by I0, interference plus noise density. The performance indicator Eb/N0 is always related to some quality (BLER) target.
In the downlink, the Eb/N0 is calculated by the model:
![]()
where Iown is the total power received from the serving cell, Ioth is the total power received from the surrounding cells, and PN is the noise power (thermal and equipment). The factor a is the so-called orthogonality factor, which depends on the multipath conditions. The codes are fully orthogonal, thus in the case of no multipath the interference from the serving cell is cancelled and a = 1. In our case, then, the resulting equation is:
![]()
The physical layer in the UE is constantly taking power measurements in the DPCCH channel (only those that have not been sent by the NodeB we are attached to) and periodically (interval fixed to 5 radio frames by pBler_ timer) calculates Eb/N0 and BLER. The noise power is constant in this case:
![]()
where
and NF is
approx. 6 dB. Then, PN=6.1e-14W.
W is the chip rate (3.84 Mcps) and R is taken from the variable lastRate (update its value when a DPCCH is received). Prx is taken from the physical header (as we explained before) of the DPCCH targeted to the UE, and Ioth is taken from the DPCCH targeted to other UEs. Once we have got Eb/N0 we consult a table (class BlerTable implemented in phy-umts.{cc.h}) to get the BLER and then, that result is passed to the MAC layer. Here there is the blerHandler():
// Called when BlerTimer
expires.
// Timer for calculating the
Interference
void
PhyUmts::blerHandler(Event *e)
{
double Eb_No, bler, I=0;
// Restart timer for next time.
pBler_.start((Packet *)e, 5*UMTS_FrameTime);
if ((lastRate > 0) && (noIoth > 0)) {
// if there has been any interference
I = Ioth / noIoth;
Eb_No = (CHIP_RATE * Prx) / (lastRate * (I + Pn));
} else {
// no interference at all
Eb_No = 1e38;
}
Ioth = 0;
noIoth = 0;
bler = BTable->getbler(Eb_No); // consult table (Eb_No)
dl_ErrorRate = (int)(1/bler) + 1;
mac_->dl_interference(dl_ErrorRate); // send the results to
MAC
return;
}
The class PhyUmtsNodeB represents the physical layer in the NodeB and inherits from the class LinkDelay because it presents all the basic needing for our purpose. It is quite similar to the UE and so its understanding is easier because it complements the class PhyUmts. In the dimensioning of the variables we had into account that the NodeB has to support transmission and reception to several UEs, so some of the variables already seen in the UE can be seen in this class but in an array form (MAX_NUM_UE). First we are going to describe the main variables of the class and their meaning:
public:
Phy *netif_; // network
interface
int nodeb_address_;
protected:
LL* ll_; //
ll
NsObject* downtarget_; // for outgoing packet
NsObject* uptarget_; // for incoming packet
PacketQueue* paging_temp_; //temporary
storage for a PCH pkt while waiting
int wait_; // wait to tx the next paging
// user defined variables
static int max_num_ms_; // user defined max num of MS in cell
int ue_id[MAX_NUM_UE]; // is a conversion matrix for internal
addressing.
//
The position in the array is the internal address
ue_info ue_info_[MAX_NUM_UE]; // information related to UE's
wcdma_control w_control; // control parameters for WCDMA coding
int sig_ue_[MAX_NUM_UE]; // signature used for each UE
double tti_; //
time for a TTI
dpcch_contr d_control[MAX_NUM_UE]; // control parameters for DPDCH mux
PacketQueue* aichtoTx_; // packets to tx in the current slot
PacketQueue* pkttoTx_; // packets to tx in the current slot
PacketQueue* pchtoTx_; // packets to tx in the current slot
PacketQueue* data_temp_[MAX_NUM_UE]; //
packet received in the current
// slot from MAC for each UE
Packet * pch_recv_; // PCH packet received in Physical layer
// DPDCH packets received in a slot
Packet *
rx_dpdch[MAX_NUM_UE][MAX_NUM_DPDCH];
int paging_dest_; // address of the UE target for paging procedure
int tx_rate; //
Rate for transmission
// Interference Model
double Ioth[MAX_NUM_FREQ]; // Interference power received
int noIoth[MAX_NUM_FREQ]; // Number of samples of Ioth
int down_slot_; // number of downslot, 0 beginning of frame.
private:
// time variables:
static double slot_time_; // The duration of each WCDMA slot
static double aich_slot_time_; // The duration of each AICH slot
// indicates whether the radio is active or
not.
int radio_active_;
// Timers
PagingTimer pPaging_;
DownSlotUmtsTimer pDownSlot_;
AichSlotUmtsTimer pAichSlot_;
TxPktNodebTimer pTxPkt_;
RxPktNodebTimer pRxPkt_;
BlerNodebTimer pBler_;
NsObject* logtarget_;
};
Apart from the variable w_control (already explained in the class PhyUmts), there is worth mentioning the variable ue_info_[], whose type is a structure called ue_info and has got parameters specific of the UE and for the control of signaling:
struct
ue_info{
int paging_group; // paging group allocated for UE
double bytes_slot; // bytes per slot dedicated ch.
int in_seq_; // control in decode()
int count; // control in mk_dpxch()
// Interference Model
unsigned
long ul_ErrorRate; // 1 / BLER per
frequence
double
lastRate; // last rate
arrived for this UE
double
Prx; // Power
received from this UE
int ul_freq; // freq used for this UE
int nSamples; // samples taken for interference calculation
double IntPower; // total power received from this UE
// for
interference calculation
};
The main methods that this class implements are:
void
recv(Packet *p, Handler *h); // Reception
of any packet from // netif and mac
// Timer handlers
void PagingHandler(Event *e); //
Handler for PagingTimer
void downslotHandler(Event *e); //
Handler for DownSlotUmtsTimer
void aichslotHandler(Event *e); //
Handler for AichSlotUmtsTimer
void recvHandler(Event *e); //
Handler for TxPktNodebTimer
void sendHandler(Event *e); //
Handler for RxPktNodebTimer
void blerHandler(Event *e); //
Handler for BlerNodebTimer
// Radio transmission and reception
void radioSwitch(int i); //
active the radio
// Packet Transmission Functions.
void recv_from_phy(Packet* p, Handler *h); // Reception from
netif
void tx_aich(Packet* p); //
Transmission of AICH channel
void tx_pich(Packet* p); //
Transmission of PICH channel
void
tx_pch(Packet* p); //
Transmission of PCH channel
void dl_mux(Packet* p); //
1st part DL multiplexing chain
void ul_demux(Packet* p); //
demultiplexing chain
void mk_dpxch(void); // 2nd part of DL multiplexing
chain,
//
coding and modulation of user data
//
into dedicated channels DPDCHs/DPCCH
void decode(int id); //
undo the mk_dpxch()
void send_msg(Packet* p, int scram_c, int sf, int k); //
contructor
//
for dedicated channels, called from mk_dpxch
void send_to_rrc(Packet* p); //
To send directly to RRC (LL)
int packet_for_me(Packet* p); //
Check if received packet is
//
registered in ue_id[], register new UEs and make
//
conversion for internal addressing
void mk_pccpch(void); //
Construction of P-CCPCH and tx
void mk_sch(void); //
Construction of SCH and tx
int n_rx_dpdch(int id); //
Check number of DPDCH received
void tx_to_ue(Packet* p); //
Transmission to the netif
void ue_registry(Packet* p);// Give paging group to the UE
int look_for(int phyaddr); //
return the position of UE in ue_id[]
void tx_fach(Packet* p); //
Transmission of FACH channel
void c_coding(Packet* p); //
Apply channel coding
void c_decoding(Packet* p); // Decode channel coding
// to calculate tx time for a packet
double TX_Time(Packet *p);
Finally, it is convenient to explain how the method packet_for_me() do the internal addressing. Since the whole system has to cope with lots of UEs, it is quite normal that there are some UEs in a cell with not consecutively addresses. Our storage structures are dimensioned to MAX_NUM_UE and the position in them does not imply the physical address. Then, when a packet arrives and there is a needing to access a structure, it is impossible to know which position corresponds to that UE. What we did was to implement the structure ue_id[MAX_NUM_UE] that store the addresses of all the UEs in a cell. Then, when a packet arrives to the physical layer, recv() invokes the method packet_for_me() that changes the field sa_ in the hdr_phy for the position for that UE in all the structures. Furthermore, this conversion saves computational complexity. Of course, before sending the packet to uptarget_ or downtarget_, the field sa_ is set to its previous value.
The timers used in the class PhyUmtsNodeb are implemented in file phy-timers.{cc,h}. This is a list of the main classes (timers) implemented there:
PagingTimer: the amount of time to wait for a paging_ok.
DownSlotUmtsTimer: clocks slots on the Uplink.
AichSlotUmtsTimer: clocks slots on the Uplink for AICH.
TxPktUmtsTimer: times the transmission of a packet.
RxPktUmtsTimer: times the reception of a packet.
BlerTimer:
interval between calculations of BLER (interference).
WCDMA slot structure
Likewise in the class PhyUmts, there are two types of slot structure in the downlink depending on the channel: the DPDCH and DPCCH, P-CCPCH, S-CCPCH and SCH go in a disposition of a 10ms frame, 15 slots, 2560 chips per slot and a number of bits per slot that depends on the SF, which are controlled by the timer pDownSlot_ (class DownSlotUmtsTimer), whereas for the AICH the timer instance pAichSlot_ (class AichSlotUmtsTimer) is the one that controls it. The variable pDownSlot_ has got the control of the current slot by means of down_slot_.
Both timers are initiated in the initialization of the class PhyUmtsNodeb and are called periodically each 667 ms and 1,25 ms respectively. So when the timers detect the end of the period, invoke the method handle and this calls the methods downslotHandler() and aichslotHandler().
The general working of the timers is based on the management of several PaketQueue for sending packets through the air interface. These queues are pkttoTx_, pchtoTx_ (both controlled by pDownSlot_) and aichtoTx_ (controlled by pAichSlot_).
Packet transmission and reception
Likewise in the UE, for the air transmission we need to model the transmission and reception of packets in terms of delays. In the transmission, the system has to be able to simulate how long the radio transmission should take and in the reception the operation is the same but simulating the reception time. So we want to transmit a packet, we switch on the radio (in downslotHandler() and aichslotHandler()) and then call the method tx_to_ue(), which initiates the timer pTxPkt_ (class TxPktNodebTimer) to emulate the transmission time and send the packet to downtarget_. When the timer expires the method sendHandler() simply switches off the radio.
In the reception, the process begins when a packet arrives to the method recv(). Then, we switch on the radio and initiate the timer pRxPkt_ (class RxPktUmtsTimer) passing the received packet as argument. When this timer expires, the method recvHandler() is invoked so the radio is switched off and the packet is passed to the proc recv_from_phy().
Physical Addressing
The system we are trying to implement involves that it is going to be possible to make multi-hop communications, (UE-UE in the same cell, UE-UE in different cells, UE-fixed node and viceversa). The IP directions that the ip header hdr_ip provides are valid only end-to-end, but we need to control addressing in each wireless link. Likewise there are directions for MAC layer already implemented in ns (consult ns manual) we need physical directions. For that we need an internal variable called nodeb_address_ that stores the address of the UE and a couple of fields that express source address and destination address in the physical header hdr_phy (sa_ and da_). The variable nodeb_address_ is filled in the initialization of the class PhyUmtsNodeb, taking its value from a static variable called PhyNodebIndex_ (that increments itself in each initialization of the class from a initial value of 1000).
Broadcasting
This a function that is only done in the NodeB. The NodeB needs to broadcast some parameters for the general performance of the radio interface. This is going to be done in each slot by means of the method downslotHandler(). After doing the multiplexing of DPDCH/DPCCH, the method mk_pccpch() is called (every slot) and mk_sch() only in the first slot of each frame.
The first one broadcasts the available signatures and the available rach sub-channels to be used in prach procedure. For doing that, the fields of hdr_phy free_resources_[][] and rach_[] are set with w_control.free_res_[][] and w_control.dl_scramb_[] respectively. The sending is done by queuing the P-CCPCH in pkttoTx_ so the downslotHandler() can transmit it after. The second one broadcasts the primary scrambling code in the cell by queuing the packet in pkttoTx_ likewise the previous case.
Spreading and scrambling code handling
The codification and modulation information in a packet is carried in some fields of the physical header hdr_phy: c_coding (TURBO, CONV_HALF, CONV_THIRD), scramb_c, sf_, k_ and f_, which correspond to the channel coding, scrambling code, spreading factor, k, and frecuency (0-11 in uplink and 12-23 in downlink).
The allocation of the frequency the UE has to use is done in the RRC layer of the NodeB so it set the variable ue_info_[].ul_freq to the frequency allocated to the UE before transmitting it in a RACH message. In the NodeB we do not need to keep a variable to know in which frequency we transmit to each UE because the interference model does not need it (later explained).
The spreading factor needed to transmit the dedicated channels DPDCH is calculated (in the method mk_dpxch()) in base of the value of the variable ue_info_[].bytes_slot_. This variable is set by the RRC and represents the maximum amount of information that the physical layer is allowed to transmit to each UE through the air interface per slot. The DPCCH has got a fixed SF of 256. For the dedicated channels, the scrambling code used to codify them does not change from frame to frame as in the UE. In the Nodeb we are going to transmit always using w_control.p_scrambling_ as the scrambling code.
For the rest of the channels the election of the spreading factor is not carried out because they have assigned fixed SF, whereas the process with the scrambling code is the same as for dedicated channels.
DCH handling
When a packet arrives in a DCH channel from the MAC layer, recv() calls the method dl_mux(); this procedure does the first part of the downlink chain (see UTRAN) in a general way and finally stores the packet in the PacketQueue data_temp_[i] corresponding to the UE destination. As was commented before, the downslotHandler() is executed in each slot and the first thing this does is to invoke mk_dpxch(). This procedure is in charge of doing the last part of the uplink chain such as channel mapping, spreading, etc. Therefore, inside mk_dpxch() we calculate the number of DPDCH necessary (and their SF depending on bytes_slot) for each UE and start de-queuing packets from data_temp_[] to form the DPDCHs. The way we store the packets inside a DPDCH is by means of the PacketQueue dpdch_control_ in the physical header hdr_phy. It could be necessary some times to fragment the packets, stamping the packets with the fields seqno_ and eoseq_ of the hdr_phy. When the programmed DPDCHs have been filled, a DPCCH is constructed to inform the NodeB about the number of DPDCH multiplexed and the tx_rate we are transmitting (for interference module). Once all the DPDCH/DPCCH are ready to transmit, they are queued in pkttoTx_ and then downslotHandler() will send them.
DPDCH/DPCCH handling
By the way the UE transmits the dedicated channels, the NodeB should receive all the DPDCH before the reception of the DPCCH. Therefore, as the DPDCHs from a UE arrive they are stored in rx_dpdch[][]. When the DPCCH arrives, the NodeB checks if has received all the DPDCH from that UE in the function n_rx_dpdch() and if affirmative, the method decode() is called. This function takes DPDCH corresponding of that UE from the variable rx_dpdch[][] and extract the packets multiplexed within it. At the same time, the function checks if the packets have arrived in sequence by consulting the fields seqno_ and eoseq_. If all the fragments of a packet have been received correctly, the procedure ul_demux() is called. If not, the packet is dropped. ul_demux() is the dual of dl_mux() and ends with the instruction uptarget_->recv(), that sends the packet towards the MAC layer.
Cell search procedure
This procedure starts when the NodeB receives a preamble in the PRACH channel from a UE that it is not registered in the NodeB and its field hdr_phy::first_access_ is set to 1. This is done in the method packet_for_me(), called from recv(), which register the UE in the internal matrix ue_id[], giving it a free position within it. As was commented in the description of the methods, this method makes an addressing conversion for reducing the computing effort. After that, we carry out the RACH procedure as seen above.
When the RRC layer wants to send a LL_SETUP_REPLY to a UE, the physical layer in the NodeB receives a FACH. Before transmitting it, the physical layer invokes the procedure ue_registry() to give it a paging group for allowing the UE to receive paging requests (updating the variable ue_info_[][].paging_group_). Furthermore, the class PhyUmtsNodeb updates ue_info_[][].ul_freq to answer the UE with a FACH afterwards.
PRACH procedure
For having a whole understanding of this procedure it is recommendable to have a look to the PRACH procedure in the class PhyUmts. When the NodeB detects a preamble in recv(), we check if effectively the preamble is for this NodeB and then fill sig_ue_[] with the field hdr_phy::signature_ for the sender UE. Then, the method tx_aich() is called to simply construct a AICH and queue it in the PacketQueue aichtoTx_. Then, when pAichSlot_ expires the aichslotHandler() will transmit the AICH to the UE. If every has gone right, the NodeB would have to receive the message part by means a PRACH access. The PRACH has got control part and message part and we have implemented them as copies of the same packet, so we only take one of them and send it to higher layers with uptarget_->recv().
Paging procedure
This process starts when the physical layer receives a PCH from the RRC layer for a determinate UE. We have implemented this layer for allowing only one paging procedure at the same time, so we have to program the mechanisms to handle multiple paging requests. For doing that we use a lock called wait_ and a variable pch_recv_ where we store the packet we want to transmit in the paging procedure. Then, after receiving the PCH, we store the packet in pch_recv_ and invoke tx_pich() whereby the NodeB makes a broadcasting with the paging group of the target UE (stored in ue_info_[].paging_group_). The broadcasting is done by queuing the PICH in pkttoTx_. Next, the method tx_pch() is called from inside tx_pich(). This function is going to transmit the packet stored in pch_recv_ for a whole frame by queuing the PCH in pchtoTx_. Furthermore, the timer pPaging_ is initiated for sending a failure message to the RRC layer in case the NodeB does not receive response from the UE target of the paging in two frames time (20 ms).
The procedure finishes when a packet with a paging_ok gets the NodeB through the PRACH. Then, the recv() procedure sends the packet to the higher layers, pPaging_ is stopped and wait_ is set to 0 to allow other paging requests to take place.
Interference model
This interference model is based on the same principles than in the downlink (see class PhyUmts) but now we have to calculate the BLER for every UE attached to the NodeB. In the uplink, the Eb/N0 is calculated by the model:
![]()
where I is the received
interference power (for a UE that transmits to the NodeB, only other UEs that
are transmitting in the same frequency influence). W is the chip rate (3.84
Mcps) and R is taken from the variable ue_info_[].lastRate (update its value when a
DPCCH from a UE in our cell is received). The physical layer in the NodeB is
constantly taking power measurements in the DPCCH channel and periodically
(interval fixed to 5 radio frames by pBler_ timer) calculates Eb/N0 and BLER.
Prx is taken from the physical header (as we explained before) of the DPCCH targeted to our NodeB, and I is taken from the DPCCH targeted to our NodeB or other (and it comes in the same frequency that the UE for which we are trying to calculate the interference). Once we have got Eb/N0 we consult a table (class BlerTable implemented in phy-umts.h) to get the BLER and then, that result is passed to the MAC layer. Here there is the blerHandler():
// Called when BlerTimer
expires.
// Timer for calculating
the Interference
void
PhyUmtsNodeb::blerHandler(Event *e)
{
double Eb_No, bler, I = 0;
int i, j = 0;
// Restart timer for next time.
pBler_.start((Packet *)e, 2*UMTS_FrameTime);
for (i=0; i<MAX_NUM_UE; i++) {
if (ue_info_[i].ul_freq < 0) {
// there is not interference for i freq
continue;
}
if ((ue_info_[i].lastRate > 0) &&
(noIoth[ue_info_[i].ul_freq] > 0)) {
// if there has been any interference
if ((noIoth[ue_info_[i].ul_freq] - ue_info_[i].nSamples)
> 0) {
I = (Ioth[ue_info_[i].ul_freq] - ue_info_[i].IntPower)/
(noIoth[ue_info_[i].ul_freq] -
ue_info_[i].nSamples);
Eb_No = (CHIP_RATE*ue_info_[i].Prx) /
(ue_info_[i].lastRate*I);
} else {
// no interference at all
Eb_No = 1e38;
}
} else {
// no interference at all
Eb_No = 1e38;
}
bler = BTable->getbler(Eb_No); // consult table (Eb_No)
ue_info_[i].ul_ErrorRate = (int)(1/bler) + 1;
error[j] = ue_info_[i].ul_ErrorRate;
addr[j] = ue_id[i];
ue_info_[i].nSamples = 0;
ue_info_[i].IntPower = 0;
j++;
}
for (i=0; i<MAX_NUM_FREQ; i++){
Ioth[i] = 0;
noIoth[i] = 0;
}
// put bounds to the arrays error[] and addr[]
error[j] = 0;
addr[j] = -1;
mac_->ul_interference(addr, error); // send the results to MAC
return;
}
The functions the MAC layer has to do are basically the channel mapping and the interference model implementation. As we saw in the Physical Layer, the interference model is based on power measurements to calculate the interference and the BLER afterwards. As its name indicates, the BLER indicates the Error Probability for the Transport Blocks, so we decided to divide the implementation of the error model within the physical layer and the MAC layer. The first one will make the measurements and calculations of Eb/No and BLER, and pass them to the MAC layer, which will be the entity in charge of dropping the whole transport block in case of erroneous reception.
This class is implemented in files mac-umts.{cc.h} (see code section). The main variables and procedures are the following:
// procedures
void
waitHandler(void); // handler of class
WaitTimer
void
dl_interference(unsigned long error);// for passing results from PHY
int
check_error(void);// returns if the actual Transport Block is erroneous.
void sendpkt(Packet*
p); // if erroneous packet, drop it. If
not, send it.
// variables
int error_; // 0 not erroneous, 1 error in
transport block
unsigned long
dl_error; // error probability in
transport block.
WaitTimer
mhwait_; // timer for error
calculation
Basically, the general working of the class is: when a packet from the physical layer (in DCH channel) is received, the recv() method invokes sendpkt(). This later function just checks the variable error_ to decide what to do with the packet: if 1, drops it, but if not, deliver to the RLC layer. This variable error_ is updated every TTI thanks to the execution of waitHandler() (timer), which calls the method check_error(). This function is the one that calculates if all the packets in the current TTI must be dropped based on the value of dl_error (updated periodically by the physical layer).
This class is implemented in files mac-umts-nodeb.{cc.h} (see code section). The main variables and procedures are the following:
// procedures
void
waitHandler(void); // handler of
class WaitNodebTimer
void
ul_interference(int addr[], unsigned long error[]);// for passing
//
results from PHY
void remove(int
addr); // free resources of ul_interf[]
void
storepkt(Packet *p);// if erroneous packet, drop it. If not, send it.
int
check_error(int pos);// returns if the actual TB is erroneous.
// variables
errormodule
ul_interf[MAX_NUM_UE]; // structure for
error model
WaitNodebTimer
mhwait_; // timer for error
calculation
// structure
for error model
struct
errormodule{
int phyaddr_; // physical address of the UE
int error_; // 0 not erroneous, 1 error in transport block
unsigned long ul_error_; // error probability in transport block.
};
The general working of this class is
basically the same of class MacUmts, but considering that it is needed to calculate the same for every
UE attached to the NodeB; when a packet from the physical layer (in DCH
channel) is received, the recv() method invokes storepkt(). This later function just checks the variable ul_interf[].error_ for that UE to
decide what to do with the packet: if 1, drop it, but if not, deliver to the
RLC layer. This variable ul_interf[].error_ is
updated every TTI thanks to the execution of waitHandler() (timer), which calls the method check_error(). This function is the one that calculates if all the packets in the
current TTI and for that UE must be dropped based on the value of ul_interf[].ul_error (updated
periodically by the physical layer by means of the method ul_interference()).
RLC Layer can offer three types of transfer services to the higher layers. The Transparent mode transmits upper layer PDUs without adding any protocol information, possibly including segmentation/reassembly functionality. The Unacknowledged mode transmits upper layer PDUs without guaranteeing delivery to the peer entity. Then, the unacknowledged data transfer mode will deliver only those SDUs to the receiving upper layer that are free of transmission errors by using the sequence-number check function. Finally, the Acknowledged mode transmits upper layer PDUs and guarantees delivery to the peer entity. In case RLC is unable to deliver the data correctly, the user of RLC at the transmitting side is notified.
Unlike the actual implementation of mobile nodes in ns, our project needed to have two different classes for the same layer (corresponding to UE and NodeB entities): class RlcUmts and class RlcUmtsNodeb. In both of them, the design is oriented to give the flows the maximum capacity of configuration, thus any flow can be configured as fragmented mode or not, and ACK mode or Non-ACK mode.
For the implementation of this layer we have used the RLC Layer that was developed by the GPRS extension for ns, although several improvements has been included. The main one has been the possibility of supporting, not only one application in the UE but several, and not only one terminal in the Node B when working in ACK mode, but all of them.
As we have mentioned just above, our UE node and Node B have different classes to implement the RLC layer, that is, class RlcUmts in rlc-umts.{cc, h} for UE node, and class RlcUmtNodeb in rlc-umts-nodeb.{cc, h} for Node B. They both are derived from class LinkDelay, so they have the same basis and basically the same functions. Their main difference is that class RlcUmts manages the incoming and outgoing flows only for that node, while the class RlcUmtsNodeb has to manage all the incoming and outgoing flows for each UE. That increased considerably the complexity of the variables and procedures, just as the computational processing and the memory required.
Another aspect we have to notice is that our implementation (as in UTRAN) the acknowledge mode is performed link by link, not point to point as it used to be before. This introduces improvements such as less delay. Although, because of this extension some more complexity has been added to the system, above all in the Node B RLC layer that has to support structures of MAX_NUM_UE in the cell multiplied by the MAX_FLOWS in each one (that is the number of outgoing and incoming flows that the UE can support).
The struct hdr_rlc_umts is implemented in rlc-umts.h. It is based on the header that was defined in GPRS extension but with several modifications due to our implementation requirements. Next, we are going to explain the fields of the header:
rlctype_ informs about the kind of packet that is transmitted. Is the type RLCFrameType, an enumerate type with possible values: RLC_DATA, RLC_ACK and RLC_NONACK. The last one is not going to be used but has been introduced for future extensions.
ack_ and frag_. When creating an application we should specify through the script what type of application it is, e.g. video, audio or ftp. Depending on the type of application, the RRC will decide if it needs fragmentation and/or ACK mode, by defect we are going to suppose that every application is going to require fragmentation. These variables are integers and specify if the packet belongs to a flow that is working in ACK mode (ack_ to 1) and/or if it requires fragmentation (frag_ to 1).
seqno_. Is the sequence number of the fragment and is represented with an integer. It is used in reception for reassembly and, error and duplicate detection.
ackno_. This field is an integer and is used when we send an ACK. It is filled with the sequence number of the last packet received plus one.
bopno_. This integer is the number of the first fragment of a specific packet.
eopno_. This integer is the number of the last fragment of a specific packet.
psize_. It is the size of the packet that is sent before fragmentation. It is an integer.
sendtime_. It is a double that represents the time when the packet was sent.
struct
flow_inf {
int flow_: is the flow_id of the flow at which belongs the following information.
int fraged_: informs if the packets of that flow may be fragmented.
int acked_: informs if the that flow may be transmitted with ACK mode.
int seqno_: is the sequence number that we will assign to the next fragment of
that flow. It is used for correctly reassembly.
int ackno_: is the sequence number of the last ACK received of this flow.
int rackno_: is the sequence number of the last fragment received of this flow in order.
int window_: is the size of the transmission buffer (Txbuf_).
PacketQueue* Txbuf_: queue where packets that have been transmitted are waiting for ACK.
PacketQueue* Rxbuf_: queue where packets received out of sequence are stored waiting for the previous ones.
int unackseqno_: is the number of the fragment that we are waiting in non-ACK-mode.
int numdups_: is the number of supplicated ACKs.
Packet* lastRx_: is the last packet received in sequence stored to ask for retransmissions.
};
flow_inf info_[MAX_FLOWS]: is an array of outgoing and incoming flows. It stores the information related to a specific flow. It is the type flow_inf.
flow_inf hinfo_[MAX_FLOWS]: is the same as the previous one but stores the information when the mobile is in performing the handover procedure. This allows the implementation of softer handover.
PacketQueue* buf_: is the queue where packets (in ACK mode) arrived from the IFQ are stored waiting to be transmitted.
nsaddr_t ip_nodeb_: is the IP direction of the Node B where the UE is currently allocated.
nsaddr_t ip_ue_: is the its IP direction.
int handover_: this variable is changed to 1 when a handover procedure begins. It is used to perform the softer handover, allowing the UE to receive from two Node Bs at the same time during a while.
int rlcfragsz_: is the fragmentation size. This valiue should be introduced by the script; if it not introduced it takes a default value of 20 bytes stored in ns-default.tcl.
int length_: is used for internal operations with buf_ queue.
rTxList
*rtx_: is a
list of timers to retransmit packets not acknowledged. Each timer belongs to an
application.
In this section we will try to explain the main procedures of this class, its functions and, also how they develop those functions. For better understanding of the processes, the procedures are explained in pseudo-code. Nevertheless is worth having a look to the implementation in the code section.
void recv(Packet* p, Handler* h)
Is the first place where all the packets (data and signaling) are received. This method distinguishes between data and signaling. If the packet arrived is signaling, it sends the packet to upper or lower layers (depending on the direction), while if is data or ACK, the packet will be processed.
void RlcUmts::recv(Packet*
p, Handler*) {
if the packet is an ACK
recvACK(p)
else
if the packet arrived is data
recvDATA(p)
else
if direction is UP
removes rlc header
sends the packet to LL through the scheduler
if direction is DOWN
adds
rlc header
sends the packet to MAC through the scheduler
return
}
void recvACK(Packet* p)
This method detects if it has been received a duplicate ACK of an ACK correctly. If it has received a correct ACK, it removes packets form info_[].Txbuf_, updates the field info_[].ackno_ and calls sendDownDATA for trying to send more packets. If a duplicate ACK has been received it sends again the packets requested by the ACK.
void
RlcUmts::recvACK(Packet* p) {
if ACK duplicated
for the first sequence number on the header to the last
sendDownDATAonACK(flow_position, seqno)
frees
the packet
return
else if is the ACK expected or higher
info_[flow_position].ackno_ = ACK number received
else
frees
the packet
return
goes through the whole info_[flow_position].Txbuf_
if
packet with seqno < ackno received
remove packet from the queue
if there are pkts to tx and info_[flow_position].Txbuf_ not full
sendDownDATA(flow_position)
return
}
void recvDATA(Packet* p)
This method directs the data packet to the correct procedure depending on the direction (UP or DOWN) if is in ACK mode or needs fragmentation/reassembling. If not sends the packet to the correct layer.
void
RlcUmts::recvDATA(Packet* p) {
if direction is UP
remove rlc header
if
the packet requires reassembling or is in ACK mode
sendUpDATA(p, flow_position)
else
sends the packet to
LL through the scheduler
if direction is DOWN
if
the packet requires reassembling or is in ACK mode
enqueDATA(p,
flow_position)
else
adds rlc header
sends the packet to MAC through the
scheduler
return
}
void sendUpDATA(Packet* p)
This method is called when receiving a packet from the air interface to the application and is in ACK mode or requires reassembly.
void
RlcUmts::sendUpDATA(Packet* p, int flow_position) {
if packet erroneous
drop packet
else
if
non-ACK mode
if is the seqno expected
info_[flow_position].unackseqno_++
if is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
else
frees the packet
else if handover and is seqno expected from
the old Node B
hinfo_[flow_position].unackseqno_++
if is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
else
frees the packet
else if is 1st fragment of a pkt
coming from a wired node
info_[flow_position].unackseqno_ = 1
if is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
else
frees the packet
else if packet out of sequence
info_[flow_position].unackseqno_ = seqno
arrived + 1
frees the packet
if
ACK mode
if the seqno received =
info_[flow_position].rackno_
info_[flow_position].rackno_++
stores in info_[flow_position].lastRx_ a
copy of the pkt
if
is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
goes through the whole
info_[flow_position].Rxbuf_
if is the seqno = info_[flow_position].rackno_
info_[flow_position].rackno_++
stores in lastRx_ a copy of the pkt
if
is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
removes packet from the queue
sendACK(p, flow_position)
if the seqno received >
info_[flow_position].rackno_
enques the packet in
info_[flow_position].Rxbuf_
sendACK(p, flow_position)
if handover & seqno received =
hinfo_[flow_position].rackno_
hinfo_[flow_position].rackno_++
if is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
goes through the whole
hinfo_[flow_position].Rxbuf_
if is the seqno = hinfo_[flow_position].rackno_
hinfo_[flow_position].rackno_++
if is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
removes packet from the queue
sendACK(p, flow_position)
return
}
void enqueDATA(Packet* p, int flow_position)
This method is called when receiving data from the IFQ.
void
RlcUmts::enqueDATA(Packet* p, int flow_position) {
if the flow requires fragmentation
psize = rlcfragz
else
psize = size of the arrived packet
calculates the bopno and eopno
for bopno to eopno
fragments the packet in fragments of psize
if is the last fragment, adds padding if needed
adds to them the seqno
info_[flow_position].seqno_++
if non-ACK mode
sends the packet to MAC through the
scheduler
if ACK mode
enques the packet in
buf_
if ACK mode
sendDownDATA(flow_position)
return
}
void sendDownDATA (int flow_position)
It is a recursive method. It extracts packets that belong to a specific flow from the queue buf_ until there were no more packets of that flow stored or until the transmission buffer was full. We used the variable length to know when we have finished to go through the queue buf_.
void
RlcUmts::sendDownDATA(int flow_position) {
if buf_ empty or no more packets of that flow
length = 0
return
else
extracts a packet
length++
if the packet extracted does not belong to this flow
enques the packet
sendDownDATA(flow_position)
if belongs to this flow
if Txbuf_ length <
info_[flow_position].window_
enques a copy of the pkt in
info_[flow_position].Txbuf_
sends
the packet to MAC through the scheduler
length—-
sendDownDATA(flow_position)
else the buffer is
full
length = 0
enques the packet at the beginning of buf_
return
}
void sendDownDATAonACK (int flow_position, int sequence number)
This method is called when a duplicate ACK has been received. It sends again the packet with the same sequence number as the sequence number received in the ACK. It is worth mentioning that the retransmission is based on transmission blocks due to the interference affects to the entire block and not only to one packet.
void
RlcUmts::sendDownDATAonACK(int flow_position) {
points to the first packet in info_[flow_position].Txbuf_
goes through info_[flow_position].Txbuf_
if packet’s seqno = sequence number
sends the packet to MAC through the
scheduler
return
points
to the next packet
return
}
void sendACK (Packet* p, int flow_position)
This method is called when the expected packet has arrived. It takes this packet and the position of the flow in the array info_[]. This procedure sends to the packet Node B an ACK with uid and ackno_ the same as the seqno_ of the next fragment expected.
void
RlcUmts::sendACK(Packet* p, int flow_position) {
allocates
a packet and fills its headers asking for the first requested fragment and the first fragment arrived out of
sequence
sends the packet to MAC through the scheduler
stops and starts the retransmission timer
return
}
void store_flow (int acked, int fraged, int flowid)
This method is invoked from the RRC when a new flow, either outgoing or incoming, is detected. Its function is to update the info_[] array with the new information about the flow, such as flow_id, if works in ACK mode or if needs fragmentation. It is also called when the UE is in handover procedure to update the value of the number of sequence expected, in both ACK (rackno_) and non-ACK (unackseqno_) mode for each registered flow. That allows the correct reception of data coming from the new Node B.
void remove_flow (int flowid)
This method is also invoked from the RRC. It is used when it must remove a flow because has happened any error or because the application has finished. Its function is to free the resources assigned to that flow in the info_[] array.
void get_acked (int flowid)
This method is invoked from the RRC and returns to it if that specific flow is working in ACK mode by looking up in its info_[] array.
void get_fraged (int flowid)
This method is invoked from the RRC and returns to it if that specific flow needs to be fragmented by looking up in its info_[] array.
void handover (int flowid)
This method is invoked from the RRC when a handover procedure begins or ends (depending on the flag value; 1 handover beginning). Its function is to update the variable handover_ and all the fields in the hinfo_[] array with the values of the old Node B that were in the info_[] array.
void RlcUmts::handover(int
flag) {
handover_ = flag
if flag = 1
for each application
puts the fields in
hinfo_[] = fields in info_[]
if flag = 0
for each application
removes the values in
hinfo_[]
return
}
void rTxHandler (int flowid)
This method is called when the retransmission timer attached to a specific application expires. These timers are leafs of a list of timers implemented in class rTxList and class rTxTimer located in file rlc-umts.{cc, h}.
void RlcUmts::rTxHandler(int flow) {
sends
the ACK of the last packet received and stored in info_[].lastRx_
return
}
As we commented before, this class is quite similar to the class RlcUmts, because of that almost all the variables and structures are the same and performs the same functions.
struct flows_nodeb is similar to flow_inf in the UE and is used in the same way, but has two fields more:
q int src_. This field is the physical address of the flow source. That field is necessary due to Node B must distinguish between the users in its cell.
q int handover_. This variable informs to the RLC layer about whether the incoming flow with direction UP belongs two a UE that has just made handover to our cell. It is update by the RRC layer through the RLC layer method store_flow (explain later).
flows_nodeb info_[MAX_LOAD]. This variable is the same as info_[] in class RlcUmts and is basically used by the same methods.
This class also has the variables: PacketQueue* buf_, nsaddr_t ip_nodeb_, int rlcfragsz_ and int length_.
The main procedures in the class RlcUmtsNodeb are:
virtual
void recvACK(Packet* p);
virtual void recvDATA(Packet* p);
virtual void sendUpDATA(Packet* p, int pos);
virtual void enqueDATA(Packet* p, int pos);
virtual void sendDownDATA(int pos);
virtual void sendACK(Packet* p, int pos);
virtual void sendDownDATAonACK(int pos, int seqno);
void store_flow(int acked, int fraged, int flowid, nsaddr_t src, int handover);
void remove_flow(int flowid, nsaddr_t src);
Almost all of them perform the same functions as in class RlcUmts, even though is worth reviewing the methods sendUpDATA and store_flow.
void sendUpDATA(Packet* p)
This method is called, as we explained before, when receiving a packet from the air interface to the application and is in ACK mode or requires reassembly. The difference between this method and the one explain in class RlcUmts, is the way each one resolves the handover procedure. Here, we must take into account that news UEs with applications already running can make handover to our cell and we have to be able to support this news applications even if we do not know with sequence number is going to arrived. The following pseudo-code explains better that problem, but is recommended to have a look to the code in the code section.
void
RlcUmts::sendUpDATA(Packet* p, int flow_pos) {
if packet erroneous
drop packet
else
if
non-ACK mode
if is the seqno expected
info_[flow_pos].unackseqno_++
if is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
else
frees the packet
else if is 1st fragment of a pkt
coming from a wired node
info_[flow_pos].unackseqno_ = 1
if is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
else
frees the packet
else if packet out of sequence
info_[flow_pos].unackseqno_ = seqno
arrived + 1
frees the packet
if
ACK mode
if info_[flow_pos].handover_ &
(info_[flow_pos].rackno_+1)=0
info_[flow_pos].handover_ = 0
info_[flow_pos].rackno_
= seqno of the packet received
jump to code: GO
if the seqno received =
info_[flow_position].rackno_+1
info_[flow_position].rackno_++
GO: stores a copy of the packet in info_[ flow_pos].lastRx_
if
is the last fragment of the packet
reassemblies the packet
sends the packet to LL through the scheduler
goes through the whole
info_[flow_position].Rxbuf_
if is the seqno = info_[flow_position].rackno_
info_[flow_position].rackno_++
if is the last fragment of the
packet
reassemblies the packet
sends the packet to LL through the scheduler
removes packet from the queue
sendACK(p, flow_position)
if the seqno received >
info_[flow_position].rackno_
enques the packet in
info_[flow_position].Rxbuf_
sendACK(p, flow_position)
return
}
void store_flow(int
acked, int fraged, int flowid, nsaddr_t src, int handover)
This method is invoked from the RRC when a new flow, either outgoing or incoming, is detected. Its function is to update the info_[] array with the new information about the flow, such as flow_id, if works in ACK mode or if needs fragmentation and if the flow has been update because a handover procedure.
This queue had to be added to make a flow control depending on the SF allocated for the flow/UE in uplink/downlink respectively. The IFQ was located between the RRC and the RLC layers but it does not represent a layer, but a mechanism for a better performance of the overall system.
This queue only handles incoming packets from upper layers, as can be seen in Figure 2, whereas the packets from RLC upwards do not need to pass through the IFQ because the RRC layer can support all thus traffic without any restriction. Basically, each TTI the IFQ let pass down a number of bytes, but it cannot make segmentation, thus only whole packets can be transmitted to the RLC layer. For doing this, in the method add-interface() of classes Node/MobileNode/UE and Node/MobileNode/NodeB we specified that the object downtarget_ of the RRC layer is the IFQ, the object target_ of the IFQ is the RLC, but the object uptarget_ of the RLC is not the IFQ, but the RRC layer.
Since in the UE and in the NodeB the way of transmitting is different, it was necessary, as it happened with the rest of layers, to implement two IFQs, one for the Node B and other for the UE node. In the UE, the IFQ will be in charge of let go through a maximum number of bytes corresponding to the maximum amount of information for the SF allocated in the uplink. In the NodeB, the IFQ will be in charge of do the same but for each UE it has got in its cell. The maximum number of bytes per TTI is configured by the RRC layer, which uses this mechanism to ensure that the physical layer does not receive more information that can be cursed a priori. The IFQ of the UE node is implemented by the class FCQueue in fc-queue.{cc, h} and the IFQ of the Node B is implemented by the class BsFCQueue in bsfc-queue.{cc, h}, both of them derived from class DropTail.
The main difference apart or the one already mentioned is that the IFQ in NodeB has got a pointer to the upper layer (RRC) in case of handover. When this procedure is happening and the previous NodeB the UE was attached to notice the change, all the packets stored in the queue have to be delivered to the new NodeB by sending them through the fixed network, so the looses fo packets are minimised. To do this, the class BsFCQueue has got an object callback_ which points to the RRC layer.
This class is composed basically for a timer, a PacketQueue and a structure to store information about the incoming flows.
PacketQueue q_. This is the place where packets arrived are stored. This queue is inhered from the parent class DropTail and has a limit of its capacity given by the parameter qlim_.
FCTimer qTimer_. This timer is an object of the class FCTimer. This timer is started in the method init() with a period of TTI, each TTI is started again. When it expires the method fcHandler() is invoked. This method is the core of the class FCQueue, and it will be in the next section.
double tti_. This variable is the TTI. It takes its value from the constant TTI. The possible values are 10, 20, 40 or 80 ms. It is going to mark how often packets will be sent to the physical layer.
ifq_control ifqc[MAX_NUM_FLOWS]. This variable is where the information about the different application is stored. It is the type ifq_control, which has three fields:
q flowid_ to identified the application arrived;
q
bytes_per_tti_, this parameter is passed by the RRC layer and is the maximum
number of bytes of one application that the IFQ can let pass through in each
TTI;
q len is the number of bytes that were sent in previous TTI. When an application is running, this variable is decremented in each TTI the number specified in bytes_per_tti_. It is used to know when we can send another amount of data. That is, when the value of this variable is smaller than bytes_per_tti_ more packets can be sent. This variable is incremented with the number of bytes transmitted in each packet. If after sending a packet this variable continues being smaller than bytes_per_tti_ another packet can be sent, but if is bigger the queue does not let pass more packets. It will be clarify when explaining the procedure fcHandler().
This procedure has three main methods:
void recv(Packet* p,
Handler*)
This method is the first place where data and signaling packets, with direction DOWN, arrived. Once here, it has to decide what to do with them. This decision is taken depending on the channel the packet is carried on. If the channel is the logical channel DTCH and the length of the queue q_ is smaller than qlim_ the packet is stored. If the length of q_ is qlim_ the packet is dropped. If the packet arrives in a different channel is just sent to the RLC layer.
void reg(int flow, double
l)
This method is used to register and deregister an application and it is invoked from the RRC layer. It takes two parameters: flow is the flow_id of the application packets and l is the bytes per TTI of this application that the IFQ has to send in each TTI. l can take the value 0 and other values. If l is other value different from 0 means that the RRC is registering a new application, so ifqc[].flowid_ is filled with the flow_id of the new application and ifqc[id].bytes_per_tti_ takes the value l. If l is 0 means that the RRC wants to deregister a flow. In this case that method goes through the queue q_ looking for packets of that flow, if finds any, it will drop it. After the resources allocated for that flow are liberated.
void fcHandler(Event *e)
This method decides which packets can be sent. When the timer qTimer_ expires this method is called. It first programs the timer again with TTI milliseconds. Then goes through the queue q_ and for each packet verifies if ifqc[].len is smaller than ifqc[].bytes_per_tti_; if is smaller it adds the size of the packet that is going to sent to the previous value of ifqc[].len and sends the packet. If the value of ifqc[].len is bigger it cannot send the packet, so it stores it again. Then, the methods decrements ifqc[].len of each application registered in ifqc[].bytes_per_tti_.
This class has the same variables and structures than the class FCQueue explained before. The main difference is that class FCQueue worked with applications and this class works with UEs, so all the variables and method that used flow_ids now are going to work with IP addresses. The other difference is that this class has an object callback_ that acts as an object uptarget_ between the IFQ and the RCC layer. It is used when a UE makes handover to other cell and the packets stored in the queue must be sent to the new Node B. This procedure will be reviewed in the next point when explaining the method reg().
double tti_. This variable is the same as the one explained in class FCQueue.
BsFCTimer qTimer_. This variable acts in a similar way that the timer explained in class FCQueue but this timer is an object of class BsFCTimer.
user users_[MAX_NUM_UE]. This matrix has the same function as the one explained in class FCQueue, ifqc[], but instead of working with flow_id, it works with the IP address of the node destiny.
The procedures recv() and fcHandler() are similar as the ones explained in class FCQueue. The procedure reg() varies a little bit.
void reg(nsaddr_t dir, double l)
The thing that varies this method from the method reg() of class FCQueue is that when the IFQ has to deregister a user, instead of dropping the packets belonging to that user that were stored in the queue, it sends them to the RRC layer by means of the object callback_.
The Radio Resource Control (RRC) layer handles the control plane signalling of Layer 3 between the UEs and UTRAN. Basically, we have got functionalities such as establishment, re-establishment, maintenance and release of an RRC connection between the UE and UTRAN, establishment, reconfiguration and release of Radio Bearers, assignment, reconfiguration and release of radio resources for the RRC connection, RRC connection mobility functions, paging/notification and routing of higher layer PDUs.
When we designed the stack layer, it was found that there was a kind of RRC layer used by the class MobileNode, but only implemented basic functionalities related to routing. However, our system does not need a layer with these routing features because our routing is going to be done in a different way. The link layer only has to send the packets to the higher layers and the entry_ (new classifier) will route the packets properly.
Unlike the actual implementation of mobile nodes in ns, our project needed to have two different classes for the same layer (corresponding to UE and NodeB entities): class LLUE and class LLNodeb. These two classes have inherited from class LL due to compatibility reasons with other network objects.
We have to differentiate clearly between uplink and downlink. There are 224 long and 224 short uplink scrambling codes. The allocation of scrambling codes in the uplink requires the interaction with the system, but since millions of codes are available, no uplink code planning is needed. In the downlink, physical channels can be transmitted either with the same primary scrambling code or using a secondary scrambling code taken from the set of codes associated with it. In our case, we are transmitting with the primary scrambling code.
When it comes to spreading codes, in the uplink they are used for separating physical channels in the same terminal, whereas in the downlink for separating downlink connections to different users within one scrambling code (cell). This is traduced in the fact that spreading code planning is only needed in the downlink.
Anyway, both the uplink and the downlink have to apply mechanisms for controlling the flows according to the resources allocated for them. In general we can distinguish between:
· User rate: is the rate at user level. For example, if an application is specified to transmit with 40 Kbps, that is the user rate.
· Physical rate: is the rate in the physical layer resulting from the user rate plus the overhead due to headers and coding. The expression to calculate it is the following:
![]()
where Rrlc is the effective rate in the RLC (due to the fragmentation and padding in RLC layer), Srlc is the fragment size in RLC layer, Hrlc and Hmac are the size of the headers added in RLC and MAC layers respectively and Ccod is the channel coding (x2 for CONV_HALF and x3 for CONV_THIRD and TURBO). For calculating the effective rate in the RLC, we have to take into account the way the applications send the data. There is a fixed size for packets and what varies depending on the rate is the interval of time between the transmissions of two consecutive user packets. If we call T to the that time, Npkts to the number of RLC packets that corresponds to a user packet, the Rrlc can be calculated as following:
![]()
For the previous example, with CONV_HALF channel coding, the resulting physical rate would be around 90 Kbps.
·
Maximum physical rate (for a SF): is the maximum rate supported by the physical layer for a SF. This
is necessary to calculate because when an application arrives, we need to know
the SF that needs. For instance, with the example used until new, with a SF=32
in the uplink would be enough because is traduced in a maximum physical rate of
120 Kbps < 90 Kbps.
· Maximum rate in IFQ layer: is the maximum rate that the IFQ layer has to course to the MAC layer. It is necessary for letting pass the maximum number of bytes per TTI according to the maximum physical rate allocated for that flow. Being Bifq the maximum number of bytes per TTI in the IFQ layer, Bphy the maximum number of bytes per TTI in the physical layer (for the SF) and C’cod the inverse operation of Ccod. The expression is:
![]()
As was commented above, for the implementation of this layer we have done two new classes inherited from class LL: the class LLUE (files ll-ue,{cc.h}) and class LLNodeb (files ll-nodeb,{cc.h}). The header for this class is basically the one that was implemented in file ll.h but we extended it by adding some variables. The struct hdr_ll has got this main fields:
struct hdr_ll {
LLFrameType lltype_; //
link-layer frame type
int seqno_; //
sequence number
int ackno_; //
acknowledgement number
int bopno_; //
begin of packet seqno
int eopno_; //
end of packet seqno
int psize_; //
size of packet
double sendtime_; //
time the packet is sent
int paging_ok_; //
for paging procedure
double tx_rate_; //
transmission rate of the data flow
:
The new fields we added are paging_ok_ and tx_rate_, which informs about if the response of a paging procedure has been affirmative and the transmission rate of the data flow the packet carries. However, the main field is lltype_, which is the mechanism we have got to indicate the frame type for this layer and is used for signaling purposes. The final structure of the enumerated LLFrameType is:
enum LLFrameType {
LL_DATA =
0x0001,
LL_ACK =
0x0010,
LL_SETUP =
0x0011,
LL_SETUP_REPLY =
0x0100,
LL_FAILURE =
0X0101,
LL_PAGING =
0x0110,
LL_RES_REQ = 0x0111,
LL_RES_REPLY =
0x1000,
LL_HANDOVER =
0x1001,
LL_RELEASE_REQ =
0x1010,
LL_RELEASE_REPLY = 0x1011
};
Then, the meaning of LL_DATA is that the content of the packet is user data, LL_ACK involves ACK interchanging in this layer level (not used in our system), LL_SETUP indicates that the packet is a request of access to a cell (switch on procedure and handover procedure), LL_SETUP_REPLY is the answer for that request, LL_FAILURE is a general indication of error in a lot of procedures, LL_PAGING is like LL_SETUP but for paging procedure, LL_RES_REQ is the case for resource requests, LL_RES_REPLY involves an affirmative response of the NodeB to a resource request, LL_HANDOVER is used for the handover procedure, and finally, LL_RELEASE_REQ and LL_RELEASE_REPLY are messages for releasing resources.
It is very convenient to explain in
detail how the RRC procedures have been designed for implementing them in the
classes LLUE and LLNodeb. Then, establishment, re-establishment, maintenance and
release of an RRC connection between the UE and UTRAN, establishment,
reconfiguration and release of Radio Bearers, assignment, reconfiguration and
release of radio resources for the RRC connection, RRC connection mobility
functions can be resumed in three main procedures:
This procedure takes place when a UE wants to switch on or when a handover has produced and the UE wants to register in the new cell. In Figure 3 it is shown the interchanging of signaling between RRC entities (only link layer messages, the whole procedure involving all the layers will be explained later). The UE A produces a LL_SETUP message that is transmitted through the air interface and when it gets the RRC layer in the NodeB, it register the UE A if there are available resources. After that, the NodeB responds to the UE A with a SETUP_REPLY message, so the UE updates its internal registers and passes into a connected mode (in the new cell).

Figure 3. Cell search procedure.
This procedure is carried out when a UE wants to initiate a communication. When incoming packets reach the RRC layer in the UE A, it detects that it is a new application and starts the procedure. First, it sends a LL_RES_REQ to the NodeB is attached and after that, it queues the user data packet (the UE A will do this until it has got resources to send them to the UE B). In the example shown in Figure 4, the destination of the application is a UE (UE B) located in other cell, but the procedure in analogous or simpler for other cases. In the figure is shown the interchanging of signaling between RRC entities (only link layer messages, the whole procedure involving all the layers will be explained later). When the NodeB 1 detects the message, it checks where is the destination. In this case, the UE B is in other cell, so it sends the message upwards (through the wired network). In case the UE B is in NodeB’s cell, the paging procedure initiates directly. The reception of the LL_RES_REQ in NodeB 2 invokes the procedure of paging (search for the UE B by LL_PAGING and paging_ok_). At this point, the Node B knows the location of the UE B, so it checks if there are resources for the incoming application (in downlink) and establishes them. Then, a message of LL_RES_REPLY is sent to NodeB 1. This node also checks the resources for the uplink transmission and responds the UE B with the LL_RES_REPLY message. The UE A now can start the transmission of packets, first from the buffer and later, directly as received from upper layers.

Figure 4. Connection Setup procedure.
This procedure is used when a UE changes its location from a cell to another. As we saw in the Physical Layer, the UE is constantly taking measurements of power so when it decides, passes a directive (thanks to send_to_rrc()) to the RRC layer with the field ptype_ of the common header = PT_RRC_HANDOVER. After the decision of the physical layer about the new cell to attach with, the UE initiate a setup procedure with the new cell, as was seen previously. Figure 5 shows the whole procedure. The differences with the setup procedure are in the RRC layer of the NodeB 2; after the registration of the UE, the NodeB 2 sends a message LL_HANDOVER through the fixed network to the previous NodeB the UE was attached (to NodeB 1, for deregistering the UE A). Other important step is done in the NodeB 2 before the transmission of data can start. This is the updating of the addressing and routing tables for the UE A to make it reachable for any entity in the scenario.

Figure 5. Handover procedure.
The class LLUE has several variables and objects used to perform functions related to data and control management. As we saw before, the RRC layer deals with the general management of the other layers functions by passing directives to them. To carry out this function it should have three variables:
PhyUmts* phy_;
RlcUmts* rlc_;
FCQueue* ifq_;
These variables are pointers to objects of class PhyUmts, class RlcUmts and class FCQueue, respectively, configured by the method add-interface (explain in previous points).
The RRC layer has two variables that are directly related with data management, they are: apps_[] and q[].
apps_info apps_[MAX_NUM_FLOWS]. This array is in charge of storing the information related with all the applications generated in this UE. It has MAX_NUM_FLOWS positions; that is, so many positions as the maximum number of originated flows (originated in this UE) that the UE can support. Its type is apps_info; this structure has the following fields:
struct
apps_info{
int flowid_;
nsaddr_t dest_;
int wait_;
int sf_;
double rate_;
double user_rate_;
double interval_;
Chan_Coding c_coding_;
};
flowid_. It is the identity of the flow. That value is the key to access the array, due to there cannot be two applications originated in the same node with the same flow_id.
dest_. It is the IP address of the flow destiny. It is usually the IP address of another UE or a fixed node, although it also can be the IP address of a Node B.
wait_. This field is used during the resource allocation procedure. It can take three values: -1, 0 and 1 for a specific flow. If its value is –1 means that the first packet of the flow has not arrived yet. This variable turns from –1 to 0 when arrives the first packet. Then, the packet is stored in a buffer and a resource request message is sent to the Node B to ask for resources and begin the transmission. All the packets of this flow will be stored in the buffer until the resource reply message arrives to the UE. When the resources for that flow have been allocated this value turns to 1, which means that the packets can be sent to the destiny.
sf_. This variable is the spreading factor of the flow. Its value is calculated based on the user transmission rate arrived.
rate_. It is the rate that is going to be sent over the air interface. For calculating these value is taking into account the application type, to know the type of channel coding that is going to be used, the size of the headers that are going to be added to each packet, the RLC fragment size and the packet size.
user_rate_. It is the rate at which the application upon the RRC layer transmits.
c_coding_. This variable represents the type of channel coding that is going to be used to transmit the information over the air interface. It can take three values: CONV_HALF, CONV_THIRD or TURBO, that means, convolutional ˝, convolutional 1/3 or turbo coding, respectively.
PacketQueue* q_[MAX_NUM_FLOWS]. This variable is an array of PacketQueues of MAX_NUM_FLOWS components. These queues are used for blocking the flows arrived from the application until the resource reply message arrives from the Node B.
The RRC layer also has other variables for supporting control. These are:
int ue_address_ and nsaddr_t ip_ue_ are the physical address and the IP address of the UE.
int nodeb_address_ and nsaddr_t ip_nodeb_ are the physical and the IP addresses of the Node B in which cell is currently allocated the UE.
int handover_. This variable takes the value 1 when the UE is going to perform handover to other cell; in other case has the value 1.
int ue_state_. This variable represents the current state of the UE. It can take three values: 0, 1 or 2. 0 means that the UE is switched off, and every packet that arrives to the layer during that state will be dropped. When the terminal is switched on by calling the procedure switch_on(), this variable turns to 1. That means that the terminal has begun a cell search procedure and is waiting for a setup reply. In this state the signaling messages from the Node b can be processed; although, it cannot accept data from the application yet, and like before, all the packets arrived with direction DOWN will be dropped. When the reply is received, the variable takes the value 2. Now is able to accept both data and signaling.
TUEList *appstimer_. This variable is a pointer to an object of type TUEList. The main methods and variables of classes TUEList and AppsTimer are shown below.
class
TUEList
{
public:
TUEList(LLUE* m) : ll(m) {
Head
= new AppsTimer(ll); Tail=Head; CurrentPtr = Head;
}
TimerPtr Previous(TimerPtr);
void AddANode(int flow);
void DeleteANode(int flow);
void start(int flow, double time);
void stop(int flow);
LLUE *ll;
TimerPtr Head,Tail,CurrentPtr;
};
class
AppsTimer : public Handler {
public:
AppsTimer(LLUE* m, double s = 0) : ll(m),
Next(0) {
busy_=paused_=0; stime=rtime=0.0;
flow_=-1;}
virtual void handle(Event *e);
virtual void start(double time);
virtual void stop(void);
virtual void pause(void) { assert(0); }
virtual void resume(void) { assert(0); }
LLUE *ll;
int
flow_;
int busy_;
int paused_;
Event intr;
double stime; // start time
double rtime; // remaining time
AppsTimer* Next;
};
This class implements a list of timers of type AppsTimer. It is used to know when the application has finished and free the resources assigned to that flow in all layers. We chose the implementation of these timers like a list because we needed an independent timer for each application; in other way we lose the information of one application when a packet of other application arrived.
The method AddANode(int flow) is used to add a timer to the list that is going to control a specific flow. The method DeleteANode (int flow) is used to remove a timer that belongs to a specific flow to the list. The methods start(int flow, double time) and stop (int flow) are used to control the flow; that is, when a packet of a flow arrives is called the method stop() that stops the timer of the class AppsTimer belonging to this flow; then is called the method start() which in turn starts the timer of the flow. In that way, we can estimate the time between the arrivals of two packets. If passes 1 second since the last packet of a flow arrived, the timer of that application expires, and it is considered that the application has finished. So the RRC begins a release produce.
The drawback of using a list of timers is that every time a packet arrives, the RRC layer has to perform a search in the list to find the timer that belongs to that application. It adds complexity and takes computational resources. Nevertheless, it is supposed that a terminal, in real life is not going to run more than three or four applications at the same time.
In this section we are going to explain the main methods that the class LLUE uses to perform the functions explained before.
void recv(Packet* p, Handler* h)
This method is the core of the class. It is the first place where data and signaling packets from both directions, UP and DOWN, arrived. Once here, it has to decide what to do with them. This decision is taken depending on the direction and the channel the packet is carried on. Due to its importance and complexity, this method is going to be deeply explained later.
void switch_on(void)
This method is invoked from the script to switch on the terminal. Although applications upon the UE start, it cannot support any until this method is called and the setup procedure, that it sparks off, will be completed. The method acts directly through the variable ue_state_ previously explained.
void configure_flow(int flow, const char*const& type ,
double rate)
This method is invoked from class MobileNode when we put in the script the following command and the first argument is a mobile node (UE or Node B):
$node_(0)
2222 ftp rate_ $opt(rate)
It is used to configure the applications that are going to run upon nodes. The first argument is the node in which the application with flow_id 2222 is running. This node can be a UE, not very often a Node B, or a wired node. If the source is a wired node this method is not invoked, instead, the flow information is kept in a list, this will be explained in class LLNodeb. The third argument is the type of application; this is going to determine some characteristics of the flow, such as if is going to used ACK mode or not, if requires fragmentation and the type of channel coding that is going to be applied. The possible types and their characteristics are:
|
Application
Type |
Channel
Coding |
ACK
Mode |
Frag.
Mode |
|
Audio |
TURBO |
Non-ACK
mode |
Fragmentation |
|
Video |
TURBO |
Non-ACK
mode |
Fragmentation |
|
Mail |
CONV_THIRD |
ACK
mode |
Fragmentation |
|
Fax |
CONV_HALF |
Non-ACK
mode |
Fragmentation |
|
Speech |
CONV_HALF |
Non-ACK
mode |
Fragmentation |
|
Ftp |
TURBO |
ACK
mode |
Fragmentation |
|
Http |
CONV_HALF |
ACK
mode |
Fragmentation |
Table 6.1. Application Types and Characteristics
And the fifth parameter is the rate at which the application is going to transmit packets to the RRC layer.
The method configure_flow() called the method insert_flow() to introduce the new application parameters in the RRC structures, and also called the method update_flow() to update the RLC structures with the new parameters.
int insert_flow(int flow, double rate, Chan_Coding coding)
This method is invoked from the method configure_flow() to insert the parameters related with a new flow, such as flow_id, rate and channel coding, in a free position of the apps[] array.
void update_flow(int acked, int fraged, int flowid)
This method is also invoked from the method configure_flow(). It calls the RLC method store_flow(), which inserts the parameters related with the given flow in the RLC layer structures.
int detect_flow(Packet* p)
This method is called when a packet from the application (direction DOWN) arrives. It checks if the packet arrived is part of an application that is currently running in the UE. It searches the flow_id in the apps[] array; there are four possibilities:
q If is the first packet of the flow, this method first adds a new timer in the list of timers for this flow; then fills the rest of the apps[] fields for that flow, such as the node destiny, the rate by invoking the method get_rate() and the spreading factor by calling the method get_sf(). Later, it begins the resource allocation procedure by invoking the method send_res_req(), which sends to Node B a resource request method. Finally, it stored the current packet in the queue q_[] puts the variable apps[].wait_ to 0, that means that when the next packets of this flows come they will be stored until the resources will be allocated.
q If the flow is waiting for resources, it stops and starts the timer related to that flow and stores the packet in the queue q_[].
q If the flow has already resources allocated, it stops and starts the timer related to that flow and calls the method send_msg() to send the packet to the destiny.
q If it is not able to find the flow in apps_[] is because previously it was impossible to allocate resources for that application and the flow is denied, so the packet is dropped.
void remove_flow(Packet* p)
This method removes the resources already allocated for a specific flow in the RLC layer by invoking the RLC method remove_flow(), in the IFQ by invoking the IFQ reg() method with 0 bytes, in the physical layer by invoking the method update_phy_bytes_slot() and in the RRC layer by removing resources in apps_[]. It is called when a failure has occurred and is impossible to serve the application. It is also called when an application has finished.
double get_rate(double rate, Chan_Coding code, int psize)
This method calculates the rate that is going to be sent upon the air interface from parameters such as the channel coding, the RLC fragment size, and the size of headers.
double get_bytes(int pos)
This method calculates the bytes per tti that the IFQ is going to let pass through from parameters such as the channel coding, the RLC fragment size, and the size of headers.
void update_phy_bytes_slot(void)
This method calculates total bytes per slot that the physical layer has to send over the air interface. It calculates these bytes by adding all the rates that the RRC layer has stored in apps_[], and then it passes this value to the physical layer through the physical variable bytes_slot.
void send_res_req(Packet* p, int i)
This method sends a resource request message of type LL_RES_REQ to the Node at which the UE is attached, asking for the necessary transmission rate for that flow.
void send_setup(void)
This method sends a setup request message of type LL_SETUP, beginning with it the cell search procedure. The physical layer will decided the best Node B based in power measurements. This method is invoked when switching on or when handover procedure. If handover procedure, the UE has to inform the new Node B about the applications already running in it; this is done by the physical header field flows_, which is matrix that carries information about the flows and if they work in ACK mode or they need fragmentation.
void send_paging_ok(Packet* p)
This method is the response of a paging message. It sends through the CCCH logical channel a paging response message, of type LL_PAGING and with the physical header paging_ok_ to 1, to the Node at which the UE is attached.
void send_msg(Packet* p)
This method sends through the DTCH logical channel a data packet to the node destiny.
void AppsHandler(int flow)
This method is invoked when a timer expires, that means that an application has finished because one second has passed after the last packet received by that application. It sends a release request message of type LL_RELEASE_REQ to inform the Node B and the node destiny that the application is ended.
void send_release_reply(Packet* p)
This method is invoked when a LL_RELEASE_REQ arrived from the Node B. That means that a current communication has finish and we have to free resources. It sends a LL_RELEASE_REPLY to ensure the Node B and the application source that we know that the application is finished.
Arrived at this point and after explaining the variables and methods of LLUE class, we find interesting to see in deepness the recv() method, due to, as we commented before is where all the decisions are taken. To show how the method works we are going to use pseudo-code.
void LLUE::recv(Packet* p,
Handler*) {
if the direction is UP
if is coming for channel CCCH
if LL_SETUP_REPLY
changes ue_state to 2
takes the Node B physical and IP directions
if connection refused
deletes resources in RRC, IFQ, RLC and PHY
if LL_RELEASE_REPLY
remove_flow(p)
if LL_RELEASE_REQ
removes resources from the RLC
send_release_reply(p)
if LL_RES_REPLY
if ok
allocates
resources for that flow in RRC, IFQ and PHY
sends the
packets stored in q_[]
if failure
removes
resources in RRC and RLC
drops packets
stored in q_[]
if channel DTCH
sends packet to the application through the
scheduler
if is a directive coming from PHY
if PT_RRC_PCH for
paging procedure
if paging for me
updates
resources in RLC and PHY
send_paging_ok()
if PT_RRC_HANDOVER for handover procedure
handover_ = 1
phy->handover_ =1
send_setup()
if PT_RRC_FAILURE
removes resources in RRC and RLC
drops packets stored in q_[]
if the direction is DOWN
if ue_state != 2
drops packet
if ue_state = 2
detect_flow(p)
}
The class LLNodeb has several variables and objects used to perform functions related to data and control management as the class LLUE had. To make possible the management and interchange of control information between the RLC and the other layer, it requires three variables that will point to objects of class PhyUmtsNodeb, class RlcUmtsNodeb and class BsFCQueue. These variables are configured by the method add-interface (explained in previous points) and are shown above:
PhyUmtsNodeb* phy_;
RlcUmtsNodeb* rlc_;
BsFCQueue* ifq_;
Although, the class RlcUmtsNodeb has some similarities with the class RlcUmts, they are not so similar as other classes explained before that implemented the same layer; this is because the former has to perform much more functions, and has to control more things than the latter. First of all, we have to differentiate between the variables and structures that belong to each object of the class RlcUmtsNodeb and the variables common to all of them.
The variables that share all the Node Bs are:
location registry_[MAX_NUM_NODES]. This matrix is a static variable; that means that is shared by all class RlcUmtsNodeb objects in the system. This variable has the situation of each UE in every moment. When a Node B wants to know in which is attached a specific UE, just has to look for the IP direction of the UE in the matrix. This is used like a kind of HLR but belonging to all the Node Bs. This matrix is the type location:
struct
location {
nsaddr_t ue_;
nsaddr_t nodeb_;
};
MAX_NUM_NODES is a constant that represent the maximum number of mobile nodes that a simulation can support.
SLList *list. This variable is also static and it is a list where the information related to the applications originated in fixed nodes is kept. This list is a pointer to an object of the class SLList implemented in wired-flows.{c, h}. The nodes of the list are introduced in class Node when we put in the script the following command (that was explained in class LLUE) and the first argument is a fixed node:
$W(0)
3333 http rate_ $opt(rate)
The nodes of this list have some variables that stored information such as the IP direction of the source transformed to integer, the flow_id or the application type (see table 6).
loc ue_info_[MAX_NUM_UE]. This variable stores all the information related to the UE attach in our cell that is needed to perform all the functions in the RRC layer. Its dimension is MAX_NUM_UE, that is the maximum number of mobile nodes that one Node B can support. It is the type loc, shown and explained after:
struct
loc {
nsaddr_t ipaddr_;
int phyaddr_;
int dltempsf_;
int dlsf_;
int dlfreq_;
double dltotalr_;
};
ipaddr_ and phyaddr_ are the IP and physical addresses of a UE. These are the fields that differentiate one register to the others.
dlsf_ is the spreading factor given to a user for the downlink communication. That spreading factor includes all the applications ended in a determined UE. That is, while the RRC in UE nodes differentiated between flows, the RRC in Node B do not differentiate flows but UEs.
dltempsf_ is a temporary storage of the dlsf_ to ensure that, if there are some failure during the resource allocation, the last transmission rate supported is not lost.
dlfreq_ is the downlink frequency assigned to each user. This represents the OVSF tree in which has been allocated the DL spreading factor given to the user. Every packet for a specific user is going to be sent for its dlfreq_.
dltotalr_ is the total rate of a user in the downlink direction. Is the addition of every application rate that the Node B must send to the UE.
tree *dl_sftree[MAX_NUM_FREQ]. This variable is an array of trees of MAX_NUM_FREQ (the number of frequencies in an UMTS system) that represents the OVSF trees. To perform the spreading function, the Node B must assign to each EU a spreading factor depending on its requirements, and it has also to manage which spreading factor is giving to prevent interferences and errors between users. So, each Node B has to have several OVSF trees, in concrete 12, because this is the number of frequencies in the DL. To implement the trees it was necessary to make the class tree, shown in files tree.{cc, h}.
As we can see, this is a special tree because each leaf has four pointers to other trees: two of them (*left and *rignt) point to the next level leafs that born in the current leaf, *next points to the leaf in the same level and left side of the current leaf and the last one *up, that points to the father (previous level). Moreover, each tree has a variable root that is a pointer to a node object. The structure node has four fields: source_ is the IP address of the UE transformed to integer at which has been assigned the spreading factor sf_; sf_ is the spreading factor that corresponds to the level of the tree (1 to 8), that is, the level 1 corresponds to the SF = 4 and the level 8 corresponds to SF = 512. deep_ is the level is which is allocated the tree and k_ is the position of the tree in that level. The structure node is shown next:
struct
node {
int source_;
int sf_;
int k_;
int deep_;
};
The class tree has several methods, for example, built_tree(int maxsf) creates a tree structure of (log2 maxsf –1) number of levels. insert_sf(int source, int sf) assigns the sf to the user with address source. If it has been impossible to allocate it in that tree because that sf or some of the below leafs have been already allocated, returns 0. remove_sf(int source, int sf) removes the source from the tree structure.
nsaddr_t ip_nodeb_ is the Node B IP address.
FlowList *flows_. This variable is a list of type FlowList (see files ll-flows.{cc, h}) where the information about the data flows coming with direction DOWN is stored; this data only can come either from a fixed node or from a mobile node allocated in other Node B. The class FlowList is keeps a structure with the source address of that application, the destiny address, the flow_id and a variable wait_ that if is to 1 means that the Node B is waiting for the destiny paging response to send the packets, meanwhile the packets are stored in the queue fl.
linked_list *refused. This list maintains information about the flows arrived with direction DOWN that have been refused due to some failure. If the flow is in this list, the packets arrived of this flow are dropped in the Node B. It is an object of the class linked-list implemented in files linked-list.{cc, h}.
PacketQueue *fl_. This queue is a temporary storage of the flows arrived with direction DOWN. When the first packet of a flow with direction DOWN arrives to the Node B, it has to begin a paging procedure to ensure that the UE destiny is in its cell and updates all the needed resources. Until the paging response arrives, the Node B stores all the flow packets arrived in this queue.
The Node B also has two timers:
TNodebList *removeflow_. This variable is a list implemented in class TNodebList that has a variable timer of class AppsNodebTimer, these classes are implemented in file ll-nodeb.h. It is used to detect when an application that is generated in a fixed node has finished. Is the same as appstimer_, the timer explained in class LLUE. When one timer expires the method void RemoveFlowHandler(nsaddr_t src, nsaddr_t dest, int size, int flow) is called with the parameters related to the flow that controlled that timer. This method performs the same functions as the method AppsHandler(int flow), explained in class LLUE.
ReleaseUETimer remove_. This timer is used to remove, after a time, the physical resources assigned to a UE that was in our cell but has made handover to other. That is, after 40 frames that the Node B received a message notifying the handover, this timer expires and calls the method RemoveUEHandler(Event* e) which updates the physical resources assigned to that UE.
Following we are going to explain the main procedures of this class.
void recv(Packet* p, Handler* h)
This method is the core of the class. It is the first place where data and signaling packets from both directions, UP and DOWN, arrived. Once here, it has to decide what to do with them. This decision is taken depending on the direction and the channel the packet is carried on. Due to its importance and complexity, this method is going to be deeply explained later.
register_ue(Packet* p)
This method is called when arriving a setup request message. If there are space for another terminal, it introduces the new UE information such as the physical and IP addresses and, if the UE is in handover procedure, the flows already running in that UE by invoking the procedure update_flow(). If we cannot allocate another terminal a failure message is sent to the UE.
void update_flow(int
acked, int fraged, int flowid, nsaddr_t src, int handover)
This method calls the RLC method store_flow(), explained before, to
register the new flow in this layer.
void update_db(Packet* p)
This method is called when arriving a setup request message and we have enough resources to attach the mobile to our cell. It updates the static matrix registry_ with the new UE address and our address if is the first time that the mobile is registered in the system or changes the address of the old Node B to our address, if the mobile is in handover.
void
send_setup_reply(Packet* p)
This method sends a setup reply message as a response to a setup request message, which is sent when a mobile wants to attach itself to our cell. When receiving this message the UE knows that its request has been accepted.
int cal_sf(Packet* p) and int cal_sf(double rate)
These methods calculate the spreading factor needing to support a given transmission rate.
int alloc_res(Packet* p,
int sf)
This method is called by the method free_res(). It looks for a tree where the spreading factor required was free. If it finds a tree, it updates the variables ue_info_[].dlsf_ and ue_info_[r].dlfreq_ with the new values and returns 1. If there are not enough resources in the Node B to support the new application, returns 0.
int free_res(Packet* p)
This method calculates the spreading factor necessary to support the new application and if is possible allocates resources. If the source of the communication is a mobile, the method takes the transmission rate from the RRC header, but if is a fixed node, the transmission rate is not in the RRC header, so we have to take it from list. First of all, if the node destiny has already assigned a spreading factor, it calculates the maximum physical rate that can be supported with that spreading factor, by invoking the method get_rate(), and checks if is possible supporting the new application with the same spreading factor. If we do not need other SF, the new transmission rate supported is recalculated and the method returns 1. If a higher spreading factor is required, it calculates the new spreading factor with get_sf() and tries to allocate the new spreading factor in any of its trees by calling alloc_res(). If is possible to allocate the new application, the method updates all the ue_info_ fields related to SF, transmission rate, etc. and returns 1, but if is impossible, returns 0 and leaves all the variables like they were.
int get_bytes(int
pos)
This method calculates the maximum bytes per TTI that the IFQ should let pass through it, from the parameters ue_info_[].dltotalr_ and ue_info_[].dlsf_.
int phy_rate(double
rate, int code, int psize)
This method calculates the user transmission rate that is going to be sent over the air interface as we explained previously.
void
update_phy_bytes_slot(int pos)
This method obtains the maximum physical rate that we can send over the air interface. It calculates the bytes per slot related to a specific user that the physical layer should sent in each slot. In previous points it was explained how this operation is carried out.
int remove_res(int pos)
This method removes the resources allocated in the OVSF trees for a specific user. It is invoked when a failure has happened or when a mobile that was receiving flows in our cell makes handover. It is also called when a new application arrives for a user with resources already assigned or an application finishes.
void send_to_ra(Packet*
p)
The method send_to_ra() is used to send to the routing agent all this packets which destiny is a fixed node or a mobile in other cell.
void update_nodeb(Packet*
p, nsaddr_t addr)
When a UE makes handover to our cell, this method sends a LL_HANDOVER to the Node B at which the UE used to be attached. With this message the old Node B is informed about the departure of the UE, so it can free the resources given to it.
void
remove_flow_ul(Packet* p, int pos)
This method is called when an application finished. It remove the uplink resources from the RLC layer by invoking the RLC method remove_flow().
void
remove_flow_dl(Packet* p)
This method is called when an application finished and the Node B receives a release reply message. It removes the downlink resources related to that application from the OVSF trees by invoking the method remove_res(), from the RLC layer by invoking the RLC method remove_flow, form the IFQ by invoking the IFQ method reg() with zero bytes and from the physical layer by invoking the method update_phy_bytes_slot(). It also checks if the application came from a fixed node or from a mobile node in other cell. In this case, it removes the flow from the list flows_ and if there was any packet stored in the queue fl_ is dropped.
void pag_proc(Packet* p,
int pos)
When the Node B receives a resource request message to initiate a communication with one of the UEs attached to it, it sends a paging procedure to inform the node destiny about the new application. If the communication arrives from a fixed node, the Node B when receiving the first packet of the flow, sends the paging message.
void
send_res_reply(Packet* p, int state)
When receiving the response of a paging from a node in our cell, we send the response of the resource request message received previously from the mobile node origin. This response can be affirmative, that means that the origin can begin to send messages, or negative, the communication is not going to take place because there are not enough resources or the destiny is unreachable.
void
send_release_reply(Packet* p)
This method sends a release reply message to the mobile node origin. It can be invoked for two reasons: when arriving a release reply from the mobile node destiny or when arriving a release request from the mobile node origin and the destiny is a fixed node.
void send_msg(Packet* p,
int loc)
It is used to send data packets coming, with direction DOWN, to the IFQ.
Arrived at this point and after being explained the main variables and methods of LLNodeb class, we find interesting to see in deepness the recv() method, due to, as we commented before is where all the decisions are taken. To show how the method works we are going to use pseudo-code as we did in class LLUE.
void LLNodeb::recv(Packet*
p, Handler*) {
if the direction is UP
if is coming for channel CCCH
if LL_SETUP_REQ
if there are enough resources to register the UE
registers the
UE in our internal structures
update the
static UE-NodeB structure
sends the Setup
Reply message
else
sends a FAILURE
message
if LL_RELEASE_REQ
if the node destiny is a mobile node
if the destiny
is in my cell
sends the Release Request message to the destiny
else
sends the message to the routing agent
if the node destiny is a fixed node
removes the UL
resources assigned to that flow
sends the
Release Reply message to the origin
if LL_RELEASE_REPLY
removes the DL resources assigned to that flow
if the origin is a UE in my cell
removes the UL
resources assigned to that flow
sends the
Release Reply message to the origin
if the origin is a UE in other cell
sends the
message to the routing agent
if the origin is a fixed node
deletes this
flow timer from the list of timers
if
LL_RES_REQ
if the destiny is in my cell
if there are
enough DL resources
allocates resources
begins the paging procedure
else
sends a failure in the resource reply message
else
if is a mobile
node
sends the request to the routing agent
if is a fixed
node
allocates resources to the flow
sends the resource reply message
if LL_PAGING,
response to a paging
allocates resources to the flow in the IFQ, RLC and PHY
if the origin is a UE
sends the
resource reply message
if there are packets stored in fl_
wait_ = 0
sends the
packets stored
if channel DTCH
sends packet UP through the scheduler
if is a directive coming from PHY
if PT_RRC_FAILURE for
paging procedure
adds the flow to the refused_ list
removes the resources assigned to that flow
if there are any packet stored
drops packets
if the direction is DOWN
if is a directive coming from other Node B
if LL_RES_REPLY
if failure
removes
resources assigned to that flow
sends a failure
in the resource reply message
else
allocates resources
sends the resource reply message
if LL_RELEASE_REQ
sends the message to the destiny of the communication
if LL_RELEASE_REPLY
removes the UL resources assigned to that flow
sends the message to the origin of the communication
if LL_HANDOVER from
other Node B
if the mobile had DL resources allocated
removes
resources form IFQ
starts ReleaseUETimer to remove physical
resources
if there is any
packet stored for that UE
sends them to the routing agent
removes
RRC resources from RRC
if no resources in
other Node B to support the flow
removes resources of that flow in RRC, IFQ, RLC and PHY
if LL_RES_REQ
if there are enough
DL resources
registries the flow in the list flows_
initiates paging procedure
else
sends a failure in the resource reply message
if arrives a data packet
if the flow has not
been rejected
if is the first packet of the flow
if there are
enough DL resources
registers the flow in the list flows_
stored the packet in fl_
initiates the paging procedure
if the source is a fixed node
adds a
new timer in the list removeflow_
removeflow_->start()
else
adds the
flow to the list refused_
if the source is a mobile
sends a
failure message
else
if the origin
is a fixed node
removeflow_->start()
if is waiting
for the paging response
stores the packet in fl_
resources
already allocated
sends the message
else
drops the packet
}
Routing in ns is based on forwarding of packets by classifiers in each node. As can be found in ns manual, the function of a node when it receives a packet is to examine the packet’s fields, usually its destination address, and on occasion, its source address. It should then map the values to an outgoing interface object that is the next downstream recipient of this packet. Each classifier contains a table of simulation objects indexed by slot number. The job of a classifier is to determine the slot number associated with a received packet and forward that packet to the object referenced by that particular slot. Multiple classifier objects, each looking at a specific portion of the packet forward the packet through the node.
A very important issue related to routing is the addressing format. There are two possible addressing formats in ns: flat addressing and hierarchical addressing. In flat addressing, the address of each node is a number and every node has to know how lo route every other node in the topology, thus resulting a routing table size of n2. For hierarchical routing, a topology is broken down into several layers of hierarchy (an example address with three levels is 1.0.10), thus downsizing the routing table to about log n. Normally, the addressing space is divided into three levels, which correspond to domains, clusters and nodes in each cluster.
The advantages of the hierarchical addressing are the reduction of the size of the routing tables, and that the nodes only need to know about its neighbors in its own cluster, about the all clusters in its domain and about all the domains (not as in flat topology, where each node has to store the next_hop info of all other nodes in the topology). However, hierarchical addressing involves the substitution of the classifier in the nodes by three new classifiers.
The wireless topologies that ns had implemented did not consider the possibility of changes of location. This is why for wireless simulations the routing was implemented as hierarchical because it presents a lot of advantages. Then, in the script of a wireless simulation you have to put this:
$ns
set-address-format hierarchical
which involves ns to set the addressing format as hierarchical and enable the routing module RtModule/Hier (implemented in ns-hiernode.tcl, ns-address.tcl, ns-route.tcl and route.cc).
For implementing a cellular network the mobility of mobiles is essential, thus this static routing based on addressing cannot be done. Also, for having a hierarchical routing, one has to attach the UE to its NodeB at the beginning of the simulation by means of:
set node_ [ $ns_ node
[lindex $temp 1]
$node_ base-station
[AddrParams addr2id [$BS(0) node-addr]]
In our system, this must be done automatically. For all these reasons, we opted for flat addressing, but we had to implement a new routing module that could be able to handle mobility of UE through all the topology.
Other limitation in ns is that only fixed nodes and base stations are recognized in terms of routing, being the routing agent installed in the base stations in charge of forwarding the packets to the correspondent mobile within its cell. Then, the working of routing for wireless scenarios was based in the hierarchy structure of the addressing. In our case, we need to have control also of mobile for routing them independently of the address, thus, we have to include the UE in the addressing space; therefore now the routing tables of every node include all the UEs (this is done in ns-lib.tcl).
After the considerations already seen, the decided to implement a new routing module that comprises all the new elements and modifications we need. This routing module is called “RtModule/Umts” and is implemented in the files ns-lib.tcl, ns-node.tcl, rtmodule.{cc.h} and ns-rtmodule.tcl. It implemented the class UmtsRoutingModule (which introduces the class UmtsClassifier, explained later), as following:
class UmtsRoutingModule :
public RoutingModule {
public:
UmtsRoutingModule() : RoutingModule() {}
virtual const char* module_name() const { return
"Umts"; }
virtual int command(int argc, const char*const* argv);
protected:
UmtsClassifier *classifier_;
};
With this routing module, a user who wants to make a UMTS simulation, apart of configuring the node as was shown, has to configure the addressing format and enable our routing module as following (through the script):
$ns_ set-umts-routing
Basically, this method enables the new routing module by calling this instruction, among others:
RtModule/Umts instproc
register { node } {
$self next $node
$self instvar classifier_
set classifier_ [new Classifier/Umts]
$classifier_ set mask_ [AddrParams NodeMask 1]
$classifier_ set shift_ [AddrParams NodeShift 1]
$classifier_ index $node
$classifier_ set id_ [$node id]
$classifier_ set type_ [$node nodetype]
$node install-entry $self $classifier_
}
This procedure configures the internal structure of a node, giving it a classifier_ instance of the class UmtsClassifier (implemented in file classifier-umts.{cc.h}) as the only classifier (apart of demux_, see Figure 2).
This module handles only Unicast Routing, implemented in files ns-route.tcl, route-proto.tcl, McastProto.tcl, and rtProtoDV.{cc, h}. Now we are going to describe the general working of the routing.
For our system, we use a kind of Dynamic Routing, so any user that wants to make a UMTS simulation has to put the line shown bellow. It is necessary to understand its working before explaining the modification we had to do. As an example, the following command illustrate what a user has to put for use the Dynamic protocol:
$ns_
rtproto DV
DV Routing. DV routing is the implementation of Distributed Bellman-Ford (or Distance Vector) routing in ns. The implementation sends periodic route updates every advertInterval. This variable is a class variable in the class Agent/rtProto/DV. Its default value is 2 seconds.
In addition to periodic updates, each agent also sends triggered updates; it does this whenever the forwarding tables in the node change. This occurs either due to changes in the topology, or because an agent at the node received a route update, and recomputed and installed new routes. Each agent employs the split horizon with poisoned reverse mechanisms to advertise its routes to adjacent peers. “Split horizon” is the mechanism by which an agent will not advertise the route to a destination out of the interface that it is using to reach that destination. In a “Split horizon with poisoned reverse” mechanism, the agent will advertise that route out of that interface with a metric of infinity.
Each DV agent uses a default preference_ of 120. The value is determined by the class variable of the same name. Each agent uses the class variable INFINITY (set at 32) to determine the validity of a route.
It is necessary to understand the objects that the dynamic routing involves and their principles of working and main procedures (the following is taken from ns manual). In a dynamic routing strategy, nodes send and receive messages, and compute the routes in the topology based on the messages exchanged. The procedure init-all{} takes a list of nodes as the argument; the default is the list of nodes in the topology (where now all the UE are already included thanks to include them into the addressing space). At each of the nodes in the argument, the procedure starts the class rtObject and a class Agent/rtProto/DV agents. It then determines the DV peers for each of the newly created DV agents, and creates the relevant rtPeer objects.
The constructor for the DV agent initializes a number of instance variables; each agent stores an array, indexed by the destination node handle, of the preference and metric, the interface (or link) to the next hop, and the remote peer incident on the interface, for the best route to each destination computed by the agent. The agent creates these instance variables, and then schedules sending its first update within the first 0.5 seconds of simulation start.
Each agent stores the list of its peers indexed by the handle of the peer node. Each peer is a separate peer structure that holds the address of the peer agent, the metric and preference of the route to each destination advertised by that peer.
The routine send-periodic-update{} invokes send-updates{} to send the actual updates. It then reschedules sending the next periodic update after advertInterval jittered slightly to avoid possible synchronization effects.
send-updates{} will send updates to a select set of peers. If any of the routes at that node have changed, or for periodic updates, the procedure will send updates to all peers. Otherwise, if some incident links have just recovered, the procedure will send updates to the adjacent peers on those incident links only.
send-updates{} uses the procedure send-to-peer{} to send the actual updates. This procedure packages the update, taking the split-horizon and poison reverse mechanisms into account. It invokes the instproc-like, send-update{} (Note the singular case) to send the actual update. The actual route update is stored in the class variable msg_ indexed by a non-decreasing integer as index. The instproc-like only sends the index to msg_ to the remote peer. This eliminates the need to convert from OTcl strings to alternate formats and back.
When a peer receives a route update (recv-update{} of the class Agent/rtProto/DV) it first checks to determine if the update differs from the previous ones. The agent will compute new routes if the update contains new information by calling compute-routes{} of the protocol, and, if there have been changes, it calls compute-routes{} of the rtObject for making the modifications in the routing table.
Now we are going to explain how the UEs are registered in the whole network in our system and the changes we had to do for supporting that. When the simulation starts, the routing tables of the classifiers in all the nodes (fixed, NodeB and UEs) are set to NULL. As it happens in the actual cellular systems, a UE can switch on and off, so we added a function to do it in mobilenode.cc, so when a user put the line bellow, the procedure MobileNode::command{} calls switch_on() which has been already explained:
$node_(0) ON
This procedure, involves the needing of registering in the NodeB, but also in the classifiers of the whole network. This is done thanks to a LL_SETUP_REPLY packet that is sent by the class LLNodeb already seen. Then, the classifier on top of the NodeB (class UmtsClassifier, thanks to the UmtsRoutingModule) receives the packet and does the following inside the method find():
tcl.evalf("%s
update-changed %d", index_->name(),
Address::instance().get_nodeaddr(ih->daddr()));
tcl.evalf("%s add-route
%d %s", index_->name(),
Address::instance().get_nodeaddr(ih->daddr()),
default_target_->name());
We had to do this instead of invocating intf-changed{} (to populate the classifier and to update the routing table at the same time) because we are trying to say the other nodes that the path to find the UE is through the NodeB (specifically its entry_). The first instruction is in charge of updating the routing table for this node and spreading the change through the network. For doing that, it calls this new method we implemented in ns-mobilenode.tcl:
Node/MobileNode instproc
update-changed { destid } {
$self instvar rtObject_
set entrypoint [$self entry]
if [info exists rtObject_] { ;#
i.e. detailed dynamic routing
puts "Actualizamos distancias....."
$rtObject_ update-changed $destid $entrypoint
}
}
As can be seen, it makes a variable entrypoint and set it to the entry of the node. Then, it is passed in our method update-changed{} of the rtObject (in charge of controlling the routing table in the node). It does these operations:
rtObject instproc update-changed { destid entry } {
$self instvar ns_ node_ rtProtos_ nextHop_
nextHopPeer_ rtpref_ metric_
set
changes 0
foreach
dst [$ns_ all-nodes-list] {
if {$destid
== [$dst id]} {
foreach p
[array names rtProtos_] {
if
($rtProtos_($p) dynamic? == 1) {
$rtProtos_($p)
set nextHop_($dst) $entry
$rtProtos_($p)
set metric_($dst) 0
$rtProtos_($p)
set update_($dst) [clock clicks]
}
}
set
nextHop_($dst) $entry
set
metric_($dst) 0
incr
changes
foreach
proto [array names rtProtos_] {
$rtProtos_($proto)
send-updates $changes
}
}
}
}
Basically, what it does is to update the routing table in the class rtObject (nextHop_($dst) with the entrypoint of the node, and set the cost for the UE to 0 in metric_($dst)) and update the routing table in the dynamic routing protocol (nextHop_, metric_). We also had to consider including the variable update_ in the class Agent/rtProto/DV, and this procedure set it with the actual value of the internal clock (we will se later its function). After all these, the procedure calls send-updates{} to spread the change, as already explained.
After that, the procedure add-route{} of the class Node (ns-node.tcl) is in charge of populating the classifier of the NodeB for the UE that is being registered (the slot for that UE will point to the default_target_ object in the classifier, which is the routing agent NOAH).
We had to modify the way the class Agent/rtProto/DV sends the updates by means of the method send-updates{}. Apart of sending the metrics, we send other message with the array update_, which contains the value of the clock in the last time that each UE changed its location (or was switched on) in the topology. With this array, any node (fix nodes and NodeB) has in any time the times they think that every nodes changed their location for the last time.
Finally, as was commented previously, the neighbors of the NodeB the UE has registered in receive the updating in the method recv-update{}. We changed this method as well for taking into account the contain of the array update_ received in the peer:
:
if
{[$dest nodetype] == 1} {
set c [set chan($dest)]
if {$c != ""} {
if {($update_($dest) != ""
&& $c > $update_($dest)) ||
$update_($dest)
== ""} {
set changed_($dest) 1
set update_($dest) [set c]
incr metricsChanged
break
}
}
}
:
Basically, this piece of code check if the destination of the change is a UE, take the value of the array update_ for it and compare with the value of its own update_ for that destination. In case that the received update_ is higher that its one, that means that the neighbor who sent the change has got a new location for that UE, so the receptor of the change updates its update_. It also set the new array changed_ to 1 for that destination (for indicating that there is a change for that UE and being used in compute-routes{}). Then, the class Agent/rtProto/DV will call the method compute-routes{} for re-calculate its routing table with some modifications as follows:
:
if { $ppf < $pf || $pmt
< $mt || $changed_($dst) == 1} {
set pf $ppf
set mt $pmt
unset nh ;# because we
must compute *new* next hops
if {$changed_($dst) == 1} {
set nh($ifs_($nbr)) $peers_($nbr)
if {$nextHop_($dst) != $ifs_($nbr)} {
set nh($ifs_($nbr)) $peers_($nbr)
set rtpref_($dst) $pf
set metric_($dst) $mt
set nextHop_($dst) ""
set nextHopPeer_($dst) ""
lappend nextHop_($dst) $ifs_($nbr)
lappend nextHopPeer_($dst) $nh($ifs_($nbr))
incr rtsChanged_
return
}
}
}
:
The modifications in this method involves that if there is a change (changed_ set to 1) for a UE, the class takes the modifications in the routing table as the Dynamic Routing Protocol indicates without checking if the previous route was better. Therefore, it is a forced changed, as we desired.
After that, the process concludes with the invocation of compute-routes{} of the rtObject for updating the routing table for the node that has received the change and then, spreads the change. Of course, this method had to suffer some modifications and these are the main:
:
if
{[$dst nodetype] == 1 && [$node_ nodetype] == 2} {
set nh
$pnh
set pf
$ppf
set mt
$pmt
set rv
$p
if {[$p dynamic?] == 1 && [$p set
changed_($dst)] == 1 &&
[$node_ nodetype] == 2 && [set pnh] != [set
nextHop_($dst)]} {
$p set changed_($dst) 0
set nextHop_($dst) $nh
set rtpref_($dst) $pf
set metric_($dst) $mt
set rtVia_($dst) $rv
$node_ delete-routes [$dst id]
$nextHop_($dst) $nullAgent_
set nextHop_($dst) $nh
$node_ add-routes [$dst id]
$nextHop_($dst)
$p send-updates 1
return
} else {
set prov 1
}
}
:
The case that it wants to handle in different way as it used to do is when the NodeB receives a change of a UE. This happens when the dynamic protocol indicates that the nextHop_ for that destination is different as the previous one.
With all these modifications, we have got the whole implementation of routing. Then, when a UE switches on or moves from one cell to another, the modifications are spread all over the network, updating the classifiers and internal routing tables.
For executing examples after compiling these extensions, one has to operate as usual. For instance, for executing a script called example.tcl one has to do:
$ ns example.tcl
The results of this simulation can be obtained in four ways:
· Standard output: While doing the simulation, text messages can be obtained through the starndard output by setting the appropriate variables in the general configuration of the script. Each of the layers implemented has got a variable called verbose_, which shows messages related to that layer if is set to 1.
· Procedures in the script: This is a basic mechanism for obtaining results directly from objects in the simulation. An example can be found in the examples provided (directory examples) by means of the method calcBW{}. The result can be visualized thanks to xgraph.
· Trace file: Every ns simulation gives results through an output output.tr file. The format can be found in ns manual. However, in our extensions it is very recommendable to study other parameters not considered in the original format, so the files cmu-trace.{cc.h} have been modified to support that. Basically, we add the procedure format_phy(p, why, offset) to trace packets in physical/transport channels. The fields added are:
"-Phs
%d -Phd %d -Phc %d -Php %e ",
ph->sa_,
ph->da_,
ch->channel_t(),
ph->rx_power_);
To have results from this output files, any tool can be used, although we recommend using awk. For instance, for having delays in some cases, use (for example2.tcl) and then you can see it with xgraph:
$ awk -f delay.awk -v FID=2222
example2.tr > delay
· Nam file: This is a complementary file for visualisating the whole network and the packet interchanging. We made some modifications for make it to support mixed scenarios (wired and wireless), but in some cases and depending on the computer, it cannot be working. In our implementation, ACKs are visualized in the same color as the flow.
In the directory examples of the package, some examples can be analysed.
· Example1.tcl (and ex1-traffic, ex1-scen): it consists of two UEs and a NodeB. Both of the UEs try to switch on at the beginning of the simulation successfully. Then, one application in ACK mode runs between the mobile node through the NodeB. The bytes received in the receptor agent can be seen in the resulting file bw1.
· Example2.tcl (and ex2-traffic, ex2-scen): it consists of two UEs and two NodeBs. Both of the UEs try to switch on at the beginning of the simulation successfully in the same NodeB. Then, three applications are setup: one between one node and a fixed node, another from a fixed node to a UE and the last one between the two mobile nodes. The peculiariry of this scenario is that the second UE makes a handover procedure to attach to the second NodeB. The bytes received in the receptor agents can be seen in the resulting file bw1, bw2 and bw3.
· Example3.tcl (and ex3-traffic, ex3-scen): it consists of several UEs and three NodeBs. For a detailed implementation, refer directly to files. Several applications with different characteristics are setup between this scenario nodes. This example can take quite a long time. Please, be patient….