Other Network Topologies
************************

Network topology is the study of the arrangement of of the elements (in ns-3 represented by the classes Channel and Node) of a network.  Two fundamental types of physical topologies are the point-to-point and bus topologies.  We have already been exposed to the ns-3 channel specialization named CsmaChannel.  This is a simulation of a bus network.  We also provide a simulation of a point-to-point channel with associated net devices.  As described previously, the associated C++ classes specialize the ns-3 base classes NetDevice and Channel and are called PointToPointNetDevice and PointToPointChannel respectively.

We will use combinations of these bus and point-to-point topology elements to show how to create several commonly seen network topologies.

A Point-to-Point Network
========================

We're going to take what might be seen as a step backward and look at a simple point-to-point network.  We will be building the simplest network you can imagine.  A serial link (point to point) between two computers.  When you see this point-to-point network, you can think of an RS-422 (or RS-232 for you old-timers) cable.  This topology is shown below.

We have provided a file for you in the tutorial directory called tutorial-point-to-point.cc.  You should now be familiar enough with the system to pick out fairly easily what has been changed.  Let's focus on the following lines:

  Ptr<Node> n0 = CreateObject<InternetNode> ();
  Ptr<Node> n1 = CreateObject<InternetNode> ();

  Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
    n0, n1, DataRate (38400), MilliSeconds (20));

  PointToPointTopology::AddIpv4Addresses (link, n0, "10.1.1.1",
    n1, "10.1.1.2");

You can see that we created two InternetNode objects in the usual way. Then, instead of creating a CsmaChannel we create a PointToPointChannel.  This point-to-point channel, which we call link, connects node zero (n0) and node one (n1) over a simulated link that runs at 38400 bits per second and has a 20 millisecond simulated speed-of-light delay.  This call also creates appropriate net devices and attaches them to nodes zero and one.

We then add IP addresses to the net devices we just created using the topology helper AddIpv4Addresses.  Node zero gets the IP address 10.1.1.1 and node one gets the IP address 10.1.1.2 assigned.

The alert tutorial user may wonder what the network number or prefix is of those IP addresses.  The point-to-point topology assumes that you want a /30 subnet and assigns an appropriate net mask for you.  It then then asserts that the network numbers of the two net devices match.  So there is an implicit network mask created down in the topology code that looks like,

  Ipv4Mask netmask("255.255.255.252");

The rest of the code you should recognize and understand.  We are just going to echo one packet across the point-to-point link.  You should be now be able to build and run this example and to locate and interpret the ASCII trace file.  This is left as an exercise for you.

The file tutorial-point-to-point.cc is reproduced here for your convenience:

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "ns3/log.h"
#include "ns3/ptr.h"
#include "ns3/internet-node.h"
#include "ns3/point-to-point-channel.h"
#include "ns3/mac48-address.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/point-to-point-topology.h"
#include "ns3/udp-echo-client.h"
#include "ns3/udp-echo-server.h"
#include "ns3/simulator.h"
#include "ns3/nstime.h"
#include "ns3/ascii-trace.h"
#include "ns3/pcap-trace.h"
#include "ns3/global-route-manager.h"

NS_LOG_COMPONENT_DEFINE ("PointToPointSimulation");

using namespace ns3;

// Network topology
//
//                       point to point
//                      +--------------+
//                      |              |
//                     n0             n1
//
int
main (int argc, char *argv[])
{
  LogComponentEnable ("PointToPointSimulation", LOG_LEVEL_INFO);

  NS_LOG_INFO ("Point to Point Topology Simulation");

  Ptr<Node> n0 = CreateObject<InternetNode> ();
  Ptr<Node> n1 = CreateObject<InternetNode> ();

  Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
    n0, n1, DataRate (38400), MilliSeconds (20));

  PointToPointTopology::AddIpv4Addresses (link, n0, "10.1.1.1",
    n1, "10.1.1.2");

  uint16_t port = 7;

  Ptr<UdpEchoClient> client = CreateObject<UdpEchoClient> (n0, "10.1.1.2",
    port, 1, Seconds(1.), 1024);

  Ptr<UdpEchoServer> server = CreateObject<UdpEchoServer> (n1, port);

  server->Start(Seconds(1.));
  client->Start(Seconds(2.));

  server->Stop (Seconds(10.));
  client->Stop (Seconds(10.));

  AsciiTrace asciitrace ("tutorial.tr");
  asciitrace.TraceAllQueues ();
  asciitrace.TraceAllNetDeviceRx ();

  Simulator::Run ();
  Simulator::Destroy ();
}

A Star Network
==============

A point-to-point network is considered a special case of a star network.  As you might expect, the process of constructing a star network is an extension of the very simple process used for a point-to-point link.  We have provided a file for you in the tutorial directory called tutorial-star.cc that implements a simple star network as seen below.

In order to create a star network, we need to be able to instantiate some number (greater than one) of net devices on a node.  In the name of simplicity of use, the PointToPointTopology topology helper does not allow one to do this.  We provided a separate topology helper class, the PointToPointIpv4Topology helper class that provides the slightly finer granularity we need to accomplish a star network.  In order to use this new helper we have to load the definitions by including the appropriate file.

  #include "ns3/point-to-point-ipv4-topology.h"

The star that we're going to create has a node in the center (n0) with six nodes surrounding (n1 - n6).  You should be able to easily find and understand the code that creates these nodes.

  Ptr<Node> n0 = CreateObject<InternetNode> ();
  Ptr<Node> n1 = CreateObject<InternetNode> ();
  Ptr<Node> n2 = CreateObject<InternetNode> ();
  Ptr<Node> n3 = CreateObject<InternetNode> ();
  Ptr<Node> n4 = CreateObject<InternetNode> ();
  Ptr<Node> n5 = CreateObject<InternetNode> ();
  Ptr<Node> n6 = CreateObject<InternetNode> ();

Next, we get into the differences between the PointToPointTopology helper and the PointToPointIpv4Topology helper.  The PointToPointIpv4Topology helper looks and feels a little like the CsmaIpv4Topology helper.  Just like you created a CSMA channel previously, you need to create a point-to-point channel.  The following code creates a PointToPointChannel and calls it link01.  You can interpret this name as being the channel (or link) from node zero to node one.

  Ptr<PointToPointChannel> link01 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

You need to provide a data rate for the channel which we set at 38400 bits per second.  You must also provide a speed-of-light delay which we set at 20 milliseconds.

Just as you added a net device to the nodes in the CSMA tutorial section, you do the same here but with a point-to-point net device.  The following code illustrates how we do that:

  uint32_t nd01 = PointToPointIpv4Topology::AddNetDevice (n0,
    link01);

We call the PointToPointIpv4Topology helper and ask it to add a net device to node zero (n0) and connect it to the appropriate point-to-point link (link01) which you will recall is the serial link from node zero to node one.

If you look at the following code, you will see the same calls are repeated to create the remaining five point-to-point channels and connect them to net devices on node zero.

The next new code is found after the ``spokes'' of the star have been created. It looks like the following:

  uint32_t nd1 = PointToPointIpv4Topology::AddNetDevice (n1, link01);
  uint32_t nd2 = PointToPointIpv4Topology::AddNetDevice (n2, link02);
  uint32_t nd3 = PointToPointIpv4Topology::AddNetDevice (n3, link03);
  uint32_t nd4 = PointToPointIpv4Topology::AddNetDevice (n4, link04);
  uint32_t nd5 = PointToPointIpv4Topology::AddNetDevice (n5, link05);
  uint32_t nd6 = PointToPointIpv4Topology::AddNetDevice (n6, link06);

Here we are creating the net devices on the nodes surrounding the center node. In the first call, we are adding a net device on node one (n1) and connecting that net device to the channel named link01.  Remember that we created the channel link01 as the channel connecting node zero and node one.  We previously created a net device on node zero and attached that device to link01.  Here we are connecting the other side of that link to node one.  The return value from this call is the net device index of the created net device.

The next section of code adds addresses to the net devices we just created. The first call adds the IP address 10.1.1.1 to the net device going from node zero to node one.  Recall that we first created a node named n0 and a channel called link01.  We added a net device to n0 and remembered the net device index as the uint32_t nd01.  This meant the net device nd on node 0 that we connected to node 1. We call AddAddress to add an IP address (10.1.1.1) to the net device on node zero identified by the net device index nd01.  We provide a net mask suitable for a point to point network.  This is typically a /30 address but we don't force that in this API.

