packet.h
enum packet_t {
	PT_TCP,
	PT_UDP,
	PT_CBR,
	.. .. ..
	// Pushback Messages
	PT_PUSHBACK,
	// MM Example Agent/
	// Tai Begin
	PT_UDPmm,
	// Tai End

	// insert new packet types here
	PT_NTYPE // This MUST be the LAST one
};

class p_info {
public:
	p_info() {
		name_[PT_TCP]= "tcp";
		name_[PT_UDP]= "udp";
		name_[PT_CBR]= "cbr";
		.. .. ..
		//pushback 
		name_[PT_PUSHBACK] = "pushback";
		// MM Example
		// Tai Begin
		name_[PT_UDPmm] = "udpmm";
		// Tai End
		// MM Example
		name_[PT_NTYPE]= "undefined";
	}



ns-packet.tcl
foreach prot {
	AODV
	ARP
  	ASRM
	.. .. ..
	UMP 
	# Tai Begin
	# PUSHBACK
	Pushback
	Src_rt
	Multimedia
	# Tai End
	NV
} {
	add-packet-header $prot
}



ns-default.tcl
# Tai Begin
Application/MmApp set rate0_ 0.3mb
Application/MmApp set rate1_ 0.6mb
Application/MmApp set rate2_ 0.9mb
Application/MmApp set rate3_ 1.2mb
Application/MmApp set rate4_ 1.5mb
Application/MmApp set pktsize_ 1000
Application/MmApp set random_ false
# Tai End



udp-mm.h
//
// Author:    Jae Chung 
// Date:      7/17/99
// File Name: udp-mm.h
//

#ifndef ns_udp_mm_h
#define ns_udp_mm_h

#include "udp.h"
#include "ip.h"

// Multimedia Header Structure
struct hdr_mm {
	int ack;     // is it ack packet?
	int seq;     // mm sequence number
	int nbytes;  // bytes for mm pkt
	double time; // current time
	int scale;   // scale (0-4) associated with data rates

