// Sliding window protocol
// Originally adapted from the dissertation of Roope Kaivola
// by Timo Latvala and Marko Mäkelä
// Modular version by Marko Mäkelä

/// Flags

// #define TIMEOUT_RECV // disable timeouts when acks are pending
// #define TIMEOUT_SEND // disable timeouts when data is pending
// #define LOSSY // lossy channels
// #define TIMEOUT // allow sender timeouts
// #define NODATA // eliminate data_t (assume id-less data)
// #define PRIOP // the producer has priority over other sender actions
// #define LLQ // use low-level queue data types (introduces shifting)
// #define LLA // use low-level acknowledgement array (introduces shifting)
// #define N_SEND // monitor the number of messages in sender queue

/// Parameters

/** Size of the transmission window */
unsigned tw = 3;
/** Size of the reception window */
unsigned rw = 3;
/** Max value for sequnce numbers used */
typedef unsigned (tw + rw - 1..) seqnum_t;
seqnum_t w = tw + rw - 1;

/** Data items */
#ifndef NODATA
typedef enum {red/*, white, blue*/} data_t;
#endif // !NODATA

/// The model

#ifdef LOSSY
# define TIMEOUT
#endif
#ifndef TIMEOUT
# undef TIMEOUT_RECV
# undef TIMEOUT_SEND
# undef TIMEOUT_VISIBLE
#endif
#ifdef TIMEOUT_RECV
# define TIMEOUT_VISIBLE
#endif
#ifdef TIMEOUT_SEND
# define TIMEOUT_VISIBLE
#endif

/** Sequence numbers */
typedef unsigned (0..w) seq_t;

/** Items on the transmission channel*/
typedef struct {
#ifndef NODATA
  data_t data;
#endif // !NODATA
  seq_t seqnum;
} channel_t;

#ifdef LLQ
/** The slot number of a low-level queue */
typedef unsigned (0..w - 1) slot_t;
#endif // LLQ

/** Modulus addition: (x + y) mod w */
seq_t modP (unsigned x, unsigned y) is seq_t ((x + y) % (w + 1));
/** Modulus subtraction: (x - y) mod w */
unsigned modM (unsigned x, unsigned y) (((w + 1) + x - y) % (w + 1));

// The synchronisation alphabet

#ifdef N_SEND
/** Number of messages in sender queue */
typedef unsigned (0..w) n_msg_t;
place n_msg (1) n_msg_t: 0;
prop msg_empty: place n_msg equals 0;
prop msg_full: place n_msg equals w;
# ifdef LOSSY
/** Increment the number of messages in sender queue */
trans :lose_msg
in { place n_msg: n_msg; }
out { place n_msg: is n_msg_t (n_msg - 1); };
# endif // LOSSY
#endif // N_SEND

/** Send a data message */
trans :send_msg
#ifdef N_SEND
in { place n_msg: n_msg; } out { place n_msg: is n_msg_t (n_msg + 1); }
#endif // N_SEND
{ channel_t wr; };
/** Receive a data message */
trans :recv_msg
#ifdef N_SEND
in { place n_msg: n_msg; } out { place n_msg: is n_msg_t (n_msg - 1); }
#endif // N_SEND
{ channel_t rd; };
/** Ignore a data message */
trans :ign_msg
#ifdef N_SEND
in { place n_msg: n_msg; } out { place n_msg: is n_msg_t (n_msg - 1); }
#endif // N_SEND
{ channel_t rd; };
/** Send an acknowledgement message */
trans :send_ack { seq_t wr; };
/** Receive an acknowledgement message */
trans :recv_ack { seq_t rd; };
/** Ignore an acknowledgement message */
trans :ign_ack { seq_t rd; };
# ifdef TIMEOUT_VISIBLE
/** Sender timeout */
trans :timeout;
# endif // TIMEOUT_VISIBLE

