/* * Created on Oct 27, 2004 */ package no.ntnu.fp.net.co; import java.awt.image.CropImageFilter; import java.io.EOFException; import java.io.IOException; import java.net.ConnectException; import java.net.InetAddress; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.crypto.spec.OAEPParameterSpec; import javax.sound.sampled.ReverbType; import no.ntnu.fp.net.admin.Log; import no.ntnu.fp.net.cl.ClException; import no.ntnu.fp.net.cl.ClSocket; import no.ntnu.fp.net.cl.KtnDatagram; import no.ntnu.fp.net.cl.KtnDatagram.Flag; /** * Implementation of the Connection-interface. <br> * <br> * This class implements the behaviour in the methods specified in the interface * {@link Connection} over the unreliable, connectionless network realised in * {@link ClSocket}. The base class, {@link AbstractConnection} implements some * of the functionality, leaving message passing and error handling to this * implementation. * * @author Sebj�rn Birkeland and Stein Jakob Nordb� * @see no.ntnu.fp.net.co.Connection * @see no.ntnu.fp.net.cl.ClSocket */ public class ConnectionImpl extends AbstractConnection { /** Keeps track of the used ports for each server port. */ private static Map<Integer, Boolean> usedPorts = Collections.synchronizedMap(new HashMap<Integer, Boolean>()); private KtnDatagram lastAckedPacket; private int synackseq; /** * Initialise initial sequence number and setup state machine. * * @param myPort * - the local port to associate with this connection */ public ConnectionImpl(int myPort) { //Call the abstractionConnection /* * InternalQueue * externalQueue * nextSequence number * disconnectRequest = null * lastDataPacketSent = null * lastValidPacketReceived = null * state = State.Closed * */ super(); this.myPort = myPort; this.myAddress = getIPv4Address(); this.lastAckedPacket = null; } private String getIPv4Address() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { return "127.0.0.1"; } } /** * Establish a connection to a remote location. * * @param remoteAddress * - the remote IP-address to connect to * @param remotePort * - the remote portnumber to connect to * @throws IOException * If there's an I/O error. * @throws java.net.SocketTimeoutException * If timeout expires before connection is completed. * @see Connection#connect(InetAddress, int) */ public void connect(InetAddress remoteAddress, int remotePort) throws IOException, SocketTimeoutException { // Save remote addresses on client side this.remoteAddress = remoteAddress.getHostAddress(); this.remotePort = remotePort; // Create syn packet KtnDatagram packetSent = constructInternalPacket(Flag.SYN); packetSent.setDest_port(this.remotePort); packetSent.setDest_addr(this.remoteAddress); // Reserve space for syn_ack KtnDatagram syn_ack = null; // Send until syn_ack is received while(syn_ack == null) { try { simplySendPacket(packetSent); this.state = State.SYN_SENT; } catch (ClException e) { e.printStackTrace(); } syn_ack = receiveAck(); // drop if packet is invalid or not a syn_ack if (syn_ack != null && !isValid(syn_ack) && syn_ack.getFlag() != Flag.SYN_ACK) { syn_ack = null; } } // send ack on syn_ack synackseq = nextSequenceNo; System.err.println("synackseq: " + synackseq); sendAck(syn_ack, false); this.state = State.ESTABLISHED; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } sendAck(syn_ack, false); } /** * Listen for, and accept, incoming connections. * * @return A new ConnectionImpl-object representing the new connection. * @see Connection#accept() */ public Connection accept() throws IOException, SocketTimeoutException { // Reserve space for syn packet KtnDatagram synack = null; KtnDatagram ack = null; // wait until syn is received while(synack == null) { // try to receive syn synack = receivePacket(true); // only accept valid syn packets if (synack != null && isValid(synack)) { // save address and port for response packets remoteAddress = synack.getSrc_addr(); remotePort = synack.getSrc_port(); if (synack.getFlag() == Flag.SYN) { state = State.SYN_RCVD; while(ack == null) { sendAck(synack, true); ack = receiveAck(); if (ack != null) { if (!isValid(ack) || ack.getFlag() != Flag.ACK) { ack = null; } } } this.state = State.ESTABLISHED; break; } } else { // drop invalid or not syn packet synack = null; } } lastAckedPacket = ack; lastValidPacketReceived = ack; return this; } /** * Send a message from the application. * * @param msg * - the String to be sent. * @throws ConnectException * If no connection exists. * @throws IOException * If no ACK was received. * @see AbstractConnection#sendDataPacketWithRetransmit(KtnDatagram) * @see no.ntnu.fp.net.co.Connection#send(String) */ public void send(String msg) throws ConnectException, IOException { KtnDatagram packetSend = constructDataPacket(msg); KtnDatagram packetRecv = null; while(packetRecv == null) { packetRecv = sendDataPacketWithRetransmit(packetSend); //System.err.println(packetSend.getPayload() + ": " + packetSend.getSeq_nr() + " " + packetRecv.getAck()); if (packetRecv != null) { if (!isValid(packetRecv)) { packetRecv = null; } else if (packetRecv.getAck() != packetSend.getSeq_nr()) { if (isValid(packetRecv) ) { if (packetRecv.getFlag() == Flag.SYN_ACK) { nextSequenceNo = synackseq-1; System.err.println("Reset seq: " + nextSequenceNo); sendAck(packetRecv, false); } } packetRecv = null; } } } } /** * Wait for incoming data. * * @return The received data's payload as a String. * @see Connection#receive() * @see AbstractConnection#receivePacket(boolean) * @see AbstractConnection#sendAck(KtnDatagram, boolean) */ public String receive() throws ConnectException, IOException { KtnDatagram packetRecv = null; while(packetRecv == null) { packetRecv = receivePacket(false); if (packetRecv != null) System.err.println("Recv: " + packetRecv.getSeq_nr()); if (lastAckedPacket!= null) System.err.println("Last: " + lastAckedPacket.getSeq_nr()); if (packetRecv != null) { if (!isValid(packetRecv)) { packetRecv = null; } else if (lastAckedPacket != null) { if (lastAckedPacket.getSeq_nr() >= packetRecv.getSeq_nr()) { packetRecv = null; } else if (packetRecv.getFlag() != Flag.NONE) { packetRecv = null; } } } } sendAck(packetRecv, false); lastAckedPacket = packetRecv; return packetRecv.getPayload().toString(); } /** * Close the connection. * * @see Connection#close() */ public void close() throws IOException { KtnDatagram packetSend = constructInternalPacket(Flag.FIN); KtnDatagram packetRecv = null; if (disconnectRequest == null) { while (packetRecv == null){ try { simplySendPacket(packetSend); state = State.FIN_WAIT_1; } catch (ClException e) { e.printStackTrace(); } packetRecv = receiveAck(); } state = State.FIN_WAIT_2; packetRecv = null; while (packetRecv == null) { packetRecv = receivePacket(true); } sendAck(packetRecv, false); state = State.TIME_WAIT; try { Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } } else { sendAck(disconnectRequest, false); while (packetRecv == null){ try { simplySendPacket(packetSend); state = State.LAST_ACK; packetRecv = receiveAck(); } catch (ClException e) { e.printStackTrace(); } catch (ConnectException e) { //e.printStackTrace(); } } } state = State.CLOSED; } /** * Test a packet for transmission errors. This function should only called * with data or ACK packets in the ESTABLISHED state. * * @param packet * Packet to test. * @return true if packet is free of errors, false otherwise. */ protected boolean isValid(KtnDatagram packet) { if (packet != null) { long calc = packet.calculateChecksum(); long chk = packet.getChecksum(); boolean res = (calc == chk); return res; } else { return false; } } }