// TFTPReceiver.java	(C) K. J. Turner, K. A. Whyte	05/06/01

// Trivial File Transfer Protocol receiver (server)

package protocol;				// protocol package

import java.util.Vector;			// vector (list)
import support.*;				// protocol entity support

/**
  This is the class for a Trivial File Transfer Protocol sender.

  @author	Kenneth J. Turner, Kenneth A. Whyte
  @version	1.0 (5th June 2001, KAW): initial version
  <br/>		1.4 (9th March 2006, KJT): updated for JDK 1.5
  <br/>		1.5 (27th July 2010, KJT): unchecked warning suppressed
*/
public class TFTPReceiver			// protocol receiver (server)
 implements ProtocolEntity, Timeouts {		// protocol entity, timeout

  // simulator variables

  private ProtocolEntity peer;			// peer entity (client)
  private Medium medium;			// communications medium
  private String name;				// entity name

  // protocol variables

  private int pduNo;				// PDU seq number sent/expected
  private PDU pduSent;				// PDU sent
  private PDU pduReceived;			// PDU received
  private boolean timerEnabled;			// whether timer is enabled
  private float errorProb = 0.8f;		// file I/O error probability

  // protocol state

  int state;					// current protocol state

  final static int idle = 0;			// no connection
  final static int reading = 2;			// reading data
  final static int writing = 4;			// writing data
  final static int waitLast = 5;		// wait for last re-send

  // protocol messages

  final static String ack = "ACK";		// acknowledgement message type
  final static String data = "DATA";		// data message type
  final static String error = "ERROR";		// error message type
  final static String rrq = "RRQ";		// read request message type
  final static String wrq = "WRQ";		// write request messagetype

  // protocol methods

  public TFTPReceiver(Medium m, String name) { // construct receiver instance
    this.name = name;				// set protocol entity name
    medium = m;					// set underlying medium
    initialise ();				// initialise protocol
  }

  public String getName () {			// get protocol entity name
    return((name));				// return protocol entity name
  }

  public Vector<String> getServices() {		// get protocol entity services
    Vector<String> events = 			// list of events
      new Vector<String>();
    String pduType;				// received PDU type
    int pduSeq;					// received PDU seq number
    String pduData;				// PDU data

    if (pduReceived != null) {			// PDU received?
      pduType = pduReceived.type;		// get received PDU type
      if (pduType.equals(rrq) ||		// read request or ...
	  pduType.equals(wrq))			// write request?
	pduSeq = 0;				// treat as sequence number 0
      else
	pduSeq = pduReceived.seq;		// get received PDU seq number
      pduData = pduReceived.sdu;		// get received PDU data
      if (((state == idle ||			// idle or ...
	    state == reading) &&		// reading, and ...
	   pduType.equals(rrq)) ||		// read request, or ...
	  ((state == reading ||			// reading or ...
	    state == waitLast) &&		// awaiting re-send, and ...
	   pduType.equals(ack))) {		// acknowledgement?
	if (pduSeq == pduNo) {			// expected acknowledgement?
	  timerEnabled = false;			// cancel timeout
	  if (pduSent != null &&		// valid previous PDU and ...
	      pduSent.sdu.equals("Dlast"))	// last data sent?
	    initialise ();			// re-initialise protocol
	  else {				// more data to send
	    events.addElement(			// send data
	      data + "(" + (pduNo + 1) + ",D" + (pduNo + 1) + ") - send data");
	    events.addElement(			// send last data
	      data + "(" + (pduNo + 1) + ",Dlast) - send last data");
	  }
	}
	else if (pduSeq == pduNo - 1)		// repeated acknowledgement?
	  events.addElement(			// send data
	    data + "(" + pduSent.seq + "," + pduSent.sdu + ") - re-send data");
      }
      else if (((state == idle ||		// idle or ...
		 state == writing) &&		// writing, and ...
		pduType.equals(wrq)) ||		// write request, or ...
	       (state == writing &&		// writing and ...
		pduType.equals(data))) {	// data?
	if (pduSeq == pduNo ||			// expected data or ...
	    pduSeq == pduNo - 1) {		// repeated data?
	  timerEnabled = false;			// cancel timeout
	  events.addElement(			// send acknowledgement
	    ack + "(" + pduSeq + ") - send acknowledgement");
	}
      }
    }
    if (state == waitLast)			// waiting for last re-send?
      events.addElement(			// add close event
	"Close - presume all retransmissions over");
    if (state == reading &&			// reading and ...
	pduSent != null &&			// PDU was sent and ...
	!pduSent.sdu.equals("Dlast") &&		// PDU was not last and ...
	TFTPMedium.random() < errorProb)	// file read error?
	events.addElement(			// report reading error
	  error +"(data for read unavailable) - report error");
    if (timerEnabled)				// timer is enabled?
      events.addElement(			// add timeout event
	"Timeout - presume loss of message and resend");
    return(events);				// return list of events
  }

  public boolean hasTimer(String type) {	// protocol uses timer?
    return((true));				// report it does
  }

  public void initialise () {			// initialise protocol
    state = idle;				// initialise state
    pduNo = 0;					// initialise seq number
    pduReceived = null;				// initialise no PDU received
    pduSent = null;				// initialise no PDU sent
    timerEnabled = false;			// initialise no timeout
  }

  public Vector<ProtocolEvent> performService (String s) { // protocol service
    Vector<ProtocolEvent> events = 		// initialise events list
      new Vector<ProtocolEvent>();
    int start, middle, end;			// start/middle/end positions
    int pduSeq;					// PDU sequence number
    String pduData;				// PDU data

    if (s.startsWith (ack)) {			// send acknowledgement?
      start = s.indexOf ('(') + 1;		// get seq number start
      end = s.indexOf (')');			// get seq number end
      pduSeq =					// get sequence number
	Integer.parseInt(s.substring(start, end));
      if (pduReceived != null &&		// valid PDU received and ...
	  pduReceived.sdu.equals("Dlast"))	// last data?
	state = waitLast;			// wait for last re-send
      else {					// still reading
	pduNo = pduSeq + 1;			// update sequence number
	state = writing;			// (now) writing
      }
      transmitPDU(				// send acknowledgement
	new TFTPMessage(ack, pduSeq), peer);
    }
    else if (s.startsWith (data)) {		// send data?
      start = s.indexOf ('(') + 1;		// get seq number start
      middle = s.indexOf (',');			// get comma position
      end = s.indexOf (')');			// get seq number end
      pduSeq =					// get sequence number
	Integer.parseInt(s.substring(start, middle));
      pduData = s.substring(middle + 1, end);	// get data contents
      transmitPDU(				// send data
	new TFTPMessage(data, pduSeq, pduData), peer);
      pduNo = pduSeq;				// update sequence number
      state = reading;				// (now) reading
    }
    else if (s.startsWith (error)) {		// send error?
      start = s.indexOf ('(') + 1;		// get error message start
      end = s.indexOf (')');			// get error message end
      pduData = s.substring(start, end); 	// get error message
      transmitPDU(				// send error
	new TFTPMessage(error, pduData), peer);
      state = waitLast;				// wait for last re-send
    }
    else if (s.startsWith ("Close")) {		// close?
      events.addElement(			// add closed comment
	new ProtocolEvent(ProtocolEvent.COMMENT, this, "Closed"));
      initialise ();				// initialise protocol
    }
    if (s.startsWith ("Timeout")) {		// timeout?
      transmitPDU(pduSent, peer);		// re-send PDU
      events.addElement(			// add timeout event and PDU
	new ProtocolEvent(ProtocolEvent.TIMEOUT, pduSent));
    }
    else if (pduSent != null) {			// PDU to send?
      events.addElement(			// transmit PDU
	new ProtocolEvent(ProtocolEvent.TRANSMIT, pduSent));
    }
    return(events);				// return list of events
  }

  public Vector<ProtocolEvent> receivePDU(PDU pdu) { // handle received PDU
    pduReceived = pdu;				// store PDU
    if (pduReceived.type.equals(error))		// error message?
      initialise();				// re-initialise protocol
    return((new Vector<ProtocolEvent>()));	// return no events
  }

  public void setPeer(ProtocolEntity peer) {	// set protocol peer
    this.peer = peer;				// set this entity's peer
  }

  /**
    Set the timer for a specified PDU.

    @param pdu		PDU
    @param enabled	whether the timer is enabled
  */
  public void setTimer(PDU pdu, boolean enabled) { // set timer status
    if (state != idle)				// not idle?
      timerEnabled = enabled;			// store timer status
  }

  public void transmitPDU(			// transmit PDU
   PDU pdu, ProtocolEntity dest) {		// for given PDU, destination
    pdu.setSource (this);			// source is this entity
    pdu.setDestination(dest);			// destination is as given
    pduSent = pdu;				// copy PDU sent
    medium.receivePDU(pdu);			// medium receives PDU
    pduReceived = null;				// note no PDU in response
    timerEnabled = false;			// note no timeout
  }

}