	static int offset_;
	inline static int& offset() {return offset_;}
	inline static hdr_mm* access(const Packet* p){
		return (hdr_mm*) p->access(offset_);
	}
};


// Used for Re-assemble segmented (by UDP) MM packet
struct asm_mm { 
	int seq;     // mm sequence number
	int rbytes;  // currently received bytes
	int tbytes;  // total bytes to receive for MM packet
};


// UdpMmAgent Class definition
class UdpMmAgent : public UdpAgent {
public:
	UdpMmAgent();
	UdpMmAgent(int);
	virtual int supportMM() { return 1; }
	virtual void enableMM() { support_mm_ = 1; }
	virtual void sendmsg(int nbytes, const char *flags = 0);
	void recv(Packet*, Handler*);
protected:
	int support_mm_; // set to 1 if above is MmApp
private:
	asm_mm asm_info; // packet re-assembly information
};

#endif




udp-mm.cc
//
// Author:    Jae Chung
// Date:      7/17/99
// File Name: udp-mm.cc
//

#include "udp-mm.h"
#include "rtp.h"
#include "random.h"
#include <string.h>

int hdr_mm::offset_;

// Mulitmedia Header Class 
static class MultimediaHeaderClass : public PacketHeaderClass {
public:
	MultimediaHeaderClass() : PacketHeaderClass("PacketHeader/Multimedia",
						    sizeof(hdr_mm)) {
		bind_offset(&hdr_mm::offset_);
	}
} class_mmhdr;


// UdpMmAgent OTcl linkage class
static class UdpMmAgentClass : public TclClass {
public:
	UdpMmAgentClass() : TclClass("Agent/UDP/UDPmm") {}
	TclObject* create(int, const char*const*) {
		return (new UdpMmAgent());
	}
} class_udpmm_agent;


// Constructor (with no arg)
UdpMmAgent::UdpMmAgent() : UdpAgent(PT_UDPmm)
{
	support_mm_ = 0;
	asm_info.seq = -1;
}


// Constructor (with one arg)
UdpMmAgent::UdpMmAgent(int type) : UdpAgent(PT_UDPmm)
{
	support_mm_ = 0;
	asm_info.seq = -1;
}

// Add Support of Multimedia Application to UdpAgent::sendmsg
void UdpMmAgent::sendmsg(int nbytes, const char* flags)
{
	Packet *p;
	int n, remain;

 
	if (size_) {
		n = (nbytes/size_ + (nbytes%size_ ? 1 : 0));
		remain = nbytes%size_;
	}
	else
		printf("Error: UDPmm size = 0\n");

	if (nbytes == -1) {
		printf("Error:  sendmsg() for UDPmm should not be -1\n");
		return;
	}
	double local_time =Scheduler::instance().clock();
	while (n-- > 0) {
		p = allocpkt();
		if(n==0 && remain>0) hdr_cmn::access(p)->size() = remain;
//		hdr_rtp* rh = (hdr_rtp*)p->access(off_rtp_);
		hdr_rtp* rh = hdr_rtp::access(p);
		rh->flags() = 0;
		rh->seqno() = ++seqno_;
		hdr_cmn::access(p)->timestamp() = 
		    (u_int32_t)(SAMPLERATE*local_time);
		// to eliminate recv to use MM fields for non MM packets
		hdr_mm* mh = hdr_mm::access(p);
		mh->ack = 0;
		mh->seq = 0;
		mh->nbytes = 0;
		mh->time = 0;
		mh->scale = 0;
		// mm udp packets are distinguished by setting the ip
		// priority bit to 15 (Max Priority).
		if(support_mm_) {
//			hdr_ip* ih = (hdr_ip*)p->access(off_ip_);
			hdr_ip* ih = hdr_ip::access(p);
			ih->prio_ = 15;
			if(flags) // MM Seq Num is passed as flags
				memcpy(mh, flags, sizeof(hdr_mm));
		}
		// add "beginning of talkspurt" labels (tcl/ex/test-rcvr.tcl)
		if (flags && (0 ==strcmp(flags, "NEW_BURST")))
			rh->flags() |= RTP_M;
		target_->recv(p);
	}
	idle();
}


// Support Packet Re-Assembly and Multimedia Application
void UdpMmAgent::recv(Packet* p, Handler*)
{
//	hdr_ip* ih = (hdr_ip*)p->access(off_ip_);
	hdr_ip* ih = hdr_ip::access(p);
	int bytes_to_deliver = hdr_cmn::access(p)->size();

	// if it is a MM packet (data or ack)
	if(ih->prio_ == 15) { 
		if(app_) {  // if MM Application exists
			// re-assemble MM Application packet if segmented
			hdr_mm* mh = hdr_mm::access(p);
			if(mh->seq == asm_info.seq)
				asm_info.rbytes += hdr_cmn::access(p)->size();
			else {
				asm_info.seq = mh->seq;
				asm_info.tbytes = mh->nbytes;
				asm_info.rbytes = hdr_cmn::access(p)->size();
			}
			// if fully reassembled, pass the packet to application
			if(asm_info.tbytes == asm_info.rbytes) {
				hdr_mm mh_buf;
				memcpy(&mh_buf, mh, sizeof(hdr_mm));
				app_->recv_msg(mh_buf.nbytes, (char*) &mh_buf);
			}
		}
		Packet::free(p);
	}
	// if it is a normal data packet (not MM data or ack packet)
	else { 
		if (app_) app_->recv(bytes_to_deliver);
		Packet::free(p);
	}
}



mm-app.h
//
// Author:    Jae Chung
// Date:      7/17/99
// File Name: mm-app.h
//

#include "timer-handler.h"
#include "packet.h"
#include "app.h"
#include "udp-mm.h"

// This is used for receiver's received packet accounting
struct pkt_accounting { 
        int last_seq;   // sequence number of last received MM pkt
        int last_scale; // rate (0-4) of last acked
        int lost_pkts;  // number of lost pkts since last ack
        int recv_pkts;  // number of received pkts since last ack
        double rtt;     // round trip time
};


class MmApp;


// Sender uses this timer to 
// schedule next app data packet transmission time
class SendTimer : public TimerHandler {
 public:
	SendTimer(MmApp* t) : TimerHandler(), t_(t) {}
	inline virtual void expire(Event*);
 protected:
	MmApp* t_;
};


// Reciver uses this timer to schedule
// next ack packet transmission time
class AckTimer : public TimerHandler {
 public:
	AckTimer(MmApp* t) : TimerHandler(), t_(t) {}
	inline virtual void expire(Event*);
 protected:
	MmApp* t_;
};


// Mulitmedia Application Class Definition
class MmApp : public Application {
 public:
	MmApp();
	virtual void recv_msg(int nbytes, const char *msg = 0); // (Sender/Receiver)
	void send_mm_pkt();  // called by SendTimer:expire (Sender)
	void send_ack_pkt(); // called by AckTimer:expire (Receiver)
 protected:
	int command(int argc, const char*const* argv);
	void start();       // Start sending data packets (Sender)
	void stop();        // Stop sending data packets (Sender)
 private:
	void init();
	inline double next_snd_time();                          // (Sender)
	void set_scale(const hdr_mm *mh_buf);                   // (Sender)
	void adjust_scale(void);                                // (Receiver)
	void account_recv_pkt(const hdr_mm *mh_buf);            // (Receiver)
	void init_recv_pkt_accounting();                        // (Receiver)

