Eddie,
If you have time, I would like you to consider including a satellite
link emulation element that I have written into the current repo. "A
Mobility Satellite Emulation Testbed (MSET)" 2010 INFOCOM paper
describes the architecture of the element in detail, what it does, and
what fidelity can be expected. The element handles things like TDMA
scheduling, packet framing delays, and dynamic blockages.
http://www.cs.ucsb.edu/~rchertov/MSET/mset.html This page describes a
little package that I have put together. The package includes several
script files that can be used to understand how to put all the pieces
together on Emulab or DETER. However, the element can be successfully
used on a stand alone testbed as well.
Thanks,
Roman
/*
* Roman Chertov
* Copyright (c) 2008-2009 SantaBarbara Labs, LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, subject to the conditions
* listed in the Click LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the Click LICENSE file; the license in that file is
* legally binding.
*/
#include <click/config.h>
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/timestamp.hh>
#include <click/sync.hh>
#include <click/standard/scheduleinfo.hh>
#include "satterm.hh"
#define EVENT_VECTOR_SIZE 20000
#define USEC_PER_SEC 1000000
#define MIN_SNR 1000 // just a large value as noise is very low compared to the signal
CLICK_DECLS
const int DEBUG = 0;
Framer::Framer()
{
_frame_vec.resize(MAX_FRAMES);
_max_size = 0;
_data_size = 0;
_frame_size = 0;
_used_frames = 0;
}
void Framer::set_frame_num(int n)
{
click_chatter("Framer: frame num %d", n);
_max_size = n;
_frame_vec.clear();
_frame_vec.resize(n);
for(int i = 0; i < n; i++)
{
_frame_vec[i].qtail = _frame_vec[i].qhead = 0;
_frame_vec[i].use_size = 0;
_frame_vec[i].writeable = false;
}
}
String Framer::sched_to_string()
{
String res;
char str[256];
memset(str, 0, 256);
for(uint32_t i = 0; i < _max_size; i++)
_frame_vec[i].writeable ? str[i] = 'X' : str[i] = '_';
res = str;
return res;
}
void Framer::show_frames()
{
click_chatter("%s", sched_to_string().c_str());
}
// a single push can result in a packet spanning multiple
// frames. or multiple packets inside a single frame
int Framer::push(uint32_t i, Packet *p)
{
uint32_t psize = p->length() * 8;
Frame *f;
int count = 0;
if (i >= _max_size)
{
click_chatter("Framer: Max index exceeded %d", i);
return -1;
}
while(1)
{
f = &_frame_vec[i];
count++;
// if the next frame is not writeable then, quit
// even though we could have adjusted the sizes of the previous frames, we did not
// insert this new packet. This will not affect the results
if (!f->writeable)
{
_data_size += p->length() * 8 - psize;
return -1;
}
if (!f->use_size)
_used_frames++;
if ((_frame_size - f->use_size) >= psize)
{
// populate the packet list of the current frame
f->use_size += psize;
if (!f->qhead)
f->qhead = f->qtail = p;
else
{
f->qtail->set_next(p);
p->set_next(0);
f->qtail = p;
}
//click_chatter("%d frames per %d sized packet at frame: %d", count, psize, i);
_data_size += p->length() * 8;
return i;
}
else
{
// move to another frame. another frame might be full as well from a previous
// multi-frame push
if (i + 1 < _max_size)
{
psize -= _frame_size - f->use_size;
f->use_size = _frame_size;
i++;
}
else
{
// add to data_size what we have already put into other frames
// when a pop will happen even though the packet is not there the
// size will get adjusted correctly
_data_size += p->length() * 8 - psize;
static int max = 0;
if (_data_size > max)
{
click_chatter("No more frames %d:", _data_size);
max = _data_size;
}
return -1;
}
}
}
}
// return the packet list and clear the frame
Packet* Framer::pop(uint32_t i, uint32_t *size)
{
if (i >= _max_size)
{
click_chatter("Framer:pop %d is outside the bounds", i);
return NULL;
}
Frame *f = &_frame_vec[i];
Packet *p = f->qhead;
if (size)
*size = f->use_size;
_data_size -= f->use_size;
if (f->use_size)
_used_frames--;
f->use_size = 0;
f->qhead = f->qtail = 0;
return p;
}
EventList2::EventList2()
{
_events = 0;
_size = _read = _write = 0;
_ecount = 0;
_lock = new SpinlockIRQ();
}
EventList2::~EventList2()
{
CLICK_LFREE(_events, _size * sizeof(Event));
delete _lock;
}
bool EventList2::set_size(int vec_size)
{
if (!(_events = (Event*)CLICK_LALLOC(vec_size * sizeof(Event))))
return false;
_size = vec_size;
return true;
}
bool EventList2::insert_event(Event *e)
{
SpinlockIRQ::flags_t flags = _lock->acquire();
if (_ecount + 1 <= _size)
{
_ecount++;
_events[_write] = *e;
_write = (_write + 1) % _size;
_lock->release(flags);
return true;
}
_lock->release(flags);
return false;
}
int EventList2::advance_events(Timestamp ts, Event *prev_event, Timestamp )
{
SpinlockIRQ::flags_t flags = _lock->acquire();
if (!_ecount)
{
_lock->release(flags);
return 0;
}
while(1)
{
if (_ecount && _events[_read]._ts < ts )
{
uint32_t tmp = (_read + 1) % _size;
*prev_event = _events[_read];
_events[_read]._ts = 0;
_read = tmp;
_ecount--;
}
else
break;
}
_lock->release(flags);
return 1;
}
Event* EventList2::peek(uint32_t i)
{
SpinlockIRQ::flags_t flags = _lock->acquire();
uint32_t ind = (_read + i) % _size;
_lock->release(flags);
if (i < _ecount)
return _events + ind;
return NULL;
}
// do not call this, as this is only good for debugging of very small event lists
void EventList2::show_events()
{
SpinlockIRQ::flags_t flags = _lock->acquire();
for(uint32_t i = 0; i < _size; i++)
click_chatter("%d TS: %d.%06d SNR: %d BW: %d\n", i, _events[i]._ts.sec(), _events[i]._ts.usec(),
_events[i]._snr, _events[i]._bw);
_lock->release(flags);
}
SatTerm::SatTerm() : _task(this)
{
_synchronized = false;
_curr_segment = 0;
_events_read = 0; // indicates the user-level agent how many events were read
_link_mode = UP_LINK;
_dropped = 0;
_qPkt = 0;
_tx_frame = 0;
_write_frame = 0;
_qhead = _qtail = 0;
_drop_next = false;
_drop_next_count = 0;
_default_event._bw = 1;
_default_event._snr = MIN_SNR;
_default_event._ts.assign(0, 0);
_snr_threshold = 0;
_events_read_total = 0;
_curr_event = _default_event;
_epoch_offset.assign(0, 0);
}
SatTerm::~SatTerm()
{
}
void SatTerm::cleanup(CleanupStage)
{
}
int SatTerm::initialize(ErrorHandler *errh)
{
ScheduleInfo::initialize_task(this, &_task, errh);
if (!_event_list.set_size(EVENT_VECTOR_SIZE))
return -1;
_start_ts = Timestamp::now();
return 0;
}
/*
SYMS_PER_HOP HOPS_PER_FRAME MODULATION FEC SNR_THRESH
*/
uint32_t SatTerm::mode_set(const String &str)
{
Vector<String> conf;
uint32_t res;
uint32_t bits_per_symbol = 0;
uint32_t fec_num = 1;
uint32_t fec_denom = 2;
uint32_t sym_per_hop = 0;
uint32_t hops_per_frame = 0;
cp_spacevec(cp_unquote(str), conf);
if (conf.size() < 5)
{
click_chatter("SatTerm: Incorrect number of parameters specified!\n");
return 0;
}
cp_integer(conf[0], &sym_per_hop);
cp_integer(conf[1], &hops_per_frame);
if (conf[2] == "BPSK")
bits_per_symbol = 1;
else if (conf[2] == "QPSK")
bits_per_symbol = 2;
else if (conf[2] == "8-PSK")
bits_per_symbol = 3;
else if (conf[2] == "QAM")
bits_per_symbol = 4;
else
{
click_chatter("SatTerm: Unknown modulation!\n");
return 0;
}
if (conf[3] == "3/4")
{
fec_num = 3;
fec_denom = 4;
}
else if (conf[3] == "2/3")
{
fec_num = 2;
fec_denom = 3;
}
if (conf.size() == 5)
cp_integer(conf[3], &_snr_threshold);
else
{
click_chatter("SatTerm: No SNR threshold specified!");
return 0;
}
click_chatter("SatTerm: FEC: %d/%d", fec_num, fec_denom);
res = (sym_per_hop * bits_per_symbol * hops_per_frame * fec_num) / fec_denom;
_modulation_str = str;
return res;
}
int SatTerm::process_schedule(Vector<String> &conf)
{
uint32_t numSlotsTmp;
uint32_t slotNumsTmp;
uint32_t ts1 = _epoch_len.sec() * USEC_PER_SEC + _epoch_len.usec();;
uint32_t ts2;
uint32_t ts3;
_slotNum.clear();
_numSlots.clear();
for(int i = 0; i < conf.size(); i++)
{
//click_chatter("%s ", conf[i].c_str());
cp_integer(cp_shift_spacevec(conf[i]).c_str(), &slotNumsTmp);
cp_integer(cp_shift_spacevec(conf[i]).c_str(), &numSlotsTmp);
ts2 = (_frame_length.msec() + _frame_length.sec() * USEC_PER_SEC)*slotNumsTmp;
ts3 = ts2 + (_frame_length.msec() + _frame_length.sec() * USEC_PER_SEC)*numSlotsTmp;
if (ts2 > ts1 || ts3 > ts1)
{
click_chatter("SatTerm: WARNING Segment %d %d outside of epoch. Rejecting offensive input.\n", slotNumsTmp,numSlotsTmp);
continue;
}
_slotNum.push_back(slotNumsTmp);
_numSlots.push_back(numSlotsTmp);
//click_chatter("slotNum: %d \tnumSlots: %d\n", slotNumsTmp, numSlotsTmp);
}
_framer.set_frame_size(_frame_size);
ts1 = _epoch_len.sec() * USEC_PER_SEC + _epoch_len.usec();
ts2 = _frame_length.sec() * USEC_PER_SEC + _frame_length.usec();
_frame_total = ts1 / ts2;
_framer.set_frame_num(_frame_total);
// now need to update it with which frames are writable or not
for(int i = 0; i < _slotNum.size(); i++)
{
for(int j = 0; j < _numSlots[i]; j++)
_framer._frame_vec[_slotNum[i] + j].writeable = true;
}
_framer.show_frames();
return 0;
}
int SatTerm::configure(Vector<String> &conf, ErrorHandler *errh)
{
String mode;
bool uplink;
bool downlink;
Timestamp t_zero(0, 0);
if (cp_va_kparse_remove_keywords(conf, this, errh,
"LATENCY", cpkM, cpTimestamp, &_latency,
"MODE", cpkM, cpString, &mode,
"EPOCH", cpkM, cpTimestamp, &_epoch_len,
"FRAMELENGTH", cpkM, cpTimestamp, &_frame_length,
"GUARDBAND", cpkM, cpTimestamp, &_guardband,
"EPOCH_OFFSET", 0, cpTimestamp, &_epoch_offset,
"UPLINK", 0, cpBool, &uplink,
"DOWNLINK", 0, cpBool, &downlink,
cpEnd) < 0)
return -1;
_frame_size = mode_set(mode);
if (!_frame_size) // something failed in mode_set so just abort
return -1;
click_chatter("SatTerm: frame size: %d", _frame_size);
//Clear old configuration
_slotNum.clear();
_numSlots.clear();
if (_epoch_len == t_zero || _frame_length == t_zero)
{
click_chatter("SatTerm: epoch or frame length is 0!");
return -1;
}
click_chatter("SatTerm: Latency %uus, Epoch: %d.%03d, Frame Len: %d.%03d, Guardband: %03d ms",
_latency.msec(), _epoch_len.sec(), _epoch_len.msec(),
_frame_length.sec(), _frame_length.msec(), _guardband.msec());
process_schedule(conf);
if (uplink && downlink)
return errh->error("Cannot set uplink and downlink both to true");
if (!uplink && !downlink)
return errh->error("Cannot set uplink and downlink both to false");
uplink ? _link_mode = UP_LINK : _link_mode = DOWN_LINK;
click_chatter("SatTerm: Link mode %s", _link_mode == UP_LINK ? "uplink" : "downlink");
return 0;
}
/*
Schedule begins at midnight, so then we can compute when a next epoch begins
to get a clean start
*/
void SatTerm::compute_midnight()
{
Timestamp now = Timestamp::now();
Timestamp midnight((now.sec() / 86400) * 86400, 0); // 86400 seconds/day
Timestamp tdiff = now - midnight;
uint32_t epoch_len = _epoch_len.sec() * 1000 + _epoch_len.msec(); // epoch len in milliseconds
int epoch_num;
uint32_t tmp;
// make computations in milliseconds to avoid using doubles
epoch_num = (tdiff.sec() * 1000 + tdiff.msec()) / epoch_len;
tmp = (epoch_num + 1) * epoch_len;
tdiff.assign_usec(tmp / 1000, (tmp % 1000) * 1000); // convert the millisecond time into seconds and microseconds
_bEpoch = midnight + tdiff;
_bEpoch -= _epoch_offset;
_last_frame_tx = _bEpoch;
click_chatter("Now: %d.%06d", now.sec(), now.usec());
click_chatter("Seconds from midnight %d", now.sec() % 86400);
click_chatter("Epochs since midnight %d", epoch_num);
click_chatter("Next epoch starts at %d.%06d", _bEpoch.sec(), _bEpoch.usec());
}
// pop a frame and then take all the packets on its list
// and send them to the TX queue. Also, change the annotation
// to ensure that desired delay is achieved.
void SatTerm::process_frame(uint32_t f)
{
uint32_t size;
Packet *plist = _framer.pop(f, &size);
Timestamp now = Timestamp::now();
int i = 0;
bool block = false;
Timestamp frame_start = now - _frame_length;
if ((_curr_event._bw == 0 || _curr_event._snr <= _snr_threshold))
block = true;
// this pkt spans several frames so drop it when we finally see it
if (!plist && size && block)
_drop_next = true;
while(1)
{
if (plist)
{
Packet *p = plist;
plist = p->next();
p->set_next(0);
if (_drop_next)
{
_drop_next_count++;
p->kill();
p = 0;
_drop_next = false;
}
else
{
if (block)
{
if (p)
p->kill();
_dropped++;
}
else
{
// these packets must now experience the required latency
// add them to the TX queue now
/*Timestamp t = now - p->timestamp_anno();
click_chatter("PDelay: %d.%06d", t.sec(), t.usec());*/
p->timestamp_anno() = now + _latency;
if (!_qhead)
_qhead = _qtail = p;
else
{
_qtail->next() = p;
_qtail = p;
}
i++;
}
}
}
else
break;
}
/*if (!block)
click_chatter("Frame: %d Popped %d size: %d BW: %d SNR: %d", f, i, size, _curr_event._bw, _curr_event._snr);
else
click_chatter("Frame: %d DROPPED %d size: %d BW: %d SNR: %d", f, i, size, _curr_event._bw, _curr_event._snr);*/
}
// go through the TX queue and emit all the packets whose
// time annotation is smaller than now
void SatTerm::tx_packets()
{
Timestamp now = Timestamp::now();
Packet *p;
while(_qhead && _qhead->timestamp_anno() <= now)
{
p = _qhead;
_qhead = p->next();
if (!_qhead)
_qtail = 0; // empty the list
p->set_next(0);
output(0).push(p);
}
}
// check if we lost our signal.
// that means that SNR is greater than threshold or BW is zero
void SatTerm::detect_signal_loss()
{
Timestamp tzero(0,0);
Timestamp now = Timestamp::now();
// check if the signal is lost
if (!_curr_event._bw || _curr_event._snr <= _snr_threshold)
{
if (_last_signal_ts == tzero)
_last_signal_ts = now;
else if (now - _last_signal_ts >= _frame_length)
_signal = false;
}
else
{
_signal = true;
_last_signal_ts = tzero;
}
}
inline bool SatTerm::signal()
{
if (!_signal && _link_mode == UP_LINK)
return false;
return true;
}
bool SatTerm::run_task(Task *)
{
Packet *p = NULL;
Timestamp now = Timestamp::now();
Timestamp tdiff;
Timestamp tmp;
int curr_frame;
if (!_synchronized)
{
compute_midnight();
_synchronized = true;
}
if (!_event_list.advance_events(now, &_curr_event, _start_ts))
_curr_event = _default_event; // no more events so just a default one
detect_signal_loss();
while(1)
{
tdiff = now - _last_frame_tx;
if (tdiff >= _frame_length)
{
Timestamp e;
e.assign_usec(0, 600);
process_frame(_tx_frame);
_tx_frame = (_tx_frame + 1) % _frame_total;
_last_frame_tx += _frame_length;
}
else
break;
}
tdiff = now - _bEpoch;
if (tdiff > 0 && tdiff < _epoch_len) // avoid negative tdiff when the first epoch is not reached
{
// get index to the current frame
curr_frame = (tdiff.sec() * USEC_PER_SEC + tdiff.usec()) /
(_frame_length.sec() * USEC_PER_SEC + _frame_length.usec());
// the current BW is 0 so we can't pull in the uplink mode
if (!signal())
goto DONE;
if (!_qPkt) //If there is no queued packet try to pull one
p = input(0).pull();
else //Otherwise give us the queued packet.
{
p = _qPkt;
_qPkt = NULL;
}
if (!p) // no packet for us to use
goto DONE;
// if the current BW is 0 drop the packet if we are in the downlink mode
if (p && (!_curr_event._bw || _curr_event._snr <= _snr_threshold) && _link_mode == DOWN_LINK)
{
p->kill();
_dropped++;
goto DONE;
}
while(1) //loop until we find an active segment
{
// Check to see if the packet is in any of the segments
// Calculate begining of transmission segment (bSeg)
tmp.assign_usec(0, _frame_length.usec() * _slotNum[_curr_segment]);
Timestamp bSeg = _bEpoch + tmp;
// Calculate the end of transmission segment (eSeg)
tmp.assign_usec(0, _frame_length.usec() * _numSlots[_curr_segment]);
Timestamp eSeg = bSeg + tmp;
// Consider a Guard band
Timestamp guard;
guard.assign_usec(0, (_guardband.usec()/2));
bSeg += guard;
eSeg -= guard;
// Is the packet in the transmission segment?
if (now >= bSeg && now < eSeg) // can't have <= eSeg as that avoids cases when now==eSeg (meaing the seg is done)
{
// this means that time has advanced enough to change to another frame
// otherwise write to the frame indicated by _write_frame
if (curr_frame > _write_frame)
_write_frame = curr_frame;
if ((_write_frame = _framer.push(_write_frame, p)) != -1)
p = 0; // this packet is gone so don't queue it up
break;
}
else if (now >= eSeg) // go to the next segment
{
if (_curr_segment + 1 < (uint32_t)_slotNum.size())
_curr_segment++;
else
break; // we have exhausted all the segments and a new epoch must start
}
else // now is < bSeg
break;
}
_qPkt = p;
}
else if (tdiff >= _epoch_len)// start a new epoch
{
_bEpoch += _epoch_len;
_curr_segment = 0;
_write_frame = 0;
}
DONE:
// TX packets if it is time
tx_packets();
_task.fast_reschedule();
return true;
}
enum { H_TX_EVENTS, H_EVENTS_READ, H_EVENTS_READ_TOTAL, H_EVENTS_FREE,
H_DROP_NEXT_COUNT, H_MODULATION, H_DROP_COUNT, H_FRAME_SIZE, H_SCHEDULE };
String SatTerm::read_param(Element *e, void *thunk)
{
SatTerm *u = (SatTerm *)e;
switch ((intptr_t) thunk)
{
case H_EVENTS_READ:
return String(u->_events_read);
case H_EVENTS_READ_TOTAL:
return String(u->_events_read_total);
case H_EVENTS_FREE:
return String(u->_event_list.free_events());
case H_DROP_COUNT:
return String(u->_dropped);
case H_DROP_NEXT_COUNT:
return String(u->_drop_next_count);
case H_MODULATION:
return u->_modulation_str;
case H_SCHEDULE:
return u->_framer.sched_to_string();
case H_FRAME_SIZE:
return String(u->_frame_size);
default:
return "<error>";
}
}
int SatTerm::schedule_write_handler(const String &s, Element *e, void *, ErrorHandler *)
{
SatTerm *c = (SatTerm *)e;
Vector<String> conf;
String str = cp_unquote(s);
cp_argvec(str, conf);
return c->process_schedule(conf);
}
int SatTerm::event_write_handler(const String &s, Element *e, void *, ErrorHandler *)
{
SatTerm *sat = (SatTerm *)e;
Vector<String> conf;
String str = cp_unquote(s);
sat->_events_read = 0;
cp_argvec(str, conf);
if (conf.size() % 3)
{
click_chatter("Event vector is not composed out of tripples!");
return -1;
}
for(int32_t i = 0; i < conf.size(); i += 3)
{
Event event;
cp_time(conf[i].c_str(), &event._ts);
event._ts += sat->_start_ts;
cp_integer(conf[i + 1].c_str(), &event._snr);
cp_integer(conf[i + 2].c_str(), &event._bw);
if (!sat->_event_list.insert_event(&event))
break;
sat->_events_read++;
sat->_events_read_total++;
}
return 0;
}
int SatTerm::modulation_write_handler(const String &s, Element *e, void *, ErrorHandler *)
{
SatTerm *sat = (SatTerm *)e;
sat->_frame_size = sat->mode_set(s);
return 0;
}
int SatTerm::reset_ts_write_handler(const String &, Element *e, void *, ErrorHandler *)
{
SatTerm *sat = (SatTerm *)e;
sat->_start_ts = Timestamp::now();
click_chatter("Reset Start ts to: %d.%06d", sat->_start_ts.sec(), sat->_start_ts.usec());
return 0;
}
void SatTerm::add_handlers()
{
add_write_handler("schedule", schedule_write_handler, 0);
add_read_handler("events_read", read_param, (void *)H_EVENTS_READ);
add_read_handler("events_read_total", read_param, (void *)H_EVENTS_READ_TOTAL);
add_read_handler("events_free", read_param, (void *)H_EVENTS_FREE); // how many new events can be put into the vector
add_read_handler("dropped", read_param, (void *)H_DROP_COUNT);
add_read_handler("drop_next", read_param, (void *)H_DROP_NEXT_COUNT);
add_read_handler("modulation", read_param, (void *)H_MODULATION);
add_read_handler("schedule", read_param, (void *)H_SCHEDULE);
add_read_handler("frame_size", read_param, (void *)H_FRAME_SIZE);
// write handlers
add_write_handler("modulation", modulation_write_handler, 0);
add_write_handler("events", event_write_handler, 0, Handler::NONEXCLUSIVE);
add_write_handler("reset_start_ts", reset_ts_write_handler, 0, 0);
}
CLICK_ENDDECLS
EXPORT_ELEMENT(SatTerm)
/*
* Roman Chertov
* Copyright (c) 2008-2009 SantaBarbara Labs, LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, subject to the conditions
* listed in the Click LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the Click LICENSE file; the license in that file is
* legally binding.
*/
#ifndef CLICK_SATTERM_HH
#define CLICK_SATTERM_HH
#include <click/element.hh>
#include <click/timestamp.hh>
#include <click/task.hh>
#include <click/timer.hh>
#include <click/notifier.hh>
#define EVENT_VECTOR_NUM 2
#define MAX_FRAMES 100
CLICK_DECLS
/*
=c
SatTerm([I<keywords> LATENCY, MODE, EPOCH, FRAMELENGTH, GUARDBAND, UPLINK, DOWNLINK,
EPOCH_OFFSET], TDMA_schedule)
=s shaping
pull-to-push converter
=d
Pulls packets whenever they are available, then pushes them out
its single output. The element shapes the packets to achieve delay, loss,
and jitter which is representative of a satellite link. The packets are
aggregated into frames and then are sent to a "satellite." The element
also follows a TDMA schedule specified by the user. For a detailed
explanation of how the element operates and what behaviour it mimics,
please see the following paper:
Roman Chertov, Daniel Havey, and Kevin Almeroth,
"MSET: A Mobility Satellite Emulation Testbed", INFOCOM 2010
For additional scripts which show how to run the experiments please see
http://www.cs.ucsb.edu/~rchertov/MSET/mset.html
http://www.cs.ucsb.edu/~rchertov/MSET/mset.tgz
Keyword arguments are:
=over 8
=item LATENCY
Propagation latency to/from a satellite
=item MODE
Modulation parameter of the link. This is a string of the format
SYMS_PER_HOP HOPS_PER_FRAME (BPSK | QPSK | 8-PSK | QAM) FEC SNR_THRESH
For example, "216 200 QPSK 1/2 12" specifies to use 216 symbols per hop,
200 hops per a given frame, to use 2 bits per symbol (QPSK), to use 1/2 FEC,
and to have a SNR threshold of 12. This modulation would produce a frame
of 43200 bits = 216 * 200 * 2 * (1/2).
=item EPOCH
The duration of a TDMA epoch in ms.
=item FRAMELENGTH
The duration of a TDMA frame in ms. (EPOCH/FRAMELENGTH = # of TDMA frames)
=item GUARDBAND
The duration of a guardband that separates individual TDMA frames in ms.
=item UPLINK
Takes a true or false. Specifies if the link operates in the uplink mode.
Uplink modes means that a blockage can be determined by a loss of a reference
signal, and that packets will be queued up.
=item DOWNLINK
Takes a true or false. Specifies if the link operates in the downlink mode.
Downlink modes means that a blockage cannot be determined by a loss of a reference
signal, and that packets will be transmitted and lost.
=item EPOCH_OFFSET
Specifies an offset in ms by how much to offset the start of a TDMA epoch.
It is useful in the following scenario. Given 2 satellite terminals A and B
which have different propagation delays to the satellite, epoch offset can be used
to ensure that both terminals operate on the same TDMA schedule from the satellite's
view point. Assume A has a 140 ms propagation delay and that B has a 150 ms propagation
delay. B would have to transmit 10 ms sooner to make up for the extra propagation delay.
=h schedule r
Print the current TDMA schedule.
=h schedule w
Create a new TDMA schedule.
=h modulation w
Specify a new modulation string.
=h modulation r
View the current modulation string.
=h events w
Write events into the event heap. The events are of a form, "time_offset, SNR, Bandwidth".
Time offset is from a specified starting point in time. SNR is current SNR for this event.
If the SNR is below the SNR threshold, then packets can get lost. Bandwidth can
be 0 or 1. A zero values implies that there is a blockage.
=h reset_start_ts w
Reset the event base time to now.
=h events_read r
Number of events read by the element after the last event write. At every
write, this counter gets reset. This is useful to keep track the process
of event processing to avoid overfilling the event heap.
=h events_read_total r
Total number of the events read by the element.
=h events_free r
How many new events can be put into the event heap.
=h dropped r
Number of dropped packets.
=h drop_next r
Number of dropped packets that spanned two or more frames.
=h frame_size r
The size of the current frame. This depends on the modulation string.
=back
=e
from_sat :: FromHost(SAT_TERM, PREFIX 10.0.0.1/8, ETHER 00:04:23:AE:CF:F0)
-> to_sat_cl :: Classifier(12/86dd 20/3aff 54/87, -)[1]
-> sat_mac :: StoreEtherAddress(00:04:23:ae:d1:52, dst)
-> sat_Q :: Queue(256)
-> ul_sat0 :: SatTerm(LATENCY 140ms, MODE "216 200 QPSK 1/2 12", EPOCH 1000ms,
FRAMELENGTH 20ms, GUARDBAND 0ms, UPLINK true,
EPOCH_OFFSET 0ms,
0 1, 4 1, 8 1, 12 1, 16 1, 20 1, 24 1, 28 1, 32 1,
36 1, 40 1, 44 1, 48 1);
Create an uplink link shaper that uses every 4th TDMA frame. The schedule is specified
as TDMA frame index and how many frames starting at that index to use.
*/
class Framer
{
public:
Framer();
int push(uint32_t i, Packet *p); // push a packet
Packet* pop(uint32_t i, uint32_t *size = 0); // pop a frame
void set_frame_num(int n);
void set_frame_size(int s) { _frame_size = s; }
int data_size() { return _data_size; }
void show_frames();
String sched_to_string();
uint32_t used_frames() { return _used_frames; }
struct Frame
{
Frame() { qhead = qtail = NULL; use_size = 0; writeable = true;}
uint32_t use_size;
Packet *qhead;
Packet *qtail;
bool writeable;
};
public:
Vector<Frame> _frame_vec;
private:
uint32_t _frame_size;
uint32_t _max_size;
int _data_size;
uint32_t _used_frames;
};
class Event
{
public:
Timestamp _ts;
uint32_t _snr;
uint32_t _bw;
};
class EventList2
{
public:
EventList2();
~EventList2();
int advance_events(Timestamp ts, Event *prev_event, Timestamp sts);
Event* peek(uint32_t i);
bool set_size(int vec_size);
bool insert_event(Event *);
void show_events();
uint32_t free_events() { return _size - _ecount; }
private:
Event *_events;
uint32_t _size;
uint32_t _read;
uint32_t _write;
uint32_t _ecount;
SpinlockIRQ *_lock;
};
class SatTerm : public Element
{
// deal with the scenario
public:
SatTerm();
~SatTerm();
const char *class_name() const { return "SatTerm"; }
const char *port_count() const { return PORTS_1_1; }
const char *processing() const { return PULL_TO_PUSH; }
int configure(Vector<String>&, ErrorHandler*);
void cleanup(CleanupStage);
int initialize(ErrorHandler *errh);
bool run_task(Task *);
private:
void add_handlers();
uint32_t mode_set(const String &str);
int process_schedule(Vector<String> &sched);
void compute_midnight();
void process_frame(uint32_t );
void tx_packets();
void detect_signal_loss();
bool signal();
Task _task;
Packet *_qPkt;
Vector <int> _slotNum;
Vector <int> _numSlots;
int _tx_frame;
Timestamp _last_frame_tx;
uint32_t _frame_total;
uint32_t _frame_size;
uint32_t _curr_segment;
int _write_frame;
Framer _framer;
Timestamp _epoch_len;
Timestamp _frame_length;
Timestamp _guardband;
Timestamp _bEpoch;
Timestamp _epoch_offset;
Timestamp _latency;
bool _synchronized;
String _modulation_str;
// variables for adding latency
Packet *_qhead;
Packet *_qtail;
// events related variables
uint32_t _events_read;
uint32_t _events_read_total;
EventList2 _event_list;
Timestamp _start_ts;
int _link_mode;
uint32_t _snr_threshold;
uint32_t _dropped;
uint32_t _drop_next_count;
bool _drop_next;
Event _default_event;
Event _curr_event; // an event just before the current one
bool _signal;
Timestamp _last_signal_ts;
enum {UP_LINK, DOWN_LINK}; // in uplink mode 0 bandwidth just means to delay packets
// in downlink mode 0 bandwidth means to pull and drop packets
// handler functions
static String read_param(Element *e, void *thunk);
static int event_write_handler(const String &, Element *, void *, ErrorHandler *);
static int modulation_write_handler(const String &s, Element *e, void *, ErrorHandler *);
static int reset_ts_write_handler(const String &s, Element *e, void *, ErrorHandler *);
static int schedule_write_handler (const String &s, Element *e, void *, ErrorHandler *errh);
};
CLICK_ENDDECLS
#endif
_______________________________________________
click mailing list
[email protected]
https://amsterdam.lcs.mit.edu/mailman/listinfo/click