/** Transmission channel */
subnet ch_msg
{
#ifdef LLQ
  /** The transmission queue */
  place msg_q (0..#slot_t) struct { slot_t slot; channel_t ch; };
  /** Empty slots in msg_q */
  place msg_q_empty (0..#slot_t) slot_t:
  (slot_t slot: slot) minus map m {place msg_q} m.slot;
# ifdef LOSSY
  /** Lose a message */
  trans msg_lose
#  ifdef N_SEND
  : trans lose_msg
#  endif // N_SEND
  in {
    place msg_q: { 0, m };
  }
  out {
    place msg_q_empty: 0;
  };
# endif // LOSSY
  /** Shift a message */
  trans msg_shift
  in {
    place msg_q_empty: x;
    place msg_q: { +x, m };
  }
  out {
    place msg_q_empty: +x;
    place msg_q: { x, m };
  }
  gate x != >slot_t;

  /** Enqueue a message */
  trans msg_send
  : trans send_msg
  in {
    place msg_q_empty: >slot_t;
  }
  out {
    place msg_q: { >slot_t, wr };
  };

  /** Dequeue a message */
  trans msg_recv
  : trans recv_msg
  in { place msg_q: { 0, rd }; }
  out { place msg_q_empty: 0; };

  /** Ignore a message */
  trans msg_ign
  : trans ign_msg
  in { place msg_q: { 0, rd }; }
  out { place msg_q_empty: 0; };

# ifdef TIMEOUT_SEND
  /** Allow a timeout to occur */
  trans msg_timeout
  : trans timeout
  in { place msg_l: 0; }
  out { place msg_l: 0; };
# endif // TIMEOUT_SEND
#else // LLQ
  /** Contents of the transmission channel */
  place msg_channel (1) channel_t[queue w]: {};

# ifdef LOSSY
  /** Lose a message */
  trans msg_lose
#  ifdef N_SEND
  : trans lose_msg
#  endif // N_SEND
  in { place msg_channel: ch; }
  out { place msg_channel: -ch; }
  gate /ch != 0;
# endif // LOSSY

  /** Enqueue a message */
  trans msg_send
  : trans send_msg
  in { place msg_channel: ch; }
  out { place msg_channel: ch + is channel_t wr; }
  gate %ch != 0;

  /** Dequeue a message */
  trans msg_recv
  : trans recv_msg
  in { place msg_channel: ch; }
  { channel_t rd = *ch; }
  out { place msg_channel: -ch; }
  gate /ch != 0;

  /** Ignore a message */
  trans msg_ign
  : trans ign_msg
  in { place msg_channel: ch; }
  { channel_t rd = *ch; }
  out { place msg_channel: -ch; }
  gate /ch != 0;

# ifdef TIMEOUT_SEND
  /** Allow a timeout to occur */
  trans msg_timeout
  : trans timeout
  in { place msg_channel: {}; }
  out { place msg_channel: {}; };
# endif // TIMEOUT_SEND
#endif // LLQ

};

/** Acknowledgement channel */
subnet ch_ack
{

#ifdef LLQ
  /** The acknowledgement queue */
  place ack_q (0..#slot_t) struct { slot_t slot; seq_t ch; };
  /** Empty slots in ack_q */
  place ack_q_empty (0..#slot_t) slot_t:
  (slot_t slot: slot) minus map m {place ack_q} m.slot;
# ifdef LOSSY
  /** Lose a message */
  trans ack_lose
  in {
    place ack_q: { 0, m };
  }
  out {
    place ack_q_empty: 0;
  };
# endif // LOSSY
  /** Shift a message */
  trans ack_shift
  in {
    place ack_q_empty: x;
    place ack_q: { +x, m };
  }
  out {
    place ack_q_empty: +x;
    place ack_q: { x, m };
  }
  gate x != >slot_t;

  /** Enqueue a message */
  trans ack_send
  : trans send_ack
  in {
    place ack_q_empty: >slot_t;
  }
  out {
    place ack_q: { >slot_t, wr };
  };

  /** Dequeue a message */
  trans ack_recv
  : trans recv_ack
  in { place ack_q: { 0, rd }; }
  out { place ack_q_empty: 0; };

  /** Ignore a message */
  trans ack_ign
  : trans ign_ack
  in { place ack_q: { 0, rd }; }
  out { place ack_q_empty: 0; };

# ifdef TIMEOUT_SEND
  /** Allow a timeout to occur */
  trans ack_timeout
  : trans timeout
  in { place ack_l: 0; }
  out { place ack_l: 0; };
# endif // TIMEOUT_SEND
#else // LLQ
  /** Contents of the acknowledgement channel */
  place ack_channel (1) seq_t[queue w]: {};

# ifdef LOSSY
  /** Lose a message */
  trans ack_lose
  in { place ack_channel: ch; }
  out { place ack_channel: -ch; }
  gate /ch != 0;
# endif // LOSSY

  /** Enqueue a message */
  trans ack_send
  : trans send_ack
  in { place ack_channel: ch; }
  out { place ack_channel: ch + is seq_t wr; }
  gate %ch != 0;

  /** Dequeue a message */
  trans ack_recv
  : trans recv_ack
  in { place ack_channel: ch; }
  { seq_t rd = *ch; }
  out { place ack_channel: -ch; }
  gate /ch != 0;

  /** Ignore a message */
  trans ack_ign
  : trans ign_ack
  in { place ack_channel: ch; }
  { seq_t rd = *ch; }
  out { place ack_channel: -ch; }
  gate /ch != 0;

# ifdef TIMEOUT_RECV
  /** Allow a timeout to occur */
  trans ack_timeout
  : trans timeout
  in { place ack_channel: {}; }
  out { place ack_channel: {}; };
# endif // TIMEOUT_RECV
#endif // LLQ

};

/** The sender */
subnet tx
{
  /** transmission window indexes */
  typedef unsigned (0..tw) twindow_t;
#ifndef NODATA
  /** transmission buffer type */
  typedef data_t[queue tw] tbuf_t;

  /** transmission buffer */
  place data (1) tbuf_t:
#ifdef PRIOP
    >tbuf_t
#else // PRIOP
    {}
#endif // PRIOP
  ;
#endif // !NODATA
  /** index of last available data item, plus one */
  place i (1) twindow_t:
#ifdef PRIOP
    >twindow_t
#else // PRIOP
    0
#endif // PRIOP
    ;
  /** index of next data item to be sent */
  place j (1) twindow_t: 0;
  /** sequence number for the first unacknowledged message */
  place seq (1) seq_t: 0;

  /** Produce data if the input buffer is not full. */
  trans produce
  in {
    place i: i;
#ifndef NODATA
    place data: d;
#endif // !NODATA
  }
  out {
#ifndef NODATA
    place data: d + data_t data!;
#endif // !NODATA
    place i: +i;
  }
#ifdef UNFOLD
  gate %d > 0, /d >= is unsigned i
#endif // UNFOLD
  gate i < tw;

  /** Send the next message from the input buffer */
  trans send_msg
#ifdef PRIOP
  { twindow_t i = tw; }
#endif // PRIOP
  in {
    place i: i;
    place j: j;
    place seq: k;
#ifndef NODATA
    place data: data;
#endif // !NODATA
  }
  {
    channel_t wr = {
#ifndef NODATA
      *data[j],
#endif // !NODATA
      modP (j, k)
    };
  }
  : trans send_msg
  out {
    place i: i;
    place j: +j;
    place seq: k;
#ifndef NODATA
    place data: data;
#endif // !NODATA
  }
#ifdef UNFOLD
  gate /data > is unsigned j
#endif // UNFOLD
  gate j < i;

#ifdef TIMEOUT
  /** Timeout (retransmission) */
  trans timeout
# ifdef TIMEOUT_VISIBLE
  : trans timeout
# endif
# ifdef PRIOP
  { twindow_t i = tw; }
# endif // PRIOP
  in {
    place i: i;
    place j: i;
  }
# ifndef PRIOP
  gate i > 0
# endif // !PRIOP
  out {
    place i: i;
    place j: 0;
  };
#endif // TIMEOUT

  /** Receive an acknowledgement such that the sequence
   * number is within the transmission window. */
  trans recv_ack
  : trans recv_ack
#ifdef PRIOP
  { twindow_t i = tw; }
#endif // PRIOP
  in {
    place i: i;
    place j: j;
    place seq: k;
#ifndef NODATA
    place data: data;
#endif // !NODATA
  }
  { unsigned a = modM (rd, k) + 1; }
  out {
    place i: is twindow_t (is unsigned i < a ? 0 : i - a);
    place j: is twindow_t (is unsigned j < a ? 0 : j - a);
    place seq: +rd;
#ifndef NODATA
    place data: data - a;
#endif // !NODATA
  }
#ifdef UNFOLD
  gate /data >= a
#endif // UNFOLD
  gate a <= tw;

  /** Receive an acknowledgement such that the sequence
   * number is outside the transmission window. */
  trans ign_ack
  : trans ign_ack
  in {
    place seq: k;
#ifdef PRIOP
    place i: tw;
#endif // PRIOP
  }
#ifndef TIMEOUT
  // this transition should never be enabled on a reliable channel
  gate fatal
#endif // !TIMEOUT
  { unsigned a = modM (rd, k) + 1; }
  out {
    place seq: k;
#ifdef PRIOP
    place i: tw;
#endif // PRIOP
  }
  gate a > tw;
};

/** The receiver */
subnet rx
{
  /** Received sequence number */
  typedef unsigned (0..rw-1) rseq_t;
  /** Expected sequence number */
  place e (1) seq_t: 0;
#ifdef LLA
  /** Acknowledged sequence numbers */
  typedef struct {rseq_t r; bool b;} ackarray_t;
  /** Acknowledged sequence numbers */
  place ackarray (rw > 2?|#rseq_t : #rseq_t .. #rseq_t) ackarray_t:
    rseq_t r: { r, false };
  /** Flag: 0=acknowledging, 1=receiving, 2 .. rw - 1 = shifting */
  typedef unsigned (0 .. rw <= 2 ? 1 : rw - 1) recv_t;
#else // LLA
  /** Acknowledged sequence numbers */
  typedef bool[rseq_t] ackarray_t;
  /** Acknowledged sequence numbers */
  place ackarray (1) ackarray_t: <ackarray_t;
  /** Flag: false=acknowledging, 1=receiving */
  typedef unsigned (0..1) recv_t;
#endif // LLA
  /** Flag: receiving or acknowledging, or shifting */
  place recv (1) recv_t: 1;
#ifndef NODATA
  /** Received data to be forwarded to the consumer */
  typedef data_t[rseq_t] consumer_t;
  place consumer (1) consumer_t: <consumer_t;
#endif // !NODATA
  /** Receive a message */
  trans recv_msg_
  : trans recv_msg
  {
    seq_t e;
    rseq_t s = is rseq_t modM (rd.seqnum, e);
  }
  in {
    place recv: 1;
#ifndef NODATA
    place consumer: t;
#endif // !NODATA
    place e: e;
#ifdef LLA
    place ackarray: rseq_t r: { r, .b };
#else // LLA
    place ackarray: r;
#endif // LLA
  }
  out {
    place recv: 0;
#ifndef NODATA
    place consumer: t.{[s] rd.data};
#endif // !NODATA
    place e: e;
#ifdef LLA
    place ackarray: rseq_t r: { r, r == s || .b };
#else // LLA
    place ackarray: r.{[s] true};
#endif // LLA
  }
  gate modM (rd.seqnum, e) < rw;

  /** Ignore a message */
  trans ign_msg_
  : trans ign_msg
  in { place recv: 1; place e: e; } out { place recv: 0; place e: e; }
  gate modM (rd.seqnum, e) >= rw;

  /** Send an acknowledgement */
  trans send_ack_
  : trans send_ack
  in {
    place recv: 0;
    place e: e;
#ifdef LLA
    place ackarray: { 0, false };
#else // LLA
    place ackarray: r;
#endif // LLA
  }
#ifndef LLA
  gate !r[0]
#endif // !LLA
  { seq_t wr = |e; }
  out {
    place recv: 1;
    place e: e;
#ifdef LLA
    place ackarray: { 0, false };
#else // LLA
    place ackarray: r;
#endif // LLA
  };

  /** Shift the buffer of consumed data */
  trans :consume_shift
#ifndef NODATA
  in { place consumer: t; }
  out { place consumer: (t << 1).{ [rw - 1] <data_t }; }
#endif // !NODATA
  ;

#ifdef LLA
  /** Consume received data */
  trans consume_one : trans consume_shift
  gate rw == 1
  in {
    place ackarray: { 0, true };
    place e: e; place recv: 0;
  }
  out {
    place ackarray: { 0, false };
    place e: +e; place recv: 0;
  };
  trans consume_two : trans consume_shift
  gate rw == 2
  in {
    place ackarray: { 0, true }, { +<recv_t, c };
    place e: e; place recv: 0;
  }
  out {
    place ackarray: { 0, c }, { +<recv_t, false };
    place e: +e; place recv: 0;
  };
  trans consume_start : trans consume_shift
  gate rw > 2
  in {
    place ackarray: { 0, true }, { +<recv_t, c };
    place e: e; place recv: 0;
  }
  out {
    place ackarray: { 0, c };
    place e: +e; place recv: ++<recv_t;
  };
  trans consume_end
  gate rw > 2
  in {
    place ackarray: { >recv_t, c };
    place recv: >recv_t;
  }
  out {
    place ackarray: { |>recv_t, c }, { >recv_t, false };
    place recv: 0;
  };
  trans consume_mid
  gate rw > 3
  in {
    place ackarray: { x, c };
    place recv: x;
  }
  gate x > +<recv_t && x < >recv_t
  out {
    place ackarray: { |x, c };
    place recv: +x;
  };
#else // LLA
  /** Consume a message */
  trans consume : trans consume_shift
  in {
    place recv: 0;
    place ackarray: r;
    place e: e;
  }
  gate r[0]
  out {
    place recv: 0;
    place ackarray: (r << 1).{ [rw - 1] false };
    place e: +e;
  };
#endif // LLA
};

deadlock fatal;
