/*
* Copyright (c) 2006-2007 Graz University of Technology. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The names "Graz University of Technology" and "IAIK of Graz University of
* Technology" must not be used to endorse or promote products derived from
* this software without prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE LICENSOR BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package ejip.jtcpip;
import joprt.RtThread;
import util.Dbg;
import ejip.jtcpip.util.Debug;
import ejip.jtcpip.util.NumFunctions;
/**
* Represents the TCP transport layer. Contains methods for connection
* establishment and disposal (to the upper (application) layer), and TCP Packet
* sending/receiving (to the lower (network) layer).
*
* @see ejip.jtcpip.IP
*
* @author Tobias Kellner
* @author Ulrich Feichter
* @author Christof Rath
* @version $Rev: 999 $ $Date: 2007/01/24 19:37:07 $
*/
public class TCP
{
/** Initial window size */
static short initialWindow = StackParameters.TCP_INITIAL_WINDOW_SIZE;
//*************************** FIRST SOME COMMON METHODS **********************
/**
* Get an initial randomly generated sequence number.
*
* @return the new initial sequence number
*/
synchronized private static int getSeqStart()
{
return NumFunctions.rand.nextInt();
}
/**
* Creates a basic TCP packet by allocating a Payload. All header bits are
* set to 0, except the data offset which will be set correctly. If no
* packet can be allocated, null is returned.
* Option length is assumed to be 0.
*
* @return The Payload of the new Packet (or null)
*/
protected static Payload createEmptyPayload()
{
return createEmptyPayload(0);
}
/**
* Creates a basic TCP packet by allocating a Payload. All header bits are
* set to 0, except the data offset which will be set correctly. If no
* packet can be allocated, null is returned.
* NOTE: Not used at the moment (only by {@link #createEmptyPayload()}.
*
* @param optionLength
* Length of the option data (in units of 4 bytes)
* @return The Payload of the new Packet (or null)
*/
protected static Payload createEmptyPayload(int optionLength)
{
Payload pay = Payload.newPayload();
if (pay == null)
return null;
// set whole tcp header to 0
for (int i = 0; i < 5 + optionLength; i++)
pay.payload[i] = 0;
TCPPacket.setDataOffset(pay, (byte) (5 + optionLength));
pay.length = (5 + optionLength) * 4;
return pay;
}
/**
* Create an empty TCP segment from a given {@link Payload}. All header
* bits are set to 0, except the data offset which will be set correctly.
* Option length is assumed to be 0.
*
* @param pay
* The Payload
*/
private static void emptyPayload(Payload pay)
{
emptyPayload(pay, 0);
}
/**
* Create an empty TCP segment from a given {@link Payload}. All header
* bits are set to 0, except the data offset which will be set correctly.
* NOTE: Not used at the moment (only by {@link #emptyPayload(Payload)}.
*
* @param pay
* The Payload
* @param optionLength
* Length of the option data (in units of 4 bytes)
*/
private static void emptyPayload(Payload pay, int optionLength)
{
// set whole tcp header to 0
for (int i = 0; i < 5 + optionLength; i++)
pay.payload[i] = 0;
TCPPacket.setDataOffset(pay, (byte) (5 + optionLength));
pay.reassembledBitMap.clearBitmap();
pay.length = (5 + optionLength) * 4;
}
/**
* Calculates the segment length of a given Payload, counting also the Syn
* or Fin flag
*
* @param pay
* the given Payload
* @return positive integer with the length in octets
*/
protected static int calculateSegmentLength(Payload pay)
{
int length = TCPPacket.getDataLength(pay);
if (TCPPacket.isFINFlagSet(pay))
length++;
if (TCPPacket.isSYNFlagSet(pay))
length++;
return length;
}
/**
* Attaches some data from the send buffer to the given <code>Payload</code>.
* This is done until the maximum segment size is reached, the<code>Payload</code>
* or the sender Window is full or the user's OutputStream is empty.
* Also the new Payload length will be set corectly.
*
* @param conn
* the <code>TCPConnection</code>
* @param pay
* the <code>Payload</code>
*/
private static void attachDataToPayload(TCPConnection conn, Payload pay)
{
if (conn.oStream.isNoMoreDataToRead())
return;
int nrToCopy = Math.min(StackParameters.PAYLOAD_MAX_DATA_SIZE - TCPPacket.getDataOffset(pay) * 4, conn.maxSndSegSize);
int openSendWindow = getRemainingSendWindow(conn);
// assert openSendWindow >= 0; //Overflow? no, no not possible i hope
if (openSendWindow ==0)
return;
nrToCopy = Math.min(nrToCopy, openSendWindow);
int dataCount;
int readInt = 0;
for (dataCount = TCPPacket.getDataOffset(pay) * 4; dataCount < nrToCopy + TCPPacket.getDataOffset(pay) * 4; dataCount++)
// copy the buffer to the payload until payload is full!
{
readInt = conn.oStream.read();
if (readInt == -1)
break;
pay.payload[dataCount / 4] = pay.payload[dataCount / 4] << 8 | (readInt & 0xFF);
}
// pad the last integer with zeros
if ((dataCount) % 4 == 1)
pay.payload[dataCount / 4] = (pay.payload[dataCount / 4] << 24) & 0xFF000000;
if ((dataCount) % 4 == 2)
pay.payload[dataCount / 4] = (pay.payload[dataCount / 4] << 16) & 0xFFFF0000;
if ((dataCount) % 4 == 3)
pay.payload[dataCount / 4] = (pay.payload[dataCount / 4] << 8) & 0xFFFFFF00;
pay.length = dataCount;
}
/**
* Reads out the data stored in a <code>Payload</code> and stores it in the connection's
* <code>TCPinputStream</code>.
*
* @param conn
* the <code>TCPConnection</code> containing the stream
* @param pay
* the Payload
* @param offset
* the offset at which to start reading within the payload
* @return how many bytes were read out including SYN and FIN flags!!
*/
private static int readOutPayloadData(TCPConnection conn, Payload pay, int offset)
{
int bytesWritten = 0;
if (TCPPacket.getDataLength(pay) > 0)
{
byte data;
for (int i = TCPPacket.getDataOffset(pay) * 4 + offset; i < pay.length; i++)
{
data = (byte) ((pay.payload[i / 4] >>> (3 - i % 4) * 8) & 0xFF);
if (conn.iStream.write(data) < 0)
break;
bytesWritten++;
}
//FIXME does nothing for SingleThread Version
conn.iStream.wakeUpRead();
}
if (Debug.enabled)
if (bytesWritten == TCPPacket.getDataLength(pay))
Debug.println("All data was read out from packet", Debug.DBG_TCP);
//assert bytesWritten <= TCPPacket.getDataLength(pay);
// count fin after all data in the packet was acked
if (bytesWritten == TCPPacket.getDataLength(pay) && TCPPacket.isFINFlagSet(pay))
{
bytesWritten++;
}
// syn is counted before data is recieved
if (TCPPacket.isSYNFlagSet(pay) && offset == 0)
{
bytesWritten++;
}
if (Debug.enabled)
Debug.println("Stored bytes", Debug.DBG_TCP);
return bytesWritten;
}
//************************* METHODS FOR SEGMENT RECEIVING ***********************
//--------------------------- first the heping methods --------------------------
/**
* Receive a packet from the lower (network) Layer.
* This method checks if a TCP segment is valid, and if it can processed by a
* TCPConnection in the pool. If so the method processes it depending
* on the state.
* If the segment is not acceptable a reset response will be created.
*
* @param pay
* The packet content
* @see ejip.jtcpip.IP#handlePayload
*/
synchronized public static void receivePayload(Payload pay)
{
if (Debug.enabled)
Debug.println("------------------NEW SEGMENT---------------------", Debug.DBG_TCP);
if (!TCPPacket.isChecksumValid(pay))
{
if (Debug.enabled)
Debug.println("Checksum not valid", Debug.DBG_TCP);
Payload.freePayload(pay);
return;
}
TCPConnection conn = TCPConnection.getConnection(TCPPacket.getDestPort(pay), IPPacket
.getSrcAddr(pay), TCPPacket.getSourcePort(pay));
if (conn == null)
{
if (Debug.enabled)
Debug.println("No matching connection found", Debug.DBG_TCP);
if (!TCPPacket.isRSTFlagSet(pay))
sendBackReset(pay);
else
Payload.freePayload(pay);
return;
}
if (Debug.enabled)
Debug.println("Matching connection found", Debug.DBG_TCP);
pay.conn = conn;
conn.timeLastRemoteActivity = (int) (System.currentTimeMillis() & 0xFFFFFFFF);
switch (conn.getState())
{
case TCPConnection.STATE_LISTEN:
establishConnectionPassive1(conn, pay);
break;
case TCPConnection.STATE_SYN_SENT:
establishConnectionActive(conn, pay);
break;
case TCPConnection.STATE_SYN_RCVD:
establishConnectionPassive2(conn, pay);
break;
case TCPConnection.STATE_ESTABLISHED:
handleEstablishedState(conn, pay);
break;
case TCPConnection.STATE_FIN_WAIT_1:
closeConnectionActive1(conn, pay);
break;
case TCPConnection.STATE_FIN_WAIT_2:
closeConnectionActive2(conn, pay);
break;
case TCPConnection.STATE_CLOSE_WAIT:
closeConnectionPassive1(conn, pay);
break;
case TCPConnection.STATE_CLOSING:
closeConnectionActive3(conn, pay);
break;
case TCPConnection.STATE_LAST_ACK:
closeConnectionPassive2(conn, pay);
break;
case TCPConnection.STATE_TIME_WAIT:
closeConnectionActive4(conn, pay);
break;
default:
}
}
/**
* Checks if a given sequence number seqNr lies in the reciever window set
* in the passed <code>TCPConnection</code> conn.
* Used by isPacketInRecieveSpace()
*
* @param conn
* the TCPConnection
* @param seqNr
* the sequence number to check
* @return true if seqNr lies in the window, else false
*/
private static boolean isSeqNrInWindow(TCPConnection conn, int seqNr)
{
int window = conn.rcvWindow & 0xFFFF; // window must be positive
return NumFunctions.isBetweenOrEqualSmaller(conn.rcvNext, conn.rcvNext + window, seqNr);
}
/**
* Checks if a given acknowledge number ackNr lies in the sender window set
* in the passed <code>TCPConnection</code> conn.
*
* @param conn
* the TCPConnection
* @param ackNr
* acknowledge number to check
* @return true if ackNr lies in the window, else false
*/
private static boolean isAckNrInWindow(TCPConnection conn, int ackNr)
{
return NumFunctions.isBetweenOrEqualBigger(conn.sndUnack, conn.sndNext, ackNr);
}
/**
* Used to check if even a part of a given Segment lies in the reciever
* window space.
*
* @param conn
* The <code>TCPConnection</code> wich should process this Segment
* @param seqNr
* The sequence number of the segment wich should be checked
* @param segLength
* The segment length including the FIN or SYN flag
* @return true if the segment lies in the window, else false
*/
private static boolean isSegmentInReceiverWindow(TCPConnection conn, int seqNr, int segLength)
{
//assert segLength >= 0; // should not be longer than short
if (conn.rcvWindow == 0)
if (segLength > 0)
return false;
else
return (seqNr == conn.rcvNext);
else if (segLength == 0)
return isSeqNrInWindow(conn, seqNr);
else
return isSeqNrInWindow(conn, seqNr) || isSeqNrInWindow(conn, seqNr + segLength - 1);
}
/**
* Checks if a segment pay is acceptable checking the sequence number.
* It returns true if a part of the recieved segment is in the reciever
* window, else it returns false. (see RFC 793 pg.69)
*
* @param conn
* the connection from which the bounds shall be taken
* @param pay
* <code>Payload</code> containing the segment
*
* @return true if accptable, else false
*/
private static boolean isSeqAcceptable(TCPConnection conn, Payload pay)
{
if (!isSegmentInReceiverWindow(conn, TCPPacket.getSeqNr(pay), calculateSegmentLength(pay)))
{
if (Debug.enabled)
{
Debug.println("ERROR: Segment not in window", Debug.DBG_TCP);
// Debug.println("Values: rcvNext: " + Debug.intToHexString(conn.rcvNext) + " seqNr: "
// + Debug.intToHexString(TCPPacket.getSeqNr(pay)) + " segment length: "
// + calculateSegmentLength(pay) + " window: " + (int) (conn.rcvWindow & 0xFFFF),
// Debug.DBG_TCP);
}
// sending a ack so that reciever knows wich numbers we are using
if (TCPPacket.isRSTFlagSet(pay))
{
Payload.freePayload(pay);
return false;
}
emptyPayload(pay);
sendEmptyPacket(conn, pay); // send back packet with correct seq and
// ack
return false;
}
return true;
}
/**
* Returns the size of the remaining send window. That's the window into
* which we can send data to the remote host.
*
* @param conn
* the <code>TCPConnection</code> from which the window boundaries
* shall be taken.
* @return the remaining send window size
*/
private static int getRemainingSendWindow(TCPConnection conn)
{
return NumFunctions.calcDiffWithOverflow(conn.sndUnack + ((int) conn.sndWindow & 0xFFFF),
conn.sndNext);
}
/**
* Process a valid FIN flag for all states.
*
* @param conn the connection
* @return whether sendAck has to be set
*/
private static boolean processFIN(TCPConnection conn)
{
boolean setSendAck = false;
// Signal user "connection closing"
switch (conn.getState())
{
case TCPConnection.STATE_SYN_RCVD:
// fall through
case TCPConnection.STATE_ESTABLISHED:
conn.setState(TCPConnection.STATE_CLOSE_WAIT);
if (Debug.enabled)
Debug.println("Entering state: CLOSE WAIT", Debug.DBG_TCP);
conn.oStream.close();
conn.iStream.close(); // neverthless user can read out all data that was stored until now
if (conn.flushAndClose)
{
// user called close() -> make sure we enter sendPackets
setSendAck = true;
}
break;
case TCPConnection.STATE_FIN_WAIT_1:
if (conn.sndNext == conn.sndUnack && conn.oStream.isNoMoreDataToRead()) // FIN ACKed
{ // never reached
conn.setState(TCPConnection.STATE_TIME_WAIT);
if (Debug.enabled)
Debug.println("Entering state: TIME WAIT", Debug.DBG_TCP);
}
else
{
conn.setState(TCPConnection.STATE_CLOSING);
if (Debug.enabled)
Debug.println("Entering state: CLOSING", Debug.DBG_TCP);
}
break;
case TCPConnection.STATE_FIN_WAIT_2:
conn.setState(TCPConnection.STATE_TIME_WAIT);
if (Debug.enabled)
Debug.println("Entering state: TIME WAIT", Debug.DBG_TCP);
break;
}
return setSendAck;
}
/**
* Processes data for established state starting checking the urg bit (RFC,
* pg 73) {@link #isSeqAcceptable} must be checked before!
* Here also Segments who lie in the window but are not the next awaited
* segment are stored for later. If now the next awaited segment is received
* also the previously stored "future segments" are processed.
* If the FIN flag is set in the <code>Payload</code> it will be processed
* calling <code>processFIN()</code>
* TODO: Send collective acks
*
* @param conn
* <code>TCPConnection</code> for which the segment was sent.
* @param pay
* <code>Payload</code> containing the data
*/
private static void receivePayload(TCPConnection conn, Payload pay)
{
if (TCPPacket.isURGFlagSet(pay))
{
// TODO: switch to urgent mode!
}
boolean sendAck = false; // to not get acked acks
int bytesRead = 0;
int expSeqPay = conn.rcvNext;
// We assume that the sequence number was checked correctly before with
// isSeqAcceptable - so the packet lies within the receiver window
if (!Payload.isSeqNrInPayload(expSeqPay, pay))
{
// Store segment for later use
if (Debug.enabled)
Debug.println("Storing segment for later", Debug.DBG_TCP);
pay.setStatus(Payload.PAYLOAD_WND_RX, 0);
pay = null;
}
else
{
int seqPay;
int offset;
int currentBytesRead;
Payload lastPay = null;
do
{
// lastPay = null the first time around
Payload.freePayload(lastPay);
seqPay = TCPPacket.getSeqNr(pay);
offset = NumFunctions.calcDiffWithOverflow(expSeqPay, seqPay);
// assert (offset >= 0); // Happens only when there was an overflow,
// so that means offset > MAX_INT.
// Can't happen because segment size = short
currentBytesRead = readOutPayloadData(conn, pay, offset);
bytesRead += currentBytesRead;
expSeqPay += currentBytesRead;
//Check if everything could be read out.
//calculateSegmentLength returns the whole Payload length,
//so we have to substract the offset, and additionally
//substract 1 if the SYN flag was set and the offset
//was > 0, because calculateSegmentLength counted it.
int tmp = offset;
if(offset > 0 && TCPPacket.isSYNFlagSet(pay))
tmp++;
if (currentBytesRead != (calculateSegmentLength(pay) - tmp))
{
if (Debug.enabled)
Debug.println("Emergency: Out of rcv buffer - setting window to 0", Debug.DBG_TCP);
conn.rcvWindow = 0;
sendAck = true;
lastPay = pay;
break;
}
//here we can be sure that the whole payload was read out, so also the FIN can
//be processed
//check FIN flag
if (TCPPacket.isFINFlagSet(pay))
if (processFIN(conn))
sendAck = true;
lastPay = pay;
pay = Payload.findPayload(conn, expSeqPay);
} while (pay != null);
pay = lastPay; // pay is now the last payload of all processed ones
if (bytesRead > 0)
{
conn.rcvNext += bytesRead; // just buffered bytes will be
// acknowledged
sendAck = true; // TODO: We should receive a whole Window before
// acking
// -> don't set ACK every time
}
}
if ((conn.oStream.isNoMoreDataToRead() || getRemainingSendWindow(conn) == 0) && !sendAck)
// dont send a packet if nothing is to ack and no data is available
{
if (Debug.enabled)
Debug.println("Packet must not be acked, no new data to send => dont send anything", Debug.DBG_TCP);
if (pay != null)
Payload.freePayload(pay);
return;
}
if (pay == null)
pay = createEmptyPayload();
else
emptyPayload(pay);
//FIXME not thread safe
NwLoopThread.conn = conn;
NwLoopThread.pay = pay;
//sendPackets(conn, pay);
}
/**
* Is used in {@link #handleAck(TCPConnection, Payload)} (Hendl-Eck :) to update
* the window information sent by the remote host.
* @param conn
* The connection
* @param pay
* The segment
*/
private static void handleSenderWindow(TCPConnection conn, Payload pay)
{
// check if segment is valid for window update (see RFC)
if (conn.sndWndLastUpdateSeq < TCPPacket.getSeqNr(pay) ||
(conn.sndWndLastUpdateSeq == TCPPacket.getSeqNr(pay) ||
conn.sndWndLastUpdateAck <= TCPPacket.getAckNr(pay)))
{
conn.sndWindow = TCPPacket.getWindow(pay);
// if now sndNext is bigger than sndUnack + snd.Window set it
// smaller
if (NumFunctions.isBetweenOrEqualSmaller(conn.sndUnack, conn.sndNext, conn.sndUnack
+ (int) (conn.sndWindow & 0xFFFF)))
{
if (Debug.enabled)
Debug.println("remote window was reduced, starting retransmission", Debug.DBG_TCP);
// simply start retransmission, easier do handle :)
// TODO: set sndNext = sndUnack + conn.sndWindow and the pointer in iStream to the
// right values
conn.sndNext = TCPPacket.getAckNr(pay);
if(!conn.oStream.isBufferEmpty())
conn.oStream.setPtrForRetransmit();
}
}
}
/**
* Handles the acknowledge flag and number for the established state.
* It may also be used in FIN WAIT 1, FIN_WAIT_2, CLOSE and CLOSE_WAIT.
* Window updates by the remote side are also handled here.
*
* @param conn
* the connection
* @param pay
* the payload
* @return true if no error occured (and packet can be further processed)
*/
private static boolean handleAck(TCPConnection conn, Payload pay)
{
// Hendl-Eck :)
if (!TCPPacket.isACKFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: ACK not set, dropping", Debug.DBG_TCP);
Payload.freePayload(pay);
return false;
}
if (conn.sndUnack == TCPPacket.getAckNr(pay))
{
if (Debug.enabled)
Debug.println("acked an other time", Debug.DBG_TCP);
conn.sndUnackTime = (int) (System.currentTimeMillis() & 0xFFFFFFFF);
handleSenderWindow(conn, pay);
}
else if (isAckNrInWindow(conn, TCPPacket.getAckNr(pay))) // is ack awaited?
{
// check if send window can be updated (RFC793 pg.72)
int oldSndUnack = conn.sndUnack;
conn.sndUnack = TCPPacket.getAckNr(pay);
conn.sndUnackTime = (int) (System.currentTimeMillis() & 0xFFFFFFFF);
conn.numRetransmissions = 0; // if we were in a retransmission, now it was acked
int difference = NumFunctions.calcDiffWithOverflow(conn.sndUnack, oldSndUnack);
// assert difference > 0; // unack time whould be wrong if equal 0
if (Debug.enabled)
Debug.println("Some data was acked",Debug.DBG_TCP);
// check if a syn or a fin were sent before to dont ack this
// sequence numbers in the oStream
if (conn.synToSend)
if (NumFunctions.isBetweenOrEqualBigger(oldSndUnack, conn.sndUnack, conn.synToSendSeq))
{
difference--;
conn.synToSend = false;
}
if (conn.finToSend)
if (NumFunctions.isBetweenOrEqualBigger(oldSndUnack, conn.sndUnack, conn.finToSendSeq))
{
difference--;
conn.finToSend = false;
}
conn.oStream.ackData(difference);
handleSenderWindow(conn, pay);
return true;
}
else if (NumFunctions.isBetween(conn.sndUnack, conn.sndNext, TCPPacket.getAckNr(pay))
// ACK Nr between unack + next -> error
|| (NumFunctions.unsignedGt(NumFunctions.calcDiffWithOverflow(conn.sndUnack, TCPPacket
.getAckNr(pay)), NumFunctions.calcDiffWithOverflow(TCPPacket.getAckNr(pay),
conn.sndNext)) == 1) // ACK Nr closer to next -> error
)
// TCPPacket.getAckNr(pay) > sndUnack
{
if (Debug.enabled)
Debug.println("ERROR: Unexpected Ack", Debug.DBG_TCP);
emptyPayload(pay);
sendEmptyPacket(conn, pay);
return false;
}
return true;
}
/**
* Searches for the MSS option in the tcp header. If MSS is set correctly
* the value will be stored in <code>conn.maxSndSegSize</code>
*
* @param conn
* <code>TCPConnection</code> to which the Segment was adressed
* @param pay
* <code>Payload</code> to check
*/
private static void checkAndHandleMSS(TCPConnection conn, Payload pay)
{
if (TCPPacket.getDataOffset(pay) <= 5)
return;
int mss = -1;
int i = 20;
while (i < (TCPPacket.getDataOffset(pay) * 4))
{
byte processedByte = (byte) ((pay.payload[i / 4] >>> (3 - i % 4) * 8) & 0xFF);
switch (processedByte)
{
case 0x00: // End of option list
return;
case 0x01: // NOP
i++;
continue;
case 0x02: // MSS
if ((byte) ((pay.payload[(i + 1) / 4] >>> ((3 - (i + 1) % 4) * 8)) & 0xFF) == 0x04)
{
mss = 0;
mss = (pay.payload[(i + 2) / 4] >>> ((3 - (i + 2) % 4) * 8)) & 0xFF;
mss = (mss << 8) | (pay.payload[(i + 3) / 4] >>> ((3 - (i + 3) % 4) * 8))
& 0xFF;
break;
}
else
return; // error in tcp header
default: // read out the length of the other options and
// increment i
i += (pay.payload[(i + 1) / 4] >>> ((3 - (i + 1) % 4) * 8)) & 0xFF;
continue;
}
break;
}
if (mss != -1)
{
conn.maxSndSegSize = mss;
if (Debug.enabled)
Debug.println("MSS received", Debug.DBG_TCP);
}
}
/**
* Used in <code>establishConnectionActive1()</code> to check if the acknowledge is
* acceptable. It checks if the recieved Ack number is between the initial
* sequence number (ISS) and the next sequence number to be sent (SND.NXT).
* It takes care of java's always signed interpreted datatypes and overflows.
*
* @param conn
* <code>TCPConnection</code> from which the boundaries are taken
* @param ackNr
* Acknowledge number to be checked
* @return whether the ACK is acceptable
*/
private static boolean isFirstAckAcceptable(TCPConnection conn, int ackNr)
{
return NumFunctions.isBetweenOrEqualBigger(conn.initialSeqNr, conn.sndNext, ackNr);
}
// ------------------------------ the receive methods -----------------------------
/**
* Method which is invoked when a segment arrives in state LISTEN. It looks
* for a Packet with the SYN flag set, and if such a Packet arrives a
* SYN-ACK is sent back and the state is switched then to SYN_RECIEVED. The
* behaviour on other setted flags is implementet according RFC 793
*
* @param conn
* <code>TCPConnection</code> for who the packet was sent
* @param pay
* <code>Payload</code> which contains the segment
*/
private static void establishConnectionPassive1(TCPConnection conn, Payload pay)
{
// STATE_LISTEN
if (TCPPacket.isRSTFlagSet(pay))
{
Payload.freePayload(pay);
return;
}
if (TCPPacket.isACKFlagSet(pay))
{
sendBackReset(pay);
return;
}
if (!TCPPacket.isSYNFlagSet(pay))
{
Payload.freePayload(pay);
return;
}
int seqStart = getSeqStart();
// TODO: check priority security and so on... not so important (see RFC)
conn.remoteIP = IPPacket.getSrcAddr(pay);
conn.remotePort = TCPPacket.getSourcePort(pay);
conn.rcvNext = TCPPacket.getSeqNr(pay) + 1;
conn.initialRemoteSeqNr = TCPPacket.getSeqNr(pay);
conn.sndNext = seqStart;
conn.sndUnack = seqStart;
conn.sndUnackTime = (int) (System.currentTimeMillis() & 0xFFFFFFFF);
conn.rcvWindow = initialWindow;
conn.sndWindow = TCPPacket.getWindow(pay);
checkAndHandleMSS(conn, pay);
emptyPayload(pay);
TCPPacket.setSYNFlag(pay);
conn.synToSend = true;
conn.synToSendSeq = conn.sndNext + 1;
TCPPacket.setMMS(pay);
sendEmptyPacket(conn, pay);
if (Debug.enabled)
Debug.println("Entering state: SYN_RCVD", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_SYN_RCVD);
}
/**
* Method which is invoken when a segment arrives in state SYN_RECIEVED It
* looks for the acknowledge of the previous sent SYN-ACK. If such a
* acknowledge arrives the 3-way-handshake is finished and the state is
* switched to ESTABLISHED. In this method also the blocking listen method
* will be woken up if the connection was established or a error occured.
* If some data was sent with the segment it is processed and new data
* is sent if available.
* The behaviour on other setted flags and theb check of Syn and Ack
* numbers is implementet according RFC 793.
*
* @param conn
* <code>TCPConnection</code> for who the packet was sent
* @param pay
* <code>Payload</code> which contains the segment
*/
private static void establishConnectionPassive2(TCPConnection conn, Payload pay)
{
// SYN RECIEVED STATE
if (!isSeqAcceptable(conn, pay))
return;
if (TCPPacket.isRSTFlagSet(pay))
{
if (conn.getPreviousState() == TCPConnection.STATE_SYN_SENT)
{
if (Debug.enabled)
Debug.println("ERROR: RST set, closing connection", Debug.DBG_TCP);
conn.abort();
}
else if (conn.getPreviousState() == TCPConnection.STATE_LISTEN)
{
if (Debug.enabled)
Debug.println("ERROR: RST set, returning to state LISTEN", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_LISTEN);
}
Payload.freePayload(pay);
return;
}
// if (TCPPacket.isSYNFlagSet(pay))
// {
// if (Debug.enabled)
// Debug.println("ERROR: SYN Flag set! sending back a reset", Debug.DBG_TCP);
// sendBackReset(pay);
// conn.abort();
// if (conn.getPreviousState() == TCPConnection.STATE_LISTEN)
// // Wake up listening connections
// synchronized (conn)
// {
// conn.notifyAll();
// }
// return;
// }
if (!TCPPacket.isACKFlagSet(pay))
{
Payload.freePayload(pay);
return;
}
if (!isAckNrInWindow(conn, TCPPacket.getAckNr(pay)))
{
sendBackReset(pay);
return;
}
handleAck(conn, pay);
conn.sndWindow = TCPPacket.getWindow(pay);
conn.sndWndLastUpdateSeq = TCPPacket.getSeqNr(pay);
conn.sndWndLastUpdateAck = TCPPacket.getAckNr(pay);
if (Debug.enabled)
Debug.println("Entering state: ESTABLISHED", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_ESTABLISHED);
//FIXME only one connection for now
// Wake up listening connections
// synchronized (conn)
// {
// conn.notifyAll();
// }
receivePayload(conn, pay);
}
/**
* Is invoked if a segment arrives in SYN_SENT state. Here the tree way
* handshake is finished if a valid SYN-ACK for the previous sent SYN
* arrives. If so a acknowledge will be sent, and the state is switched to
* ESTABLISHED. If the segments contains data it will be stored, and also
* userdata will be sent piggybacked with the acknowledge.
* The behaviour on other setted flags and the check of Syn and
* Ack numbers is implementet according RFC 793
*
* @param conn
* <code>TCPConnection</code> for who the packet was sent
* @param pay
* <code>Payload</code> which contains the segment
*/
private static void establishConnectionActive(TCPConnection conn, Payload pay)
{
// SYN SENT STATE
if (!TCPPacket.isACKFlagSet(pay))
{
sendBackReset(pay);
return;
}
else
// Ack set
{
if (!isFirstAckAcceptable(conn, TCPPacket.getAckNr(pay)))
{
if (Debug.enabled)
Debug.println("ERROR: Ack is not acceptable", Debug.DBG_TCP);
if (TCPPacket.isRSTFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: RST set, closing connection", Debug.DBG_TCP);
conn.abort();
Payload.freePayload(pay);
return;
}
sendBackReset(pay);
return;
}
}
conn.synToSend = false; // now the syn is acked
if (TCPPacket.isRSTFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: Connection reset by other side, closing connection", Debug.DBG_TCP);
conn.abort();
Payload.freePayload(pay);
return;
}
// TODO: check security and precedence
if (!TCPPacket.isSYNFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: SYN not set, dropping packet", Debug.DBG_TCP);
Payload.freePayload(pay);
return;
}
checkAndHandleMSS(conn, pay);
conn.rcvNext = TCPPacket.getSeqNr(pay);
conn.sndWindow = TCPPacket.getWindow(pay);
conn.sndWndLastUpdateSeq = TCPPacket.getSeqNr(pay);
conn.sndWndLastUpdateAck = TCPPacket.getAckNr(pay);
conn.sndUnack = TCPPacket.getAckNr(pay);
conn.sndUnackTime = (int) (System.currentTimeMillis() & 0xFFFFFFFF);
if (Debug.enabled)
Debug.println("Entering state: ESTABLISHED", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_ESTABLISHED);
receivePayload(conn, pay);
}
/**
* Method which is invoken when a segment arrives in state ESTABLISHED. After some
* initial parameter checks on the received segment <code>receiveAndSendData()</code>
* will do the most of the work
*
* @param conn
* <code>TCPConnection</code> for who the packet was sent
* @param pay
* <code>Payload</code> which contains the segment
*/
private static void handleEstablishedState(TCPConnection conn, Payload pay)
{
// STATE_ESTABLISHED
if (!isSeqAcceptable(conn, pay))
return;
if (TCPPacket.isRSTFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: RESET Flag set!, closing connection", Debug.DBG_TCP);
// inform user
conn.abort();
Payload.freePayload(pay);
return;
}
if (TCPPacket.isSYNFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: SYN Flag set! closing connection", Debug.DBG_TCP);
// inform user
conn.abort();
Payload.freePayload(pay);
return;
}
if (!handleAck(conn, pay))
return;
receivePayload(conn, pay);
}
/**
* Receive a segment in STATE_FIN_WAIT_1. Method is invoked after user sent a
* CLOSE call, and the other sends something after our FIN Packet. If data comes
* it will be processed. If finally our FIN is acknowledged the the state will
* be switched to STATE_FIN_WAIT_2.
* In this state we will not send more data, just retransmissions if needed
*
* @param conn
* The connection
* @param pay
* The payload
*/
private static void closeConnectionActive1(TCPConnection conn, Payload pay)
{
// STATE_FIN_WAIT_1
if (!isSeqAcceptable(conn, pay))
return;
if (TCPPacket.isRSTFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: RESET Flag set!", Debug.DBG_TCP);
// inform user
TCPConnection.deleteConnection(conn);
Payload.freePayload(pay);
return;
}
if (TCPPacket.isSYNFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: SYN Flag set!", Debug.DBG_TCP);
TCPConnection.deleteConnection(conn);
Payload.freePayload(pay);
return;
}
if (!handleAck(conn, pay))
return;
if (conn.sndNext == conn.sndUnack && conn.oStream.isNoMoreDataToRead()) // FIN ACKed
{
if (Debug.enabled)
Debug.println("Entering state: FIN WAIT 2", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_FIN_WAIT_2);
}
receivePayload(conn, pay);
}
/**
* Handles the active closing of a connection in state FIN_WAIT_2. Does just
* the same as in ESTABLISHED. Since user called close the output stream is
* closed, so no new data can't be sent, also retransmissions are not done because
* the other side has just acknowledged our FIN which is (hopefully) the last sequence
* number of the data sent by us.
* We are just waiting for the remote FIN
*
* @param conn
* The Connection
* @param pay
* The Payload
*/
private static void closeConnectionActive2(TCPConnection conn, Payload pay)
{
// STATE_FIN_WAIT_2
// do the same as in ESTABLISHED
handleEstablishedState(conn, pay);
}
/**
* Handles a segment received in state CLOSING.
* Closing is reached if the remote
* side sent a FIN before acking our FIN. Here we wait for the ACK of our FIN and
* switch to TIME WAIT if we received it.
*
* @param conn
* The Connection
* @param pay
* The Payload
*/
private static void closeConnectionActive3(TCPConnection conn, Payload pay)
{
// STATE_CLOSING
if (!isSeqAcceptable(conn, pay))
return;
if (TCPPacket.isRSTFlagSet(pay))
{
if (Debug.enabled)
Debug.println("RESET Flag set, closing the connection", Debug.DBG_TCP);
TCPConnection.deleteConnection(conn);
Payload.freePayload(pay);
return;
}
if (TCPPacket.isSYNFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: SYN Flag set!", Debug.DBG_TCP);
TCPConnection.deleteConnection(conn);
Payload.freePayload(pay);
return;
}
if (!TCPPacket.isACKFlagSet(pay))
{
Payload.freePayload(pay);
return;
}
if (conn.sndNext == conn.sndUnack && conn.oStream.isNoMoreDataToRead()) // FIN ACKed
{
if (Debug.enabled)
Debug.println("Entering state: TIME WAIT", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_TIME_WAIT);
}
receivePayload(conn, pay);
}
/**
* Handles a segment received in state TIME WAIT.
* This just means that we dropp every segment, except the RST or
* SYN flag are set:
* if this is the case the connection will be closed immediately.
*
* @param conn
* The Connection
* @param pay
* The Payload
*/
private static void closeConnectionActive4(TCPConnection conn, Payload pay)
{
// STATE_TIME_WAIT
if (!isSeqAcceptable(conn, pay))
return;
else if (TCPPacket.isRSTFlagSet(pay))
{
if (Debug.enabled)
Debug.println("RESET Flag set, closing the connection", Debug.DBG_TCP);
TCPConnection.deleteConnection(conn);
}
else if (TCPPacket.isSYNFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: SYN Flag set!", Debug.DBG_TCP);
TCPConnection.deleteConnection(conn);
}
Payload.freePayload(pay);
return;
}
/**
* Method which is invoken when a segment arrives in state CLOSE_WAIT. Here
* Data is just acknowledged but not stored! The state CLOSE_WAIT can be
* left just with an user input
*
* The behaviour on setted flags and the check of Syn and Ack numbers is
* implementet according RFC 793
*
* @param conn
* TCPConnection for who the packet was sent
* @param pay
* Payload which contains the segment
*/
private static void closeConnectionPassive1(TCPConnection conn, Payload pay)
{
// STATE_CLOSE_WAIT
if (!isSeqAcceptable(conn, pay))
return;
if (TCPPacket.isRSTFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: RST Flag set! sending back a reset", Debug.DBG_TCP);
sendBackReset(pay);
conn.abort();
return;
}
if (TCPPacket.isSYNFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: SYN Flag set! sending back a reset", Debug.DBG_TCP);
sendBackReset(pay);
conn.abort();
return;
}
if (!handleAck(conn, pay))
return;
//send FIN if user called close()
if (conn.flushAndClose)
{
emptyPayload(pay);
sendEmptyPacket(conn, pay);
return;
}
if (Debug.enabled)
Debug.println("Still waiting... no more data to read: ", Debug.DBG_TCP);
// urg must be ignored as written in rfc
// payload must also be ignored
// fin doesnt mather
Payload.freePayload(pay);
}
/**
* It is invoken if a segment arrives in the state LAST_ACK Here we wait for
* the last acknowledge for our previous sent FIN-ACK. If this acknowledge
* arrives the connection is closed. Data will not be processed any more.
*
* The behaviour on setted flags and the check of Syn and Ack numbers is
* implementet according RFC 793
*
* @param conn
* TCPConnection for who the packet was sent
* @param pay
* Payload which contains the segment
*/
private static void closeConnectionPassive2(TCPConnection conn, Payload pay)
{
// STATE_LAST_ACK
if (!isSeqAcceptable(conn, pay))
return;
if (TCPPacket.isRSTFlagSet(pay))
{
if (Debug.enabled)
Debug.println("RESET Flag set, closing the connection", Debug.DBG_TCP);
TCPConnection.deleteConnection(conn);
Payload.freePayload(pay);
return;
}
if (TCPPacket.isSYNFlagSet(pay))
{
if (Debug.enabled)
Debug.println("ERROR: SYN Flag set!", Debug.DBG_TCP);
TCPConnection.deleteConnection(conn);
Payload.freePayload(pay);
return;
}
if (!TCPPacket.isACKFlagSet(pay))
{
Payload.freePayload(pay);
return;
}
// Ack is the right ack => connection gets closed normally
if (conn.sndNext == TCPPacket.getAckNr(pay))
{
if (Debug.enabled)
Debug.println("Entering state: CLOSED", Debug.DBG_TCP);
TCPConnection.deleteConnection(conn);
Payload.freePayload(pay);
return;
}
Payload.freePayload(pay);
return;
}
//********************************* SENDING METHODES ************************
/**
* Reads the actual state out from the given connection and returns if we
* are allowed to send data in this state
* @param conn
* The connection
* @return true if data sending is allowed, else false
*/
private static boolean isDataSendingAllowedInCurrentState(TCPConnection conn)
{
switch (conn.getState())
{
case TCPConnection.STATE_ESTABLISHED:
case TCPConnection.STATE_SYN_RCVD:
case TCPConnection.STATE_CLOSING: //needed for retransmission
case TCPConnection.STATE_FIN_WAIT_1: //needed for retransmission
return true;
default:
return false;
}
}
/**
* If the maximum retranmission timeout expired we send a reset and close the
* connection. Depending on the state we can close the connection immediately
* or we have to wait for the users <code>close()</code> call to provide
* that no objects used by the user will be deleted.
*
* @param conn
* The connection
* @param pay
* The payload
*/
private static void handleExpiredRetransmissionTimeout(TCPConnection conn, Payload pay)
{
if (Debug.enabled)
Debug.println("No response from the other side, closing connection!", Debug.DBG_TCP);
TCPPacket.setRSTFlag(pay);
sendEmptyPacket(conn, pay);
switch (conn.getState())
{
case TCPConnection.STATE_SYN_SENT:
case TCPConnection.STATE_ESTABLISHED:
case TCPConnection.STATE_CLOSE_WAIT:
// user didn't close yet -> abort
conn.abort();
break;
case TCPConnection.STATE_FIN_WAIT_1:
case TCPConnection.STATE_FIN_WAIT_2:
case TCPConnection.STATE_CLOSING:
case TCPConnection.STATE_TIME_WAIT:
case TCPConnection.STATE_LAST_ACK:
// user closed already -> delete
TCPConnection.deleteConnection(conn);
break;
case TCPConnection.STATE_SYN_RCVD:
conn.abort();
if (conn.getPreviousState() == TCPConnection.STATE_LISTEN)
// Wake up listening connections
// FIXME
synchronized (conn)
{
// conn.notifyAll();
}
break;
default:
// listen / closed -> can't happen
// assert(false);
}
}
/**
* Sends one or more TCP Packets by handing them to the lower (network) layer.
* Segment will be sent until the Window of the reciever is closed, the send
* buffer is empty or we run out of payloads.
*
* @see #sendPayload(TCPConnection, Payload)
*
* @param conn
* The corresponding TCP Connection
* @param pay
* The payload of the first Packet
*/
synchronized protected static void sendPackets(TCPConnection conn, Payload pay)
{
//sendPayload(conn, pay);
//inlined--------------------------------------------------------
if (!conn.checkAndprepareRetransmission())
{
handleExpiredRetransmissionTimeout(conn, pay);
return;
}
if(isDataSendingAllowedInCurrentState(conn))
attachDataToPayload(conn, pay);
if (conn.flushAndClose)
{
// User called TCPConnection.close()
switch (conn.getState())
{
// handles SYN_RCVD too
case TCPConnection.STATE_ESTABLISHED:
if (conn.oStream.isNoMoreDataToRead())
{
conn.finToSend = true;
conn.finToSendSeq = conn.sndNext + TCP.calculateSegmentLength(pay) + 1;
if (Debug.enabled)
Debug.println("Entering state: FIN WAIT 1", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_FIN_WAIT_1);
conn.flushAndClose = false;
}
break;
case TCPConnection.STATE_CLOSE_WAIT:
conn.finToSend = true;
conn.finToSendSeq = conn.sndNext + TCP.calculateSegmentLength(pay) + 1;
if (Debug.enabled)
Debug.println("Entering state: LAST ACK", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_LAST_ACK);
conn.flushAndClose = false;
break;
default:
//do nothing
}
}
// attach Syn and Fin flag to the payload if needed
if (conn.synToSend)
{
if (NumFunctions.isBetweenOrEqualBigger(conn.sndNext, conn.sndNext
+ TCPPacket.getDataLength(pay) + 1, conn.synToSendSeq))
{
if (Debug.enabled)
if (TCPPacket.isSYNFlagSet(pay))
Debug.println("SYN set twice!", Debug.DBG_TCP);
TCPPacket.setSYNFlag(pay);
}
}
if (conn.finToSend)
{
if (NumFunctions.isBetweenOrEqualBigger(conn.sndNext, conn.sndNext
+ TCPPacket.getDataLength(pay) + 1, conn.finToSendSeq))
{
if (Debug.enabled)
if (TCPPacket.isFINFlagSet(pay))
Debug.println("FIN set twice!", Debug.DBG_TCP);
TCPPacket.setFINFlag(pay);
}
}
if (conn.sndNext == conn.sndUnack) // send new data after everything was acknowledged
conn.sndUnackTime = (int) (System.currentTimeMillis() & 0xFFFFFFFF);
if (conn.getState() == TCPConnection.STATE_SYN_SENT)
{ //to provide that the MSS is also sent if we do a retransmission
TCPPacket.setMMS(pay);
sendEmptyPacket(conn, pay, false);
}
else
sendEmptyPacket(conn, pay, true);
//inlined-----------------------end----------------------------
if(!isDataSendingAllowedInCurrentState(conn))
return;
pay = null;
if (getRemainingSendWindow(conn) > 0 && !conn.oStream.isNoMoreDataToRead())
pay = TCP.createEmptyPayload();
while (pay != null)
{
if (Debug.enabled)
Debug.println("looping in send packets", Debug.DBG_TCP);
if (!sendPayload(conn, pay))
break;
if (getRemainingSendWindow(conn) > 0 && !conn.oStream.isNoMoreDataToRead())
pay = TCP.createEmptyPayload(0);
else
pay = null;
}
}
/**
* Sends a single TCP Segment, if needet adds some data to the segment and handles
* a previous close call from the user by sending a fin. If retransmission
* id needed this will be handled in
* <code>TCPConnection.checkAndprepareRetransmission()</code> who is called.
*
* @param conn
* The corresponding TCP Connection
* @param pay
* The payload of the Packet
*
* @return returns true if sucessful, false if an error occurs (e.g.
* retransmission timeout expired)
*/
private static boolean sendPayload(TCPConnection conn, Payload pay)
{
if (!conn.checkAndprepareRetransmission())
{
handleExpiredRetransmissionTimeout(conn, pay);
return false;
}
if(isDataSendingAllowedInCurrentState(conn))
attachDataToPayload(conn, pay);
if (conn.flushAndClose)
{
// User called TCPConnection.close()
switch (conn.getState())
{
// handles SYN_RCVD too
case TCPConnection.STATE_ESTABLISHED:
if (conn.oStream.isNoMoreDataToRead())
{
conn.finToSend = true;
conn.finToSendSeq = conn.sndNext + TCP.calculateSegmentLength(pay) + 1;
if (Debug.enabled)
Debug.println("Entering state: FIN WAIT 1", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_FIN_WAIT_1);
conn.flushAndClose = false;
}
break;
case TCPConnection.STATE_CLOSE_WAIT:
conn.finToSend = true;
conn.finToSendSeq = conn.sndNext + TCP.calculateSegmentLength(pay) + 1;
if (Debug.enabled)
Debug.println("Entering state: LAST ACK", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_LAST_ACK);
conn.flushAndClose = false;
break;
default:
//do nothing
}
}
// attach Syn and Fin flag to the payload if needed
if (conn.synToSend)
{
if (NumFunctions.isBetweenOrEqualBigger(conn.sndNext, conn.sndNext
+ TCPPacket.getDataLength(pay) + 1, conn.synToSendSeq))
{
if (Debug.enabled)
if (TCPPacket.isSYNFlagSet(pay))
Debug.println("SYN set twice!", Debug.DBG_TCP);
TCPPacket.setSYNFlag(pay);
}
}
if (conn.finToSend)
{
if (NumFunctions.isBetweenOrEqualBigger(conn.sndNext, conn.sndNext
+ TCPPacket.getDataLength(pay) + 1, conn.finToSendSeq))
{
if (Debug.enabled)
if (TCPPacket.isFINFlagSet(pay))
Debug.println("FIN set twice!", Debug.DBG_TCP);
TCPPacket.setFINFlag(pay);
}
}
if (conn.sndNext == conn.sndUnack) // send new data after everything was acknowledged
conn.sndUnackTime = (int) (System.currentTimeMillis() & 0xFFFFFFFF);
if (conn.getState() == TCPConnection.STATE_SYN_SENT)
{ //to provide that the MSS is also sent if we do a retransmission
TCPPacket.setMMS(pay);
sendEmptyPacket(conn, pay, false);
}
else
sendEmptyPacket(conn, pay, true);
return true;
}
/**
* Sends a TCP Packet by handing it to the lower (network) layer.
* Just calls <code>sendEmptyPacket(conn, pay, true)</code>
*
* @see #sendEmptyPacket(TCPConnection, Payload, boolean)
*
* @param conn
* The corresponding TCP Connection
* @param pay
* The payload of the Packet
*/
private static void sendEmptyPacket(TCPConnection conn, Payload pay)
{
sendEmptyPacket(conn, pay, true);
}
/**
* Sends a TCP Packet by handing it to the lower (network) layer.
* No data will be added by this method, but data which is already
* in the <code>Payload</code> will not be touched.
* It also sets the sequence number field to <code>conn.sndNext</code>
* and if not otherwise requested also the acknowledge flag will be
* set and the acknowledge number will get the value from
* <code>conn.rcvNext</code>.
*
* @param conn
* The corresponding TCP Connection
* @param pay
* The payload of the Packet
* @param sendAck
* If set, the Ack flag will be set and the acknowledge number
* will be set correctly.
*/
private static void sendEmptyPacket(TCPConnection conn, Payload pay, boolean sendAck)
{
TCPPacket.setWindow(pay, (short) (conn.rcvWindow & 0xFFFF));
if (Debug.enabled)
Debug.println("setting Port: Src",Debug.DBG_TCP);
TCPPacket.setSourcePort(pay, conn.localPort);
TCPPacket.setDestPort(pay, conn.remotePort);
TCPPacket.setSeqNr(pay, conn.sndNext);
// if (Debug.enabled)
// {
// Debug.println("sequence number: " + conn.sndNext, Debug.DBG_TCP);
// Debug.println("data length from header: " + TCPPacket.getDataLength(pay), Debug.DBG_TCP);
// }
conn.sndNext += calculateSegmentLength(pay);
if (sendAck)
TCPPacket.setACKFlag(pay);
TCPPacket.setAckNr(pay, conn.rcvNext);
IP.asyncSendPayload(pay, conn.remoteIP, IP.PROT_TCP);
}
/**
* Takes the given segment form a reset response and sends it.
*
* @param pay
* The <code>Payload</code> containing the segment.
*/
private static void sendBackReset(Payload pay)
{
if (Debug.enabled)
Debug.println("sending reset", Debug.DBG_TCP);
short srcPort = TCPPacket.getSourcePort(pay);
short dstPort = TCPPacket.getDestPort(pay);
int srcAddr = IPPacket.getSrcAddr(pay);
if (TCPPacket.isACKFlagSet(pay))
{
int rcvAck = TCPPacket.getAckNr(pay);
emptyPayload(pay);
TCPPacket.setSeqNr(pay, rcvAck);
}
else
{
int rcvSeq = TCPPacket.getSeqNr(pay);
int rcvLength = calculateSegmentLength(pay);
emptyPayload(pay, 0);
TCPPacket.setAckNr(pay, rcvSeq + rcvLength);
TCPPacket.setACKFlag(pay);
}
TCPPacket.setRSTFlag(pay);
TCPPacket.setDestPort(pay, srcPort);
TCPPacket.setSourcePort(pay, dstPort);
IP.asyncSendPayload(pay, srcAddr, IP.PROT_TCP);
}
//*************************** METHODES FOR USER CALLS ************************
/**
* Sets the flushAndClose field of a given connection. Is mainly used to
* synchronize on TCPs monitor
*
* @param conn
* The connection object
*/
synchronized protected static void flushAndClose(TCPConnection conn)
{
conn.flushAndClose = true;
}
/**
* Establish a new TCP Connection with a specific source port. This method
* is private for now, so applications can't choose the source port
* themselves. Should this change, the source port check needs to be
* relocated.
*
* @param ip
* Int containing the remote ip
* @param port
* Remote port
* @param srcPort
* Local Port
* @return The TCP Connection Object (or null in case of failure)
*/
private static TCPConnection connect(int ip, short port, short srcPort)
{
TCPConnection conn = TCPConnection.newConnection(srcPort);
if (conn == null)
return null;
int seqStart = getSeqStart();
conn.remoteIP = ip;
conn.remotePort = port;
conn.rcvWindow = initialWindow;
conn.initialSeqNr = seqStart;
conn.sndNext = seqStart;
Payload pay;
pay = createEmptyPayload();
if (pay == null)
{
TCPConnection.deleteConnection(conn);
Payload.freePayload(pay);
return null;
}
TCPPacket.setSYNFlag(pay);
TCPPacket.setMMS(pay);
conn.synToSend = true;
conn.synToSendSeq = conn.sndNext + 1;
conn.sndUnack = seqStart;
conn.sndUnackTime = (int) (System.currentTimeMillis() & 0xFFFFFFFF);
sendEmptyPacket(conn, pay, false);
if (Debug.enabled)
Debug.println("Entering state: Syn Sent", Debug.DBG_TCP);
conn.setState(TCPConnection.STATE_SYN_SENT);
return conn;
}
/**
* Establish a new TCP Connection with the destination IP given as an int.
* This method tries to find an unused source port by looking at all
* established TCP Connections.
*
* @param ip
* Int containing the destination ip
* @param port
* Destination port
* @return The TCP Connection Object (or null in case of failure)
*/
public static TCPConnection connect(int ip, short port)
{
if (Debug.enabled)
Debug.println("Connect to " , Debug.DBG_TCP);
// Get an unused src port
return connect(ip, port, TCPConnection.newLocalPort());
}
/**
* Establish a new TCP Connection with the destination IP given as a String.
*
* @param ip
* String containing the destination ip in dotted-decimal format
* @param port
* Destination port
*
* @return The TCP Connection Object (or null in case of failure)
* @throws JtcpipException
* IP String is faulty
*/
public static TCPConnection connect(String ip, short port) throws JtcpipException
{
return connect(IP.ipStringToInt(ip), port);
}
/**
* Establish a new TCP Connection with the destination ip given as four
* bytes.
*
* @param a
* First byte of the destination IP
* @param b
* Second byte of the destination IP
* @param c
* Third byte of the destination IP
* @param d
* Fourth byte of the destination IP
* @param port
* Destination port
* @return The TCP Connection Object (or null in case of failure)
*/
public static TCPConnection connect(byte a, byte b, byte c, byte d, short port)
{
return connect(((a & 0xFF) << 24) + ((b & 0xFF) << 16) + ((c & 0xFF) << 8) + (d & 0xFF),
port);
}
/**
* Opens a listening connection on the specified port and returns the new
* connection.
* This method is blocking so it will return when the connection
* was established.
*
* @param port
* The port to listen on
* @return the new connection
*/
public static TCPConnection listen(short port, RtThread listenThread)
{
TCPConnection conn = TCPConnection.newConnection(port);
if (conn == null)
return null;
conn.setState(TCPConnection.STATE_LISTEN);
if (Debug.enabled)
Debug.println("Listening on port " , Debug.DBG_TCP);
// FIXME for jop
// synchronized (conn)
// {
// while (conn.getState() == TCPConnection.STATE_LISTEN)
// try
// {
// // conn.wait();
// } catch (InterruptedException e)
// {
// }
// }
while (conn.getState() != TCPConnection.STATE_ESTABLISHED){
listenThread.waitForNextPeriod();
}//Busy wait
return conn;
//TCPConnection.deleteConnection(conn);
//return null;
}
} // class TCP