After setting up the address on node zero, we do the same for the node on the other end of the ``spoke'' --- in this case node one, with its single net device.  Note that the network number is the same on both sides of this network.

  PointToPointIpv4Topology::AddAddress (n0, nd01, "10.1.1.1",
    ``255.255.255.252'');

  PointToPointIpv4Topology::AddAddress (n1, nd1, "10.1.1.2",
    ``255.255.255.252'');

The following code repeats this pattern assining similar IP addresses to the remaining net devices.  Note that there are no Mac48Address address assignments --- they are not required.

The rest of the code you should recognize and understand.  We are just going to echo one packet across the point-to-point link.  You should be now be able to build and run this example and to locate and interpret the ASCII trace file.  This is left as an exercise for you.

The file tutorial-star.cc is reproduced here for your convenience:

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "ns3/log.h"
#include "ns3/ptr.h"
#include "ns3/internet-node.h"
#include "ns3/point-to-point-channel.h"
#include "ns3/mac48-address.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/point-to-point-ipv4-topology.h"
#include "ns3/udp-echo-client.h"
#include "ns3/udp-echo-server.h"
#include "ns3/simulator.h"
#include "ns3/nstime.h"
#include "ns3/ascii-trace.h"
#include "ns3/pcap-trace.h"
#include "ns3/global-route-manager.h"

NS_LOG_COMPONENT_DEFINE ("StarSimulation");

using namespace ns3;

// Network topology
//
//                  n3    n2
//                   |   /
//                    | /
//              n4 --- n0 --- n1
//                    /  |
//                   /    |
//                  n5    n6

int
main (int argc, char *argv[])
{
  LogComponentEnable ("StarSimulation", LOG_LEVEL_INFO);

  NS_LOG_INFO ("Star Topology Simulation");

  Ptr<Node> n0 = CreateObject<InternetNode> ();
  Ptr<Node> n1 = CreateObject<InternetNode> ();
  Ptr<Node> n2 = CreateObject<InternetNode> ();
  Ptr<Node> n3 = CreateObject<InternetNode> ();
  Ptr<Node> n4 = CreateObject<InternetNode> ();
  Ptr<Node> n5 = CreateObject<InternetNode> ();
  Ptr<Node> n6 = CreateObject<InternetNode> ();

  Ptr<PointToPointChannel> link01 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd01 = PointToPointIpv4Topology::AddNetDevice (n0,
    link01);

  Ptr<PointToPointChannel> link02 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd02 = PointToPointIpv4Topology::AddNetDevice (n0,
    link02);

  Ptr<PointToPointChannel> link03 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd03 = PointToPointIpv4Topology::AddNetDevice (n0,
    link03);

  Ptr<PointToPointChannel> link04 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd04 = PointToPointIpv4Topology::AddNetDevice (n0,
    link04);

  Ptr<PointToPointChannel> link05 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd05 = PointToPointIpv4Topology::AddNetDevice (n0,
    link05);

  Ptr<PointToPointChannel> link06 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd06 = PointToPointIpv4Topology::AddNetDevice (n0, link06);

  uint32_t nd1 = PointToPointIpv4Topology::AddNetDevice (n1, link01);
  uint32_t nd2 = PointToPointIpv4Topology::AddNetDevice (n2, link02);
  uint32_t nd3 = PointToPointIpv4Topology::AddNetDevice (n3, link03);
  uint32_t nd4 = PointToPointIpv4Topology::AddNetDevice (n4, link04);
  uint32_t nd5 = PointToPointIpv4Topology::AddNetDevice (n5, link05);
  uint32_t nd6 = PointToPointIpv4Topology::AddNetDevice (n6, link06);

  PointToPointIpv4Topology::AddAddress (n0, nd01, "10.1.1.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n1, nd1, "10.1.1.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd02, "10.1.2.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n2, nd2, "10.1.2.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd03, "10.1.3.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n3, nd3, "10.1.2.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd04, "10.1.4.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n4, nd4, "10.1.4.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd05, "10.1.5.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n5, nd5, "10.1.5.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd06, "10.1.6.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n6, nd6, "10.1.6.2",
    "255.255.255.252");

  uint16_t port = 7;

  Ptr<UdpEchoClient> client = CreateObject<UdpEchoClient> (n0, "10.1.1.2",
    port, 1, Seconds(1.), 1024);

  Ptr<UdpEchoServer> server = CreateObject<UdpEchoServer> (n1, port);

  server->Start(Seconds(1.));
  client->Start(Seconds(2.));

  server->Stop (Seconds(10.));
  client->Stop (Seconds(10.));

  AsciiTrace asciitrace ("tutorial.tr");
  asciitrace.TraceAllQueues ();
  asciitrace.TraceAllNetDeviceRx ();

  Simulator::Run ();
  Simulator::Destroy ();
}

Routing
-------

If you are really excited about this simulator you may have already tried to modify the scripts outside the tutorial.  I know that one of the first things that would have occurred to me when I saw the star network would have been to start trying to add applications to echo packets from nodes other than zero. If you tried, for example, to start the echo client on node one instead of node zero, you would have found an empty trace file.  The reason for this is that you have now created an internetwork.  This means you will need to enable internetwork routing.

We have provided a file for you in the tutorial directory called tutorial-star-routing.cc to show you how this is done.  This extremely tricky and difficult change is shown below:

 GlobalRouteManager::PopulateRoutingTables ();

This one-line addition, located just before the simulation runs, tells the ns-3 global route manager to walk the topology you created and build internetwork routing tables for all of the nodes in the simulation. We changed the client application so that it runs on node four:

  Ptr<UdpEchoClient> client = CreateObject<UdpEchoClient> (n4, "10.1.1.2",
    port, 1, Seconds(1.), 1024);

Now if you build and run tutorial-star-routing.cc you can examine the tutorial.tr file and see that your UDP echo packets are now correctly routed through the topology.

A Dumbbell Network
==================

One of the most interesting simple topologies (from a phenomenological point of view) is commonly called a dumbbell network.  The name derives from a superficial similarity in form to a piece of exercise equipment.

The dumbbell model is typically composed of two bus or star network elements connected via a point-to-point link.  The point-to-point link is usually configured with a lower bandwidth than the bus elements to provide a choke point.

The following is a representation of the topology.

We have provided a file that constructs this dumbbell network and creates enough data flowing across the choke point that some packets will be dropped. The file is called tutorial-linear-dumbbell.cc and is located in the tutorial directory.  We have already covered all of the code used to create this network, so we will just quickly go over the main sections of the script.

The first section creates a CSMA lan that will become the left side of the dumbbell network.  This code should be very familiar since we used the same process to create our first example.

//
// Create the lan on the left side of the dumbbell.
//
  Ptr<Node> n0 = CreateObject<InternetNode> ();
  Ptr<Node> n1 = CreateObject<InternetNode> ();
  Ptr<Node> n2 = CreateObject<InternetNode> ();
  Ptr<Node> n3 = CreateObject<InternetNode> ();

  Ptr<CsmaChannel> lan1 =
    CsmaTopology::CreateCsmaChannel (DataRate (10000000), MilliSeconds (2));

  uint32_t nd0 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n0, lan1,
    "08:00:2e:00:00:00");

  uint32_t nd1 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n1, lan1,
    "08:00:2e:00:00:01");

  uint32_t nd2 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n2, lan1,
    "08:00:2e:00:00:02");

  uint32_t nd3 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n3, lan1,
    "08:00:2e:00:00:03");

  CsmaIpv4Topology::AddIpv4Address (n0, nd0, "10.1.1.1", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n1, nd1, "10.1.1.2", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n2, nd2, "10.1.1.3", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n3, nd3, "10.1.1.4", "255.255.255.0");

The code to generate the CSMA lan on the right side is similar; only the names have been changed.

//
// Create the lan on the right side of the dumbbell.
//
  Ptr<Node> n4 = CreateObject<InternetNode> ();
  Ptr<Node> n5 = CreateObject<InternetNode> ();
  Ptr<Node> n6 = CreateObject<InternetNode> ();
  Ptr<Node> n7 = CreateObject<InternetNode> ();

  Ptr<CsmaChannel> lan2 =
    CsmaTopology::CreateCsmaChannel (DataRate (10000000), MilliSeconds (2));

  uint32_t nd4 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n4, lan2,
    "08:00:2e:00:00:04");

  uint32_t nd5 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n5, lan2,
    "08:00:2e:00:00:05");

  uint32_t nd6 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n6, lan2,
    "08:00:2e:00:00:06");

  uint32_t nd7 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n7, lan2,
    "08:00:2e:00:00:07");

  CsmaIpv4Topology::AddIpv4Address (n4, nd4, "10.1.2.1", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n5, nd5, "10.1.2.2", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n6, nd6, "10.1.2.3", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n7, nd7, "10.1.2.4", "255.255.255.0");

