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

Reply via email to