	double rate[5];        // Transmission rates associated to scale values
	double interval_;      // Application data packet transmission interval
	int pktsize_;          // Application data packet size
	int random_;           // If 1 add randomness to the interval
	int running_;          // If 1 application is running
	int seq_;              // Application data packet sequence number
	int scale_;            // Media scale parameter
	pkt_accounting p_accnt;
	SendTimer snd_timer_;  // SendTimer
	AckTimer  ack_timer_;  // AckTimer
};




mm-app.cc
//
// Author:    Jae Chung
// Data:      7/17/99
// File Name: mm-app.cc
// 

#include "random.h"
#include "mm-app.h"


// MmApp OTcl linkage class
static class MmAppClass : public TclClass {
 public:
  MmAppClass() : TclClass("Application/MmApp") {}
  TclObject* create(int, const char*const*) {
    return (new MmApp);
  }
} class_app_mm;


// When snd_timer_ expires call MmApp:send_mm_pkt()
void SendTimer::expire(Event*)
{
  t_->send_mm_pkt();
}


// When ack_timer_ expires call MmApp:send_ack_pkt()
void AckTimer::expire(Event*)
{
  t_->send_ack_pkt();
}


// Constructor (also initialize instances of timers)
MmApp::MmApp() : running_(0), snd_timer_(this), ack_timer_(this)
{
  bind_bw("rate0_", &rate[0]);
  bind_bw("rate1_", &rate[1]);
  bind_bw("rate2_", &rate[2]);
  bind_bw("rate3_", &rate[3]);
  bind_bw("rate4_", &rate[4]);
  bind("pktsize_", &pktsize_);
  bind_bool("random_", &random_);
}


// OTcl command interpreter
int MmApp::command(int argc, const char*const* argv)
{
  Tcl& tcl = Tcl::instance();

  if (argc == 3) {
    if (strcmp(argv[1], "attach-agent") == 0) {
      agent_ = (Agent*) TclObject::lookup(argv[2]);
      if (agent_ == 0) {
	tcl.resultf("no such agent %s", argv[2]);
	return(TCL_ERROR);
      }

      // Make sure the underlying agent support MM
      if(agent_->supportMM()) {
	agent_->enableMM();
      }
      else {
	tcl.resultf("agent \"%s\" does not support MM Application", argv[2]);
	return(TCL_ERROR);
      }
      
      agent_->attachApp(this);
      return(TCL_OK);
    }
  }
  return (Application::command(argc, argv));
}



void MmApp::init()
{
  scale_ = 0; // Start at minimum rate
  seq_ = 0;   // MM sequence number (start from 0)
  interval_ = (double)(pktsize_ << 3)/(double)rate[scale_];
}


void MmApp::start()
{
  init();
  running_ = 1;
  send_mm_pkt();
}


void MmApp::stop()
{
  running_ = 0;
}


// Send application data packet
void MmApp::send_mm_pkt()
{
  hdr_mm mh_buf;

  if (running_) {
    // the below info is passed to UDPmm agent, which will write it 
    // to MM header after packet creation.
    mh_buf.ack = 0;            // This is a MM packet
    mh_buf.seq = seq_++;         // MM sequece number
    mh_buf.nbytes = pktsize_;  // Size of MM packet (NOT UDP packet size)
    mh_buf.time = Scheduler::instance().clock(); // Current time
    mh_buf.scale = scale_;                       // Current scale value
    agent_->sendmsg(pktsize_, (char*) &mh_buf);  // send to UDP

    // Reschedule the send_pkt timer
    double next_time_ = next_snd_time();
    if(next_time_ > 0) snd_timer_.resched(next_time_);
  }
}


// Schedule next data packet transmission time
double MmApp::next_snd_time()
{
  // Recompute interval in case rate or size chages
  interval_ = (double)(pktsize_ << 3)/(double)rate[scale_];
  double next_time_ = interval_;
  if(random_) 
    next_time_ += interval_ * Random::uniform(-0.5, 0.5);
  return next_time_;
}


// Receive message from underlying agent
void MmApp::recv_msg(int nbytes, const char *msg = 0)
{
  if(msg) {
    hdr_mm* mh_buf = (hdr_mm*) msg;

    if(mh_buf->ack == 1) {
      // If received packet is ACK packet
      set_scale(mh_buf);
    }
    else {
      // If received packet is MM packet
      account_recv_pkt(mh_buf);
      if(mh_buf->seq == 0) send_ack_pkt();
    }
  }
}


// Sender sets its scale to what reciver notifies
void MmApp::set_scale(const hdr_mm *mh_buf)
{ 
  scale_ = mh_buf->scale;
}


void MmApp::account_recv_pkt(const hdr_mm *mh_buf)
{ 
  double local_time = Scheduler::instance().clock();

  // Calculate RTT
  if(mh_buf->seq == 0) {
    init_recv_pkt_accounting();
    p_accnt.rtt = 2*(local_time - mh_buf->time);
  }
  else
    p_accnt.rtt = 0.9 * p_accnt.rtt + 0.1 * 2*(local_time - mh_buf->time); 

  // Count Received packets and Calculate Packet Loss
  p_accnt.recv_pkts ++;
  p_accnt.lost_pkts += (mh_buf->seq - p_accnt.last_seq - 1);
  p_accnt.last_seq = mh_buf->seq;
}


void MmApp::init_recv_pkt_accounting()
{
  p_accnt.last_seq = -1;
  p_accnt.last_scale = 0; 
  p_accnt.lost_pkts = 0;
  p_accnt.recv_pkts = 0;
}


void MmApp::send_ack_pkt(void)
{
  double local_time = Scheduler::instance().clock();

  adjust_scale();

  // send ack message
  hdr_mm ack_buf;
  ack_buf.ack = 1;  // this packet is ack packet
  ack_buf.time = local_time;
  ack_buf.nbytes = 40;  // Ack packet size is 40 Bytes
  ack_buf.scale = p_accnt.last_scale;
  agent_->sendmsg(ack_buf.nbytes, (char*) &ack_buf);

  // schedul next ACK time
  ack_timer_.resched(p_accnt.rtt);
}


void MmApp::adjust_scale(void)
{
  if(p_accnt.recv_pkts > 0) {
    if(p_accnt.lost_pkts > 0)
      p_accnt.last_scale = (int)(p_accnt.last_scale / 2);
    else {
      p_accnt.last_scale++;
      if(p_accnt.last_scale > 4) p_accnt.last_scale = 4;
    }
  }
  p_accnt.recv_pkts = 0;
  p_accnt.lost_pkts = 0;
}




class Packet{
        ......
        static int hdrlen_;
};

class PacketHeaderClass: public TclClass{
protected:
  PacketHeaderClass(const char* classname, int hdrsize);
  TclObject* create(int argc, const char*const* argv);

  virtual void bind();
  virtual int method(int argc, const char*const* argv);
};

void PacketHeaderClass::bind()
{
  /* Call to base class bind() must precede add_method() */
  TclClass::bind();
  add_method("hdrlen");
}

int PacketHeaderClass::method(int ac, const char*const* av)
{
  Tcl& tcl = Tcl::instance();

  int argc = ac - 2;
  const char*const* argv = av + 2;
  if (argc == 2){
    if (strcmp(argv[1],"hdrlen") == 0){
      tcl.resultf("%d", Packet::hdrlen_);
      return (TCL_OK);
    }
  }
  else if (argc == 3){
    if (strcmp(argv[1],"hdrlen") == 0){
      Packet::hdrlen_ = atoi(argv[2]);
      return (TCL_OK);
    }
  }
  return TclClass::method(ac,av);
}


PacketHeader hdrlen 120
set i [PacketHeader hdrlen]