Next, we create a point to point link to connect the two lans.  We connect the point-to-point channel between nodes three (on the left lan) and four (on the right lan).  You should recoginze this as substantially similar to the link setup from the point-to-point example.

//
// Create the point-to-point link to connect the two lans.
//
  Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
    n3, n4, DataRate (38400), MilliSeconds (20));

  PointToPointTopology::AddIpv4Addresses (link, n3, "10.1.3.1",
    n4, "10.1.3.2");

Then we configure data flows.  We create four echo clients that send UDP packets from the left side lan to servers created on the right side lan. Notice that we send 100 packets with an inter-packet gap of ten milliseconds instead of the single packet we have previously used.  This data rate is sufficient to saturate the point-to-point link and will cause packets to be dropped when the queue on the link net devices overflows (the default maximum queue depth is 100 packets).  Note that we stagger the start of the echo clients to slowly bring up the data rates.

//
// Create data flows across the link:
//   n0 ==> n4 ==> n0
//   n1 ==> n5 ==> n1
//   n2 ==> n6 ==> n2
//   n3 ==> n7 ==> n3
//
  uint16_t port = 7;

  Ptr<UdpEchoClient> client0 = CreateObject<UdpEchoClient> (n0, "10.1.2.1",
    port, 100, Seconds(.01), 1024);
  Ptr<UdpEchoClient> client1 = CreateObject<UdpEchoClient> (n1, "10.1.2.2",
    port, 100, Seconds(.01), 1024);
  Ptr<UdpEchoClient> client2 = CreateObject<UdpEchoClient> (n2, "10.1.2.3",
    port, 100, Seconds(.01), 1024);
  Ptr<UdpEchoClient> client3 = CreateObject<UdpEchoClient> (n3, "10.1.2.4",
    port, 100, Seconds(.01), 1024);

  Ptr<UdpEchoServer> server4 = CreateObject<UdpEchoServer> (n4, port);
  Ptr<UdpEchoServer> server5 = CreateObject<UdpEchoServer> (n5, port);
  Ptr<UdpEchoServer> server6 = CreateObject<UdpEchoServer> (n6, port);
  Ptr<UdpEchoServer> server7 = CreateObject<UdpEchoServer> (n7, port);

  server4->Start(Seconds(1.));
  server5->Start(Seconds(1.));
  server6->Start(Seconds(1.));
  server7->Start(Seconds(1.));

  client0->Start(Seconds(2.));
  client1->Start(Seconds(2.1));
  client2->Start(Seconds(2.2));
  client3->Start(Seconds(2.3));

  server4->Stop (Seconds(10.));
  server5->Stop (Seconds(10.));
  server6->Stop (Seconds(10.));
  server7->Stop (Seconds(10.));

  client0->Stop (Seconds(10.));
  client1->Stop (Seconds(10.));
  client2->Stop (Seconds(10.));
  client3->Stop (Seconds(10.));

The remainder of the file should be quite familiar to you.  Go ahead and run tutorial-linear-dumbbell.  Now take a look at the trace (tutorial.tr) file.  You will now see trace lines that begin with d.  Alternatively you can search for the string ``queue-drop'' which is the expansion of the drop code ('d').

Interpretation of a dropped packet is straightforward.  We have expanded the first queue-drop trace for you below.  See the section on ASCII tracing for details.

  00 d
  01 2.40938
  02 nodeid=3
  03 device=1
  04 queue-drop
  05 pkt-uid=124
  06 LLCSNAP(type 0x800)
  07   IPV4(
  08     tos 0x0
  09     ttl 63
  10     id 20
  11     offset 0
  12     flags [none]
  13     length: 1052) 10.1.1.3 > 10.1.2.3
  14     UDP(length: 1032)
  15       49153 > 7
  16       DATA (length 1024)

We leave it as an exercise to examine the trace files in more detail.

Nonlinear Thinking
******************

One thing that all of our examples so far have in common is that they are composed of a linear collection of calls into the ns-3 system.  The programmers among the readers may have wondered why there is not as much as a for-loop in all of the examples.  The answer is that we wanted to introduce you to ns-3 scripting with a minimum of conceptual overhead.  We're going to remedy that situation shortly.

We have written a number of ns-3 scripts in C++.  Although we have been perfectly linear in our script implementations, just like any other C++ program, an ns-3 script can use any features of the language you desire.  If you will look back at the tutorial-linear-dumbbell.cc example, you may notice that the code to create the left and right sides of the dumbbell is operationally identical --- only the names change.  An obvious improvement of this program would be to use subroutines to create the sides. Since we are working with C++, we should probably do this in an object-oriented way.  Since object-oriented design is somewhat of a black art to some people, we'll take some time here and outline a simple methodology you can follow.

Object Design 101 --- Class Ipv4BusNetwork
==========================================

If you are a master of object oriented design, feel free to skip or skim this section, in which we derive a simplistic but fully operational bus network class.

So you want to create a BusNetwork class.  Often the biggest hurdle in a design is figuring out how to get started.  One of the simplest and most straightforward ways to do an object decomposition of a problem is to simply write down a description of the problem and take a look at the words you used.  Let's take some time and do that, first at a very high level.

     A bus network is an implementation of a particular network topology that
     contains some number of nodes.  Each of these nodes is attached to a single
     multi-drop channel.  The network itself has some attributes independent of
     the topology such as a network mask, network number (prefix) and base IP
     address.

The first thing to do is to focus on the nouns and adjectives.  These will give you a starting point for required classes and member variables.

Immediately we can notice that at the highest level we are talking about the noun network.  This probably won't surprise you.  We also have an adjective that modifies the noun --- bus.  This should lead us to our first class defintion.  Usually class names are constructed in the same way as an English language sentence would be spoken.  For example, one would speak of a bus network in conversation, so we would normally create a class BusNetwork to represent it.

One thing to note is that we have used two words in our description quite naturally: is and has.  When you see these words should should immediately think of the object-oriented concepts of ISA (inheritance) and HASA (containment) respectively.  We wrote that a bus network is an implementation of a particular network topology.  Perhaps you will agree that there is a natural base class called Network that has the attributes discussed above.  The fact that a BusNetwork ISA kind of Network suggests inheritance.  Let's capture that thought right away remembering that we're focused on IP version four here:

  class Ipv4Network
  {
  public:
    Ipv4Address m_network;
    Ipv4Mask m_mask;
    Ipv4Address m_baseAddress;
  };

  class Ipv4BusNetwork : public Ipv4Network
  {
  };

Let's take a look at the HASA relationships of the bus network.  Clearly it will have a reference to the underlying channel that implements the actual communications medium.  We use smart pointers for those references, so one member variable is obvious:

  Ptr<CsmaChannel> m_channel;

A bus network will also need to contain references to all of the nodes we eventually want to create.  If you are working in C++ and see the words contain or container, you should immediately think of the Standard Template Library or STL.  A quick search of the available containers there will probably lead you to consider the vector class.  A vector is a container that looks like an array.  This is just what we need here.  Again, we want to use smart pointers to reference our nodes, so the declaration of the vector would look like,

  std::vector<Ptr<Node> > m_nodes;

It will save you headaches in the future if you notice that the space between the two right brackets is required to differentiate this situation from a right-shift operator.  So we have a pretty good start already after just a little work.  Now we need to turn our attention to actions.  Let's write another little description of the things you consider doing to a Bus network.

     We need to be able to create a bus network.  We need to be able to delete a
     bus network.  We need to be able to get a handle to a node in order to add
     applications.  We need to be able to set the network, mask and base address
     somehow, specify how many nodes to create and provide the underlying channel
     its required bandwidth and delay parameters.

We now look at the verbs in that sentence.  These will give a good starting point for the methods of the classes.  For example, the verbs create and delete should suggest constructor and destructor.  The verb get leads us to providing a method called GetNode.  We have to provide a number of parameters so we can either provide setters or we can simply pass them in as parameters to our constructors.  Since this is a simple example, we won't bother to implement getters and setters (methods to get and set member variables to enhance data hiding).  Let's use this guidance to finish up our class declarations:

  class Ipv4Network
  {
  public:
    Ipv4Network (Ipv4Address network, Ipv4Mask mask, Ipv4Address address);
    virtual ~Ipv4Network ();

    Ipv4Address m_network;
    Ipv4Mask m_mask;
    Ipv4Address m_baseAddress;
  };

  class Ipv4BusNetwork : public Ipv4Network
  {
  public:
    Ipv4BusNetwork (
      Ipv4Address   network,
      Ipv4Mask      mask,
      Ipv4Address   startAddress,
      DataRate      bps,
      Time          delay,
      uint32_t      n);

    virtual ~Ipv4BusNetwork ();

    Ptr<Node> GetNode (uint32_t n);

  private:
    std::vector<Ptr<Node> > m_nodes;
    Ptr<CsmaChannel> m_channel;
  };

That's it.  We have actually already walked through almost all of the code required to construct a bus network in our tutorial-csma-echo.cc example, so let's just jump forward and take a look at an implementation of this thing.  We provide an implementation for you in the files ipv4-bus-network.h and ipv4-bus-network.cc located in the tutorial directory.  We also provide an example that uses the new class in the file tutorial-bus-network.cc.

The interesting method from our current perspective is the Ipv4BusNetwork constructor, shown below:

  Ipv4BusNetwork::Ipv4BusNetwork (
    Ipv4Address   network,
    Ipv4Mask      mask,
    Ipv4Address   baseAddress,
    DataRate      bps,
    Time          delay,
    uint32_t      n)
  :
    Ipv4Network (network, mask, baseAddress)
  {
    Ipv4AddressGenerator::SeedNetwork (mask, network);
    Ipv4AddressGenerator::SeedAddress (mask, baseAddress);

    m_channel = CsmaTopology::CreateCsmaChannel (bps, delay);

    for (uint32_t i = 0; i < n; ++i)
      {
        Ptr<Node> node = CreateObject<InternetNode> ();
        uint32_t nd = CsmaIpv4Topology::AddIpv4CsmaNetDevice (node, m_channel,
          Mac48Address::Allocate ());
        Ipv4Address address = Ipv4AddressGenerator::AllocateAddress (mask,
          network);
        CsmaIpv4Topology::AddIpv4Address (node, nd, address, mask);
        m_nodes.push_back (node);
      }
  }

Notice that we do the simple and straightforward thing and pass all of our parameters to the constructor.  For those unfamiliar with C++, the line after the colon and before the opening brace (shown below),

  :
    Ipv4Network (network, mask, baseAddress)
  {

Passes the appropriate parameters to the constructor of the base class Ipv4Network.  There are two new calls that we haven't seen immediately after this initialization.  They are:

    Ipv4AddressGenerator::SeedNetwork (mask, network);
    Ipv4AddressGenerator::SeedAddress (mask, baseAddress);

We provide an IP address generator class to allow us to programatically allocate IP addresses.  The first call to SeedNetwork gives the address generator a starting network number to use when generating addresses. The second call to SeedAddress gives the address generator a starting IP address to use.  There is a starting network and starting address for each of the 32 possible network masks.  Later in the for loop, you will see a call to AllocateAddress in which the IP address for each node created in the loop is actually generated.

The only unfamiliar call in the reset of the constructor will be:

  m_nodes.push_back (node);

This is the STL code to add the newly created node to the vector of nodes attached to the bus.

For your convenience, we reproduce the entire bus network implementation below:

  /* -*-  Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
  /*
   * Copyright (c) 2007 University of Washington
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 as
   * published by the Free Software Foundation;
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */

  #include "ns3/mac48-address.h"
  #include "ns3/csma-net-device.h"
  #include "ns3/csma-topology.h"
  #include "ns3/csma-ipv4-topology.h"

  #include "ipv4-bus-network.h"
  #include "ipv4-address-generator.h"

  namespace ns3 {

  Ipv4Network::Ipv4Network (
    Ipv4Address   network,
    Ipv4Mask      mask,
    Ipv4Address   address)
  :
    m_network (network), m_mask (mask), m_baseAddress (address)
  {
  }

  Ipv4Network::~Ipv4Network ()
  {
  }

  Ipv4BusNetwork::Ipv4BusNetwork (
    Ipv4Address   network,
    Ipv4Mask      mask,
    Ipv4Address   baseAddress,
    DataRate      bps,
    Time          delay,
    uint32_t      n)
  :
    Ipv4Network (network, mask, baseAddress)
  {
    Ipv4AddressGenerator::SeedNetwork (mask, network);
    Ipv4AddressGenerator::SeedAddress (mask, baseAddress);

    m_channel = CsmaTopology::CreateCsmaChannel (bps, delay);

    for (uint32_t i = 0; i < n; ++i)
      {
        Ptr<Node> node = CreateObject<InternetNode> ();
        uint32_t nd = CsmaIpv4Topology::AddIpv4CsmaNetDevice (node, m_channel,
          Mac48Address::Allocate ());
        Ipv4Address address = Ipv4AddressGenerator::AllocateAddress (mask,
          network);
        CsmaIpv4Topology::AddIpv4Address (node, nd, address, mask);
        m_nodes.push_back (node);
      }
  }

  Ipv4BusNetwork::~Ipv4BusNetwork ()
  {
  }

    Ptr<Node>
  Ipv4BusNetwork::GetNode (uint32_t n)
  {
    return m_nodes[n];
  }

  }; // namespace ns3

Using Ipv4BusNetwork
====================

If all you ever want to do with a bus network can be captured in a topology with four nodes on the bus, the preceeding section may seem like a colossal waste of time.  This is probably not the case, though.  Now that we have a relatively abstract bus class, we can create bus networks with 4, 40 or 4000 nodes with no additional effort.

A use of the bus network class is shown in the file bus-netowrk.cc located in the tutorial directory.  The interesting code is,

  Ipv4BusNetwork bus ("10.1.0.0", "255.255.0.0", "0.0.0.3",
    DataRate(10000000), MilliSeconds(20), 10);

Here we create a bus network with the network number ``10.1.0.0'' and the network mask ``255.255.0.0'' that completes the IP network definition.  You can consider these together as ``10.1.0.0/16'' if you prefer.  The next parameter tells the bus to start numbering IP addresses of contained nodes at ``10.1.0.3'' (remember the network number will be combined).  We provided a data rate of 10 megabits per second and a latency of 20 milliseconds. Finally, we ask the Ipv4BusNetwork object to create ten nodes in the network.

If you are feeling brave, go ahead and change the number of nodes to be 100, 1000, 10,000 or more to generate larger and larger networks.  Before you go too far, remember that a trace file will be generated when you run your resulting program and ee asked the trace facility to trace all net device receive events.  This will include the reception of the broadcast ARP request by all of the nodes in the simulation, so this can add up quickly.

Summary
*******

This concludes the first part of the tutorial.  We have focused on using the ns-3 system to construct various network topologies and to simulate sendng data across the networks; and we've shown you how to use the trace facility to get access to simulation results.

We now encourage you to play with the system a little.  Experiment with what we have provided.  Build a hierarchical network simulation.  Perhaps exercise your object design skills and create a new Ipv4DumbbellNetwork class to create dumbbell networks using the Ipv4BusNetwork class we just created. Hint:  An Ipv4DumbbellNetwork has two Ipv4BusNetwork objects; a left side and a right side.

In the next part of the tutorial we are going to drop down a level and begin examining the lower levels of the system in more detail.  We are going to explain how to change the behavior of the system and eventually how to write new models and applications.  This is a good time to make sure that you thorougly understand what we've gone over so far.

Object Model
************

There are two distinctly different meanings associated with the term Object Model.  The first speaks to the implementation of an object system --- a system view; and the second speaks to the application programming interface (classes or objects) one uses to access some service or system --- an application view.

As an example of the system view sense of the term, the C++ language has an associated object model that describes how objects are laid out in memory, how virtual functions work, how inheritance is implemented, constructor and destructor execution ordering, template instantiation, etc.

In the case of the application view, the Document Object Model is a good example.  In the words of W3C, the Document Object Model (DOM) is an application programming interface (API) for HTML and XML documents. It defines the logical structure of documents and the way a document is accessed and manipulated.

The Component Object Model (COM) from Microsoft actually spans both meanings of the term and extends further into policy statements.  From a system perspective, COM specifies an interface definition language, the layout of objects virtual function tables, the formats of Globally Unique Identifiers and also specifies lifetime management mechanisms for objects via reference counting.  From the point of view of the API, COM specifies a number of Interfaces as well as functions such as CoCreateInstance and various threading models.  The COM specification extends to policy by disallowing implementation inheritance.

The ns-3 object model takes the C++ language (system level) object model as its basis, and extends that model by providing an API for software componentry.  You may find terms like Component, Interface and QueryInterface in the following discussion, or used informally in other discussions about ns-3.  It is important to understand from the outset that this is the ns-3 object model, and not any other object model. Richard Feynman (an American physicist) once described the behavior of matter and light on a very small scale in the following way,

     ``They do not behave like waves, they do not behave like particles, they do not behave like clouds, or billiard balls, or weights on springs, or like anything that you have ever seen.''

Just as students of quantum mechanics must rid themselves of preconceptions regarding the behavior of matter at small scales, you should rid yourself of any preconceptions you may have about components, interfaces and APIs for software componentry before continuing.  To paraphrase Feynman, ns-3 components do not behave like COM Components, or Java Beans, or CORBA objects, or clouds or weights on springs, or like anything that you have ever seen --- they are ns-3 components.

The C++ Object Model is the Root of all Things
==============================================

Ns-3 is primarily a C++ system.  The system is written in C++ and one can use standard C++ mechanisms for creating and using ns-3 objects.  We do not change this at all, nor do we make any pronouncements about the superiority of one mechanism or another.  What we will do is provide convenience functions that we think will make creating and managing simulation objects easier.

Previously, you have seen objects created using the template function CreateObject as in the following example:

  Ptr<Node> n0 = CreateObject<InternetNode> ();

This line of code, while it may be unfamiliar to some, is pure C++.  If you were to look in the header file ptr.h, you would find the following definition of the CreateObject template.

  template <typename T>
  Ptr<T> CreateObject (void)
  {
    Ptr<T> p = Ptr<T> (new T (), false);
    p->SetTypeId (T::GetTypeId ());
    return p;
  }

As you can see, this template creates objects of type T using the operator new.  Its a little harder to find the corresponding delete --- it's in the file object.cc inside the method Object::MaybeDelete, but when that Ptr which you see above goes out of scope it will call Unref and ultimately the C++ delete operator will be called.

The ns-3 system uses the C++ new and delete operators, so there is really no reason that you as a user of the ns-3 system are forbidden from using these or any other C++ mechanism.  If you so desire, you can take on the responsibility for managing object lifetime (i.e., do not use the Ptr smart pointer), work directly with the new and delete operators and call methods like any C++ object as in the following example:

  MyClass *obj = new MyClass ();
  obj->Method();
  delete obj;

You, as a competent model author, are encouraged to use whatever methods you think are appropriate in your private code.  Remember, however, that the public ns-3 APIs do use smart pointers to pass objects around in an effort to reduce the burden of object lifetime management.  If you do intend to export an API publicly, you should use the same object lifetime management approaches as those found in the ns-3 public API if only for consistency.

These APIs are there for convenience and consistency, but do not change the fact that in ns-3 all of the objects are really just C++ objects, ultimately created using the C++ new operator with C++ constructor semantics and are ultimately deleted using the C++ delete operator, following C++ destructor semantics.  Although it may sometimes appear so, there is really no system- level magic going on in ns-3.  Ns-3 components and interfaces are C++ objects just like any other object and our object model is simply a collection of APIs built on the normal C++ object model.

Interface
=========

There are many different ideas floating around of what exactly the term interface means.  Originally an interface just meant a communication boundary between two entities.  As the concepts of object oriented programming (OOP) were surfacing in the 1980s, the term interface was applied to the collection of access methods for the modular entities that were being defined.

Two distinct approaches developed regarding specifying access mechanisms for objects.  The OOP purists were very concerned about object reuse and were led to Abstract Data Types (ADT).  These were eventually implemented in the case of C++, as pure virtual methods in Abstract Base Classes (ABC).  Another group of folks was more interested in simply specifying object access methods in one place and using inheritance as the primary reuse mechanism.

Bjarne Stroustroup, the creator of C++, embraced both approaches.  He makes the following interesting observation:

     ``Many classes [...] are useful both as themselves and also as bases for derived classes. [...] Some classes, such as class Shape, represent abstract concepts for which objects cannot exist.''

Ns-3 does not pick and enforce a particular approach.  In ns-3 an interface is determined completely by a class declaration just as any C++ object interface is declared.  If you think of an object as an abstract concept that should be implemented by derived classes, by all means, use the Abstract Base Class approach to interface declaration.  If you think that an object should be completely concrete and you foresee no need to ever modify its behavior, feel free to avoid declaring any methods virtual. If you think that an object could be useful as a base class, feel free to declare its methods virtual.  If you like to use the PIMPL idiom, again, feel free.  If you want to use any combination of these techniques, feel free. We make no restrictions.

When we speak of an ns-3 interface, we do not worry about interface definition languages, or pure virtual classes, or registries  we just think about C++ object declarations and their associated methods.  We tend to think of interfaces to objects as simply a private or public API.  When we instantiate an ns-3 interface, it is the C++ object model that dictates how that object is brought into existence.  When a method is called on an ns-3 Interface, it is the C++ object model that dictates how that method is dispatched.

We do, however, provide a base class that endows vanilla C++ objects with capabilities that can be seen as conceptually similar to those provided by Microsoft Component Model Interfaces.

The Ns-3 Object and GetObject
=============================

One thing that Microsoft arguably got right in the Component Object Model was the idea of Interface aggregation and discovery via QueryInterface.  We have embraced these ideas in ns-3.  This was done primarily to address a common problem in large software systems.  A good example of this problem happens in the ns-3 Node class.

If one were to take the standard OOP view of specializing a Node into an internet host, for example, one would typically inherit from the Node base class and include functionality to implement such things as internet routing and a TCP/IP protocol stack.  Other types of Nodes might inherit from the node class and specialize in different ways, or further specialize the internet host class, treating it as a base class.  This can result in a complicated inheritance tree in which some specializations are simply not available to other branches of the tree which can make reuse difficult or impossible.  This is known as the weak base class problem and creates pressure to drive functionality up the inheritance tree into the base classes.  This, in turn, results in base class bloat and the resulting swiss army knife base classes which end up trying to do everything in one place.

Even if one successfully avoided these swiss army knife base classes, one would also want to be able to treat new specializations of Node generically in the system.  This means one would pass references to the base class (Node) across public APIs.  This introduces upcasts prior to passing across public APIs and corresponding downcasts on the other side in order to gain access to required specialized functions.  As the inheritance tree becomes more complicated, this approach can cause another related problem known as the fragile base class problem.  This happens when changes to the base class cause unexpected problems in the various and sundry subclasses.

These effects seem always to result in a positive feedback loop driving everything into the base class and destroying much of the encapsulation which is a hallmark of the object oriented approach.

Interface Composition
---------------------

There is a completely different way to address the Node specialization problem.  Instead of approaching the situation using inheritance, one can look at the problem as one of composition.  We can look at the Node class as a container of sorts that holds other objects.  In this case, the objects would be instances of the classes implementing the internetwork routing code, or the TCP/IP protocol stack described above.  This approach preserves the encapsulation and solves the weak base class, base class bloat and fragile base class problems; but the question of method dispatch immediately comes to mind.

In many systems, delegation is used.  The base class, Node, in this approach would provide methods that simply forward to the objects implementing the desired functionality.  This situation clearly does not address the base class bloat problem since dispatch methods must be added to the base class.  The situation is mitigated somewhat by pushing the implementation of the dispatch methods to contained objects, but the fundamental problems are still present.  What is really needed is a way to compose objects but at the same time keep the interfaces to those objects separated.

Composition, usually called aggregation, along with runtime Interface discovery is the solution that Microsoft originally championed and that ns-3 has adopted --- albeit with many simplifications and a few name changes.

Objects and Interfaces
----------------------

Now that we have mentioned Microsoft COM and are almost obligated to mention the terms Interface and QueryInterface.  For those familiar with COM, loosely speaking, QueryInterface is to COM as GetObject is to ns-3. The analogy, while good conceptually, is superficial from an implementation point of view.

Addressing our current example of a Node, generically speaking, each node needs to aggregate an object that will implement internetwork routing and TCP/IP.  The system will need to provide a mechanism for locating the aggregated objects and allow a client to discover them.

These aggregated objects have interfaces in the C++ sense of collections of method signatures.  In ns-3, when objects are capable of participating in this aggregation process, they are called ns-3 Objects.  Objects receive the functionality required for this participation by inheriting from the ns-3 base class Object.

Note well that when we write the word Object (note the uppercase 'O' in the spelling and the change of font) we are referring to a kind of C++ object that has inherited the capability of participating in an aggregation.  The ns-3-specific word Object can have a significantly different meaning than that of a vanilla C++ object outside the aforementioned inheritance tree, and the difference is only readily apparent via context. In this tutorial we will always write the ns-3-specific kind of Object in a fixed font; and will write the vanilla C++ term object in normal font.  In conversation, you will need to be careful to understand which term is meant:  object or Object.

Once an object has inherited from class Object it has the ability to host an aggregation.  This means that it has the ability to add other Objects to its aggregation via the method AggregateObject.  It also means that it can provide a service to discover other objects in its aggregation via the method GetObject.

Technically, the class named Object is simply a base class that you will inherit from if you want your Objects to support aggregation and discovery.  Many systems have a base class that implements common functionality and these base classes are typically called somthing like Object.  The ns-3 version of this base class relates primarily to Object aggregation and discovery, although it does also provide methods to help with intrusive reference counting and tracing as well.

When a C++ object inherits from the ns-3 Object base class, it is conceptually promoted to an ns-3 Object irrespective of how the object was declared (e.g., as an abstract base class, concrete class, with virtual methods, etc.). In ns-3, you should associate inheritance from the class named Object with promotion of an object to the status of some locatable Object rather than with the form of the class declaration.

For those of you unfamiliar with Microsoft COM, CORBA or ORBit, this might sound obvious.  For those of with such a background, the point we are making is that there is no such thing in ns-3 as a separate Interface declaration, no such thing as an Interface Definiition Language, no such thing as a UUID or GUID, etc.  In ns-3 we just work with C++ objects that may be given some very useful abilities by inheriting from the ns-3 base class Object.  Ns-3 Objects are not required to inherit from classes composed of pure virtual methods in order to define an Interface.  It's all really just ``plain old C++.''

To summarize, when you instantiate an object that inherits from the Object class, you will have a C++ object that has four important properties:

  * The Object has a C++ interface defined by the collection of method signatures in its inheritance tree;

  * The Object has some way to identify its underlying class uniquely;

  * The Object is a kind of container that has the ability to aggregate other Objects using the method AggregateObject;

  * The Object exports a method called GetObject that allows for discovery of other aggregated Objects.

It is crucially important to understand what we have described here (especially for those coming from other systems that provide similar functionality).  A given C++ class has an object access interface that is essentially the collection of method signatures specified in its inheritance tree.  This is a C++ object model thing.  Ns-3 provides a base class from which the class in question can inherit and be promoted to the status of Object.  Once a class becomes an Object it has inherited the ability to aggregate and search for other Objects that are added to its aggregation.

That last detail is important.  In ns-3 Objects are both containers and specifications for a object method access.  We have previously mentioned that the Node class acts as a container.  In fact, the Node class inherits from Object and is itself an ns-3 Object.  So, when the Node object is created it is really an aggregation of one Object and you can call AggregateObject or GetObject on the resulting Node object.  Along with being an aggregation, the Node class also describes a public interface.  THis public interface (API) is declared just as any C++ object is declared, via its class methods as specified in the inheritance tree.  For those steeped in COM or CORBA, this is where the concept of Interface works in ns-3. Remember that it is generally true that Objects are both aggregations and APIs.

Aggregations
------------

The figure below shows how an Object could be illustrated in detail. The line with the circle at the top of the diagram represents the appearance of the Object API to the external world.  This circle and line are together called a lollipop because of its superficial similarity to a kind of childs candy.

You could declare this API and associated Object quite simply using a non-virtual class as follows,

  class A : public Object {
  public:
    static ns3::TypeId GetTypeId (void)
    {
      static ns3::TypeId tid = ns3::TypeId ("A")
        .SetParent (Object::GetTypeId ())
        .AddConstructor<A> ();
      return tid;
    }

    A ()
    {
    }

    void MethodA (void);
  };

The methods that are then available via the API labeled A in the figure above are the methods inherited from the Object base class (GetObject, Ref, and Unref) and those from class A (MethodA).

Note that you must declare a TypeId in your Object class, and it must be declared static to make it class-wide in scope.  This TypeId is a unifying element in the ns-3 object model and uniquely identifies Objects at run-time as being instantiated from a particular class.  We'll have much more to say about TypiId shortly.

You can think of the arc and arrow device coming off each side of the illustrated Objects as part of a connector.  These connectors allow GetObject to search aggregations for an instance of a class type. The figure below shows an aggregation of three Objects: A, B and C. The class declarations for classes B and C are substantially similar to that of class A.

You can visualize these Objects as being snapped together like Lego building blocks if you like.  When Objects are aggregated, a GetObject search path is formed through the connectors.  In order to create this aggregation you will first need to create the Objects. These are just normal, everyday C++ objects that we can create using the CreateObject template function and manage using smart pointers.  The following code should be obvious to you by now:

  Ptr<A> a = CreateObject<A> ();
  Ptr<B> b = CreateObject<B> ();
  Ptr<C> c = CreateObject<C> ();

When you create an aggregation, you pick one of the Objects of the aggregation to think of as the container.  In this case well pick Object A.  In order to aggregate an Object, you simply call the method AggregateObject that your class has inherited from class Object. The following code will aggregate Object B and Object C onto the Object (and container/aggregation) A.

  a->AggregateObject (b);
  a->AggregateObject (c);

Thats all there is to it.  Now that you have those connectors snapped together, you can ask each of the Objects in the aggregation for any of the other Objects in the aggregation.  Lets look at a simple example:

  Ptr<B> newB = a->GetObject<B> ();

Now, the explanation of what this snippet does is not as simple as writing it. The left hand side of this assignment declares a smart pointer to the class B to help with memory management of the returned Object pointer. You should be very familiar with smart pointers at this stage of the tutorial.

The right hand side illustrates how GetObject is acutally used. The method GetObject is templated.  The assocated template parameter (between the brackets) specifies the class that is being requested. This is important.  Since it is the class type that specifies the search criteron, there can be only one instance of a particular class present in an aggregation.  Looking back a little, although the parameter to AggregateObject appears to be a vanilla C++ object (b or c above), it actually represents (is an instance of) a class that has an associated TypeId and inherits from Object.  When you call GetObject you specify the search criterion (using the template parameter) as a class name.  This referenced class must also have an associated TypeId and must also have inherited from Object.

This may be summarized by saying that AggregateObject takes an instance of an object of a particular class that inherits from Object. GetObject looks for a class of a particular type (that again inherits from Object) and possibly returns an aggregated object instance of that type.

Now that you have those conceptual connectors snapped together, you can ask each of the Objects in the aggregation for any of the Objects in the aggregation.  For example we could walk the Objects asking each for the next in the aggregation.  First we would ask the Object pointed to by the smart pointer a to look for the Object class B:

  Ptr<B> newB = a->GetObject<B> ();

Next, we can ask the Object pointed to by the smart pointer newB to look for the Object representing class C:

  Ptr<C> newC = newB->GetObject<C> ();

Then, we can ask the Object pointed to by the smart pointer newC to look for the Object representing class A and complete our circuit of the aggregation:

  Ptr<A> newA = newC->GetObject<A> ();

GetObject has some important properties that we need to go over. Technically, GetObject is a symmetric, reflexive and transitive operation with respect to the set of aggregated Objects.

Symmetry
........

The symmetric nature of GetObject guarantees that if one performs a GetObject on a given Object for the class of that same Object, that GetObject must succeed.  In other words, the fact that you accessed the aggregation via an instance of an Object A in the aggregation implies the reachability of that Object in the aggregation.  This is usually written (by Microsoft) as,

must succeed (A >> A)

We can illustrate this property with the code snippet,

  Ptr<A> symmetricA = a->GetObject<A> ();
  NS_ASSERT (symmetricA);

Here we take as given an interface (smart) pointer --- named a --- on which we perform a GetObject looking for the class that represents that same Object.  This call must always succeed and a smart pointer to the aggregated instance of that class is returned.

Reflexivity
...........

Calls to GetObject must also be reflexive.  This means that if you successfully GetObject for Object B from Object A, then you must always be able to GetObject for A from B.  This is usually written as,

must succeed (A >> B, then B >> A)

This property can be illustrated with the code snippet,

  Ptr<B> b = a->GetObject<B> ();
  Ptr<A> reflexiveA = b->GetObject<A> ();
  NS_ASSERT (reflexiveA);

If the first GetObject on Object A looking for Object B succeeds, then a GetObject on Object B looking Object A must succeed.

Transitivity
............

GetObject must also be transitive.  This means that if one can find Object B from Object A, and Object C from Object B, then one must also be able to find Object C from Object A.  This is usually written as,

must succeed (A >> B, and B >> C, then A >> C)

This property can be illustrated with the code snippet,

  Ptr<B> b = a->GetObject<B> ();
  Ptr<C> c = b->GetObject<C> ();
  Ptr<C> transitiveC = a->GetObject<C> ();
  NS_ASSERT (transitiveC);

If you can get to Object B from Object A, and you can get to Object C from Object B, then a GetObject on Object A looking for Object C must also succeed.

Creating the TypeId
-------------------

The final piece of this puzzle is the TypeId.  Recall that the declaration our eample object above included the following code

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

This is the bit of code that ties this all together.  For those unfamiliar with the idioms involved, this declaration can be rather dense.  First, let's examine the function declaration itself.  The following code,

  static ns3::TypeId GetTypeId (void) ...

declares a function that will be associated with all of the instances of the given class.  This is a function, not a method, in that it can be accessed without a this pointer; but it is associated with the class in a namespace sense.  The use of this kind of declaration allows one to write,

  return A::GetTypeId (void);

if the TypeId is needed for our class A.  More generically the class name can be substituted in a template, as is done deep in the ns-3 object system.

From this perspective, if you leave out the middle of the function definition, the boundaries should make sense to you.

  static ns3::TypeId GetTypeId (void)
  {
    return tid;
  }

You are obviously looking at a global function associated with your class that simply returns a TypeId.  Now, what about the rest.  The code

    static ns3::TypeId tid = ns3::TypeId ("A")
      .SetParent (Object::GetTypeId ())
      .AddConstructor<A> ();

when found inside the function declaration is called a function-local variable with associated initialization.  It'll be easier to pick this statement apart piece by piece as well.  The first line,

    static ns3::TypeId tid = ...

is the declaration of the function-local variable tid.  This is essentially an initialized global variable, the scope of which has been reduced to within the enclosing method.  You can think of this as a kind of global variable that can only be accessed right there where it is created.  If the variable is initialized, this amounts to the same behavior as if a global static initializer was declared in a namespace of the same name as your class. Global static initializers are guaranteed by the C++ language definition to be executed before your main procedure is entered.  So are function-local variables.

The variable that is being initialized is of type ns3::TypeId, is named A::tid since it is inside the class declaration for class A, and is initialized by a call to the constructor for the class TypeId.  The constructor for TypeId takes a std::string that can be used to locate the type information for your class.  We usually privide the class name as the string.

Hopefully, this much of the declaration is now clear:

  static ns3::TypeId GetTypeId (void)
  {
    static ns3::TypeId tid = ns3::TypeId ("A")
    ...
    return tid;
  }

All that is left now are the lines including SetParent and AddConstructor.

  static ns3::TypeId tid = ns3::TypeId ("A")
    .SetParent (Object::GetTypeId ())
    .AddConstructor<A> ();

The last bit may seem quite odd at first glance, but don't let the way the code is broken up over several lines throw you.  If you saw something like,

  pointer->TypeId()->SetParent()->AddConstructor();

you probably wouldn't hesitate at all.  Clearly, you would think, a method called TypeId is called using the pointer called pointer as shown below.

  pointer->TypeId()

The method TypeId must further return a pointer to an object that has a method called SetParent.  Just as clearly, SetParent must return a pointer to an object that has a method called AddConstructor. The same sort of thing is happening in our code snipped, except we are using references instead of pointers.  Perhaps if we rearrange this code to live on one line it will be clearer.

  ns3::TypeId ("A").SetParent (Object::GetTypeId ()).AddConstructor<A> ();

It's just a string of method calls.  The remaining question is then, what do those three methods do.

The first, ns3::TypeId ("A"), simply allocates a new type in the system and allows you to refer to it in the future by a string.  We have mentioned inheritance trees often in the previous discussion.  The second method, SetParent associates the class being defined with its parents in the tree.  Finally, the AddConstructor method allows you to specify a constructor to be used when an instance of your class is created using CreateObject.

  AddConstructor<A> ();

You can interpret this as explaining to the ns-3 object ssytem that you have a constructor named A::A which takes no parameters.  You are saying that this constructor should be used when CreateObject is called with no parameters.

By including the structure of the inheritance tree, in ns-3 we can use implementation inheritance to easily create new Objects.  You are prevented from doing so in Microsoft COM, but this was almost universally identified as a problem.

So, looking at the entire GetTypeId declaration again,

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

it should be clear what is happening.

A Very Real Example
-------------------

At this point you may be asking yourself what the point of all of this is, since you already had those pointers laying around when you created the objects.  The typical case is that one will create and aggregate some number of Objects in a constructor and return only a pointer to a single Object as in our canonical example with class Node.  In this case, the Node would be created and the Node constructor might create and call AggregateObject to aggregate the Objects for internetwork routing and TCP/IP.  From an external point of view, these aggregated objects may be discovered at run-time using GetObject.

Generally one tends to think of one of the Objects in the aggregation as being the container and other Objects being aggregated to that container.  In the case of a Node, for example, it is quite natural to think of the Node as being the container which contains protocol stacks, internet routing, etc.  So, lets start thinking about a real example by calling the container Object Node instead of A as we have been.  The creation of this <Object> is found all over our example programs.  For example, you will find code like the following in samples/simple-point-to-point.cc:

  Ptr<Node> n = CreateObject<InternetNode> ();

It may appear obvious to you now that the InternetNode class name provided to the template function CreateObject means that InternetNode is an ns-3 Object and you will be able to call GetObject on the resulting smart pointer.  Well, I'm afraid that's not entirely true.  It's slightly more complicated.

Take a look at src/internet-node/internet-node.h and find the class declaration for InternetNode.

  class InternetNode : public Node
  {
  public:
    InternetNode();
    ...
  };

There is no declaration of a static TypeId GetTypeId (void) in this class.  This means that the InternetNode is really not an Object for which you can GetObject.  It turns out that the InternetNode is an implementation class of the Node Object.

You may recall that there can be an implicit cast in a smart pointer assignment if the cast is to a visible, unambiguous base class.  That is, in fact, what is happening here.  Now, take a look at src/node/node.h and find the class declaration for class Node.  There you will find,

  class Node : public Object
  {
  public:
    static TypeId GetTypeId (void);
    ...
  };

Class InternetNode inherits from class Node that, in turn, inherits from class Object.  It is Node that provides a GetTypeId method.  Therefore it is Node that is an ns-3 Object.  Note well that InternetNode is not an Object in the sense that one should call GetObject on an aggregation looking for an InternetNode class.  That is, you should not do,

  Ptr<InternetNode> i = node->GetObject<InternetNode> ();

since there really is not InternetNode::GetTypeId.  It is Node that is the proper Object in this case and you should view InternetNode as an implementation of the Node Object.  This may become clearer as we look a little deeper.

We spoke of a protocol stack that is aggregated to a Node in our discussions above, what we see in the real ns-3 code is that this is represented by the Ipv4 Object.  If you look in src/node/ipv4.h you will find,

  class Ipv4 : public Object
  {
  public:
    static TypeId GetTypeId (void);
    ...
  };

Since class Ipv4 inherits from class Object and has a GetTypeId, it is an ns-3 Object.  If you look in src/node/ipv4.cc you will find,

TypeId
Ipv4::GetTypeId (void)
{
  static TypeId tid = TypeId ("Ipv4")
    .SetParent<Object> ();
  return tid;
}

After all of this reading you know that this code snippet is asking the system to create a unique TypeId for the Ipv4 class and declares that Ipv4 inherits from class Object.  This is what makes an Ipv4 an Object.

It turns out that the Ipv4 class is an abstract base class (ABC).  There are a number of pure virtual methods declared in that class.  This means that an Ipv4 object may not be instantiated.  This is reflected by the fact that there are no constructors registered in the GetTypeId method above. What is instantiated in the real system is an implementation class, called Ipv4Impl.  This class inherits from Ipv4 and provides the required virtual methods.  This is where understanding what is an Object and what is not can get tricky.  The Object is the Ipv4 class since that is where the GetTypeId is found.  The fact that you see GetTypeId there tells you that the Ipv4 class is the class for which you can GetObject.

The class Ipv4Impl provides an implementation for the pure virtual methods in Ipv4.  Since class Ipv4 cannot be instantiated, one instantiates the Ipv4Impl class to create an Ipv4 Object. You will use the CreateObject template function to create an object that implements the methods of an Object.  You can probably see how this gets even more tricky in conversation.

Once the Ipv4Impl object is instantiated, the resulting pointer is immediately cast to an Ipv4 pointer.  Clients will then use the methods specified in the Ipv4 class to access the Ipv4 Object methods which are, in turn, implemented in the Ipv4Impl object.

If you now look in the file, src/internet-node/internet-node.cc you will see the following code in InternetNode::Construct that creates the Ipv4 Interface and aggregates it.

  Ptr<Ipv4Impl> ipv4Impl = CreateObject<Ipv4Impl> (ipv4);
  ...
  Object::AggregateObject (ipv4Impl);

Note that the parameter ipv4 passed to the CreateObject template function is actually a pointer to an Ipv4L3Protocol which you can ignore at this point --- it doesn't really have anything to do with the Ipv4 Interface.

This is exactly the same thing that is happening in the case of the InternetNode.

  Ptr<Node> n = CreateObject<InternetNode> ();

CreateObject is being called to create an implementation object, in this case InternetNode, which implements the methods of the Node Object.  It is the resulting Node Object which you would use as the container and it is the Node class that you would use as the template parameter when calling GetObject.  In the same way, you would not want to do,

  Ptr<Ipv4> ipv4 = node->GetObject<Ipv4Impl> ();

Rather you should understand that the proper Object is the Ipv4 not the Ipv4Impl and do the following,

  Ptr<Ipv4> ipv4 = node->GetObject<Ipv4> ();

This does illustrate that the fact that whether an object created by CreateObject is or is not an Object in the usual sense can be quite well hidden if you are casually looking at the object creation code. The designers of the system had long and involved discussions on this issue and in the end decided that mnemonic aids such as Hungarian notation were a stylistic thing and you should just refer to the system documentation to determine what objects are ns-3 Objects and what the APIs of those Objects actually are (RTFM --- as in Read the Fine Manual, of course).

In the case of Ipv4Impl, you know that the class inherits somehow from Object since there is a call to AggregateObject that refers to an instance of an Ipv4Impl.  You will have to go to the header file src/internet-node/ipv4-impl.h and find that Ipv4Impl inherits from class Ipv4.  You will then have go to the file src/node/ipv4.h and see that it inherits from Object and defines a GetTypeId.  Thus the Object for which you can GetObject is really the Ipv4 Object.

Returning to some real ns-3 example code, lets take a look at examples/simple-point-to-point.cc.  You will find the following code in this file:

  Ptr<Node> n0 = CreateObject<InternetNode> ();
  ...
  Ptr<Ipv4> ipv4;
  ipv4 = n0->GetObject<Ipv4> ();
  ipv4->SetDefaultRoute (Ipv4Address (``10.1.1.2''), 1);

The first line creates an InternetNode implementation object and casts the resulting smart pointer to a Node as we have discussed extensively. The next line shown declares a smart pointer to an Ipv4 Object.  We then do a GetObject on the Node looking for the Ipv4 Object.  You know since you've read every line of this tutorial in detail exactly how that Ipv4 Object got into every Node.  You know that the GetObject will return a smart pointer to its aggregated Ipv4 Interface.  Once we have the Ipv4 smart pointer, we simply use it as if it were any other C++ object.  The last line shows this by setting the default route for the node.

Caveats
=======

There are a few things that you should remember but which may not be immediately obvious.

Ns-3 Objects are Associated with Classes not C++ objects
--------------------------------------------------------

Okay, you can see some of the problems with the terminology popping up again. We are reminding you that when you do a GetObject you are providing the key to the lookup by giving a class name and not anything that is unique to a C++ object.

You cannot add more than one Object of a given type (class name) to an aggregation.  If you need to contain a number of Objects of the same type in the same aggregation, you will need to provide a separate container over which you can iterate.  For example, the Node class provides methods,

  uint32_t GetNDevices (void) const;
  Ptr<NetDevice> GetDevice (uint32_t index) const;

that are used iterate over the multiple NetDevice Objects associated with it.

Remember:  Object types do not identify objects.

Dont use GetObject to Check Your Own Type.
------------------------------------------

It is tempting to use GetObject as a form of runtime type information.  Dont do it.  You have no control over what other object may be added to your aggregation.  Someone else may have appropriated (reimplemented) your type and aggregated themselves onto the aggregation.

Consider a socket factory implementation.  Sockets can be either UDP sockets or TCP sockets.  A socket factory will have a generic SocketFactory Object and either a UDP specific interface for setting UDP parameters or a similar TCP-specific interface.

Consider what might happen if you declared your socket factory as a partially abstract base class, and then provided separate implementations for UDP and TCP specific methods of this factory in separate concrete classes.  Now consider what might happen if you used GetObject in your base class to determine if you were a UDP or a TCP factory.

If a factory, say the UDP version, were not aggregated to any other Object, the base class could GetObject on itself for the UDP-specific class name.  If the GetObject succeeded, it could then infer that it was a UDP implementation and would then do any UDP-specific tasks it could.  [Experienced C++ folks are cringing about how horrible this design is, but bear with me --- its a simple illustration of a specific and perhaps not-too-obvious problem.]

If another factory, say the TCP version, were not aggregated to any other Interface, the base class could GetObject on itself for the UDP-specific interface.  If this failed, it could then infer that it had a TCP implementation and would then do any TCP-specific tasks it could.

Now, what happens when these two working objects are aggregated together by some innocent end-user.  Since the Objects are conceptually snapped together, the TCP implementation would suddenly begin finding the UDP Interface from the other class factory and think it was the UPD implementation.

Objects should not be used as run-time type information.

Connecting the Dots
===================

This may all sound very complicated to you if this is your first exposure to these concepts.  It may be annoying if I tell you that its really not as hard as it sounds.  Rest assured that if you take some time, look at and understand the examples and write a little test code it will all come together for you. Grep around the system for AggregateObject and GetObject and take a look at how we have used them.  This will also give you a good idea of what our core Objects and associated APIs are.  If you grep for GetTypeId you will find most, if not all of the Object API interface declarations in the system.  The more you see this idiom in use, the more comfortable you will be with the idea and the more you will see how this addresses the weak base class, swiss army knife base class, and fragile base class problems I explained at the beginning.

As I alluded to earlier, the developers had long discussions regarding how to make navigating the Object environment easier.  The primary issue was how we could make it easier to convey to you, the model writer, that an object was an Object.  We originally used similar terminology as Microsoft COM and used QueryInterface instead of GetObject.  One suggestion was to adopt the convention that classes that implemented Interfaces must begin with the letter I.  Microsoft does this, as exemplified by the class IUnknown. We also toyed with the idea of beginning our header files with ``i-'' as in ``i-ipv4.h.'' We considered forcing some structure on Interfaces with a pure virtual class specification, the names of which begin with an I; and corresponding implementations, the names of which begin with a C.  This all got out of hand fairly quickly.

In the end we decided that we were really discussing issues of programming style, and we really could not come up with a strong reason to impose any particular solution.  No matter what direction we took, we ended up with some form of extra confusion or extra complexity somewhere in the system.  The resulting system is extremely flexible and easy to use.  It is, unfortunately, sometimes hard to document and talk about.

If it helps you to think in terms of Microsoft COM and Interfaces, by all means do so, just be aware that even though ns-3 Objects descend from COM in some sense, there are subtle differences that may get you lost or into trouble.  So to paraphrase Feynman one more time,

     ``Ns-3 Objects do not behave like COM Components, or Java Beans, or CORBA objects, or clouds or weights on springs, or like anything that you have  ever seen --- they are ns-3 components.''

Just get very familiar with the ns-3 object model.  It is the heart of the system and if you do not understand it you will not understand how to write an ns-3 model properly.

The Doxygen Documentation System
********************************

How to Change Things
********************

How to Set Default Values
*************************

How to Write a New Application
******************************