/** * This software is GPLv2. * Take a look at the LICENSE file for more info. */ package de.tu.dresden.dud.dc; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.Arrays; import java.util.LinkedList; import java.util.Observable; import org.apache.log4j.Logger; import de.tu.dresden.dud.dc.InfoService.InfoServiceInfoActiveParticipantList; import de.tu.dresden.dud.dc.InfoService.InfoServiceInfoEarlyQuitServiceNotification; import de.tu.dresden.dud.dc.InfoService.InfoServiceInfoKeyExchangeCommit; import de.tu.dresden.dud.dc.InfoService.InfoServiceInfoReqActiveParticipantList; import de.tu.dresden.dud.dc.InfoService.InfoServiceInfoReqPassiveParticipantList; import de.tu.dresden.dud.dc.InfoService.InfoServiceInfoRequestKeyExchange; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessage; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageAccepted4Service; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageAdd; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageAdded; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageInfo; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageInfoRequest; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageJoinWorkCycle; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageKThxBye; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageLeaveWorkCycle; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageQuitService; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageRegisterAtService; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageTick; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageWelcome2Service; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageWelcome2WorkCycle; import de.tu.dresden.dud.dc.WorkCycle.WorkCycle; import de.tu.dresden.dud.dc.WorkCycle.WorkCycleManager; /** * One of the most important classes in this implementation. Most of the actions * that belong to the actual communication are being performed here. I.e. * message forming and handling, etc. * * Each DC/DC+ communication relays at least on one connection. * * On the server side a connection for each new participant is established. * * For the time being, on the participant side there is only one connection * possible at a time. * * * @author klobs */ public class Connection extends Observable implements Runnable { // Logging private static Logger log = Logger.getLogger(Connection.class); /** * Default port */ public static final int DEFAULTPORT = 6867; // Lost connection handling /** * No handling for lost / broken connections */ public static final short HANDLING_EXPLODE = 0; public static final short CONNECTION_CRASHED = 1; // Modes for the connection. /** * Status indicator for connections. This connection is broken and can no * longer be used. */ public static final int MODE_BROKEN = -1000; /** * This connection is welcome to the service. It may now register at the service. */ public static final int MODE_WELCOME2SERVICE = 1; /** * Status indicator for connections. */ public static final int MODE_REGISTERATSERVICE = 2; /** * Status indicator for connections. This connection has fully registered at the * service. It may use info services and also request to join work cycles, to * actively take part in them. */ public static final int MODE_PASSIVE = 3; /** * Status indicator for connections. This connection actively takes part in * work cycles. */ public static final int MODE_ACTIVE = 5; // Sockets and their endpoints protected Socket clientSocket = null; protected Server server = null; protected ServerSocket serverSocket = null; protected boolean stopped = true; // other internal variables protected KeyManager assocKeyManager = null; public Participant assocParticipant; protected WorkCycleManager assocWorkCycleManager; protected ParticipantManager assocParticipantManager; protected int errorCount = 0; protected long exptectedEntryWC = -1; // TODO: Will have to fix the singed / // unsigned problem with java. // Long.MIN_VALUE; protected long expectedLeavingWC = -1; private LinkedList<byte[]> sendMessageBuffer = new LinkedList<byte[]>(); // we use this if no work cycle manager is active yet. protected boolean servermode; // we should know whether this connection is // created on serverside, or not // timestamps for statistics protected long firstError = 0; // If we have a certain mount of // errors, let's shut down the // connection. // Channels protected DataInputStream input = null; protected DataOutputStream output = null; // Current mode protected int currentMode = 0; // Message store protected ManagementMessageAccepted4Service acceptedForService; protected ManagementMessageRegisterAtService regsiterAtService; protected ManagementMessageJoinWorkCycle joinWorkCycle; protected ManagementMessageLeaveWorkCycle leaveWorkCycle; protected ManagementMessageKThxBye kThxBye = null; protected ManagementMessageAdd lastAdd; protected ManagementMessageAdded lastAdded; protected ManagementMessage lastMessage; protected ManagementMessageTick lastTick; protected ManagementMessageQuitService lastQuit; protected ManagementMessageWelcome2Service welcome2Service; protected ManagementMessageWelcome2WorkCycle welcome2WorkCycle; // identifier (will be set up in the toSting method) protected String ident = null; /** * Constructor * * @param server * The server that the connection belongs to. This can be null * (in case a participant creates a new connection) * @param clientSocket * The established socket, with the client waiting on the other * site. * @param creator * the object which is creating the connection (needed for * logging) */ public Connection(Server server, Socket clientSocket, Object creator) { this.server = server; this.clientSocket = clientSocket; if (server == null) servermode = false; else servermode = true; ident = creator.toString(); if (clientSocket != null) stopped = false; currentMode = MODE_BROKEN; } /** * This method will be called on server side (normally by a server object). * It checks the current status of the connection (to verify whether the state transition is allowed), * crafts a ManagementMessage and transfers it to the participant. * * This method does not check the value of acceptReject. * @param acceptReject Integer, that indicates whether the participant is accepted, or not. No semantic checking is done here. */ public void accept4Service(int acceptReject) { if (!(currentMode == MODE_WELCOME2SERVICE)) { log.warn("Bad transition. Not in WELCOME2SERVICE status, to send ACCEPTED4SERVICE message"); return; } try { ManagementMessageAccepted4Service m = new ManagementMessageAccepted4Service( acceptReject); this.sendMessage(m.getMessage()); currentMode = MODE_PASSIVE; } catch (IOException e) { log.error("Problems occured with the ACCEPTED4SERVICE message: " + e.toString()); } } public boolean checkWhetherQuitRequestedOnParticipantSide(){ if(kThxBye != null) return true; return false; } /** * Analyses an {@link InfoServiceInfoKeyExchangeCommit} and activates the * keys between this participant and the other one mentioned in i. * Activation is being done in the {@link KeyManager}. * * @param i * the {@link InfoServiceInfoKeyExchangeCommit} which is received * and which contains the two {@link Participant}s of a key * exchange. */ public void commitKeyExchange(InfoServiceInfoKeyExchangeCommit i){ if (i.getP1().equals(assocParticipant.getId())) assocKeyManager.activateKeyExchangeBetween(i.getP2(), true, assocParticipantManager); if (i.getP2().equals(assocParticipant.getId())) assocKeyManager.activateKeyExchangeBetween(i.getP1(), false, assocParticipantManager); } /** * Call this method when using automatic key exchange. * @param p */ public void commitKeyExchange(final String p){ if (p.compareToIgnoreCase(assocParticipant.getId()) < 0){ assocKeyManager.activateKeyExchangeBetween(p, true, assocParticipantManager); } else if (p.compareToIgnoreCase(assocParticipant.getId()) > 0){ assocKeyManager.activateKeyExchangeBetween(p, false, assocParticipantManager); } log.debug("Automatic key exchange: no need to exchange keys with yourself"); } /** * Each {@link Participant} can decide at any time to send DC/DC+ messages * over a communication, whether the {@link WorkCycle}s are already running, or * not. * * This method takes those messages and if the work cycles are already running, * it passes them directly to the {@link WorkCycleManager}, or - if not - it * buffers them until the {@link WorkCycleManager} starts up and fetches them. * * @param s the plain text message that you want to transmit over the connection. */ public void feedWorkCycleManager(String s) { if(assocWorkCycleManager != null) assocWorkCycleManager.addMessage(s.getBytes()); else sendMessageBuffer.add(s.getBytes()); } public void feedWorkCycleManager(byte[] s) { if(assocWorkCycleManager != null) assocWorkCycleManager.addMessage(s); else sendMessageBuffer.add(s); } /** * An {@link InfoServiceInfoActiveParticipantList} should call this method * on arrival, to remember this connection to finish unfinished key exchange * requests. * * This method invokes the {@link KeyManager}'s * finishUnfinishedKeyExchReqs(). */ public void finishUnfinishedKeyExchReqs(){ assocKeyManager.finishUnfinishedKeyExchReqs(this); } /** * Standard getter. * * Each Connection must have an associated {@link Participant}. This * participant is the participant waiting on the one side of the connection. * * The associated participant is being set in the corresponding setter * method. * * @return The associated participant */ public Participant getAssociatedParticipant() { return this.assocParticipant; } /** * Standard getter. * @return The associated {@link ParticipantManager}. */ public ParticipantManager getAssociatedParticipantManager(){ if (assocParticipantManager == null){ assocParticipantManager = new ParticipantManager(); assocParticipantManager.addParticipant(assocParticipant); assocParticipantManager.setMe(assocParticipant); assocParticipantManager .getParticipantMgmntInfoFor(assocParticipant) .setAssocConnection(this); } return this.assocParticipantManager; } /** * Standard getter. * @return The associated {@link WorkCycleManager}. */ public WorkCycleManager getAssociatedWorkCycleManager(){ return this.assocWorkCycleManager; } /** * Standard getter. * @return the associated Server */ public Server getAssociatedServer(){ return this.server; } /** * Getter for the client socket of the connection. */ public Socket getClientSocket() { return clientSocket; } /** * @return when a connection is expected to take part in a work cycle. */ public long getExpectedEntryWorkCycle() { return exptectedEntryWC; } public long getExpectedLeavingWorkCycle(){ return expectedLeavingWC; } public short getKeyExchangeMethod(){ if (welcome2Service != null) return welcome2Service.getKeXMethod(); return KeyExchangeManager.KEX_MANUAL; } /** * Return the current mode of the connection. * * @return the current mode / status of the connection. */ public int getMode() { return this.currentMode; } /** * Getter for the server socket of the connection (, if has any. Only server * side connections have server sockets) * * @return the serverSocket. */ public ServerSocket getServerSocket() { return serverSocket; } private void handleEarlyQuitConnectionClosed(){ stopped = true; synchronized (assocWorkCycleManager) { ParticipantMgmntInfo pmi = assocParticipantManager .getParticipantMgmntInfoFor(this); if (pmi != null) { assocParticipantManager.removeParticipant(pmi); if (servermode && pmi.isActive()) { InfoServiceInfoEarlyQuitServiceNotification i = InfoServiceInfoEarlyQuitServiceNotification .infoServiceInfoEarlyQuitServiceNotificationFor(pmi .getParticipant(), assocWorkCycleManager .getCurrentWorkCycleNumber(), 0); log.info("Kicking " + pmi.getParticipant().getId()); assocWorkCycleManager.broadcastToActiveParticipants(i); assocWorkCycleManager.handleEarlyQuitOnServerside(this); } } } setChanged(); notifyObservers(CONNECTION_CRASHED); } public void handleEarlyQuitConnectionUnresponsive(){ try { this.clientSocket.close(); } catch (IOException e) { log.error(e.toString()); } } /** * A connection can be in server mode, or not. A connection being in server * mode, has an associated {@link Server}. Normally only connections on the * server side are in server mode. * * A connection can automatically determine, whether it is in server mode, * or not. * * @return whether the connection is in server mode, or not. */ public boolean isServerMode(){ return servermode; } /** * Each {@link Connection} runs as a thread. The run() method runs in a * loop, to wait for arriving data. With each beginning of the loop, run() * checks, whether the connection shall run another time or not. * * Therefore it calls this method. * * @return true, or false - it is a boolean ;) */ public boolean isStopped() { return this.stopped; } /** * A {@link Participant} calls this method on a passive connection, to join * a work cycle. The method checks the state of the connection and decides * whether the desired transition is allowed. It then crafts the * corresponding {@link ManagementMessage} ({@link ManagementMessageJoinWorkCycle}) and sends it to the * {@link Server}. * * The {@link Server} then replies with corresponding * {@link ManagementMessage}. * * @param p * The participant that requests to join a work cycle. (Usually it is * the same as the assocParticipant of the connection/ or this). */ public void joinWorkCycle(Participant p) { if (!(assocParticipantManager.getParticipantMgmntInfoFor(p).isPassive() && !assocParticipantManager.getParticipantMgmntInfoFor(p).isActive())) { log.warn("Bad transition. Not in PASSIVE status, to send JOINWORKCYLE message"); return; } try { ManagementMessageJoinWorkCycle m = new ManagementMessageJoinWorkCycle(); this.sendMessage(m.getMessage()); } catch (IOException e) { log.error("Problems occured within the joinWorkCycle method: " + e.toString()); } } /** * A client can call this method to send a * {@link ManagementMessageRegisterAtService} {@link ManagementMessage} to the * server. The method checks the state of the connection and decides whether * the desired transition is allowed. It then crafts the corresponding * {@link ManagementMessage} ({@link ManagementMessageRegisterAtService}) and sends * it to the {@link Server}. * * @param p */ public void registerAtService(Participant p) { if (!(currentMode == MODE_REGISTERATSERVICE)) { log.warn("Bad transition. Not in broken status, to send REGISTER AT SERVICE message"); return; } try { ManagementMessageRegisterAtService m = new ManagementMessageRegisterAtService(p .getId(), p.getUsername(), p.getDSAPublicSignature(), p .getDHPublicPart(), p.getDHPublicPartSignature()); this.sendMessage(m.getMessage()); } catch (IOException e) { log.error("Problems occured while being in the registerAtService method: " + e.toString()); } } /** * A {@link Participant} calls this method on an active connection, to leave * a work cycle. The method checks the state of the connection and decides * whether the desired transition is allowed. It then crafts the * corresponding {@link ManagementMessage} ({@link ManagementMessageLeaveWorkCycle}) and sends it to the * {@link Server}. * * The {@link Server} then replies with corresponding * {@link ManagementMessage}. * * @param p * The participant that requests to leave a work cycle. (Usually it is * the same as the assocParticipant of the connection/ or this). */ public void leaveWorkCycle(Participant p) { if (! assocParticipantManager.getParticipantMgmntInfoFor(p).isActive()) { log.warn("Bad transition. Not in ACTIVE status, to send LEAVEWORKCYLE message"); return; } try { ManagementMessageLeaveWorkCycle m = new ManagementMessageLeaveWorkCycle( assocWorkCycleManager.getCurrentWorkCycleNumber() + assocWorkCycleManager.getLeaveOffset()); this.sendMessage(m.getMessage()); } catch (IOException e) { log.error("Problems occured within the leaveWorkCycle method: " + e.toString()); } } public void quitService(Participant p){ if (assocParticipantManager.getParticipantMgmntInfoFor(p).isActive()) { log.debug("My dear, next time you better leave the work cycles"); } try { ManagementMessageQuitService m = new ManagementMessageQuitService(); this.sendMessage(m.getMessage()); } catch (IOException e) { log.error("Problems occured within the quitService method: " + e.toString()); } } /** * A {@link Participant} can call this method to request a list of all * active participants from the {@link Server}. */ public void requestActiveConnections(){ ManagementMessage m = new ManagementMessageInfoRequest(new InfoServiceInfoReqActiveParticipantList()); try { sendMessage(m.getMessage()); } catch (IOException e) { e.printStackTrace(); } } /** * {@link Participant}s can call this method to request a key exchange with * another participant. This participant is then indicated by the ID. * * The method checks whether the ID is known / valid. If not (yet), it tries * to update the participant list by requesting it once more from the * server. It then performs a Diffie-Hellmann Key-Exchange with singed * public values. See {@link KeyExchange} for more information about the * exchange. * * @param id * the ID (SHA1 fingerprint of the public part of the signature) * of the participant that you want to exchange keys with. */ public void requestKeyExchange(String id){ if (id == null || assocKeyManager == null) return; ParticipantMgmntInfo pmi = assocParticipantManager.getParticipantMgmntInfoByParticipantID(id); if (pmi == null) { requestPassiveConnections(); log.warn("Sorry - remote participant is not known.\n\tNew ParticipantList requested.\n\t" + id + " marked as unfinished key exchange request.\n\tKey exchange request aborted."); assocKeyManager.addUnfinishedKeyExchangeRequest(id); return; } if(assocParticipant.getId().compareTo(pmi.getParticipant().getId()) == 0) { log.debug("No need to exchange keys with yourself. Key exchange request aborted."); return; } DCKey dck = pmi.getKey(); if(dck.getState() != DCKey.KEY_UNEXCHANGED) return; if(! assocKeyManager.verifyKey(pmi.getParticipant())){ log.warn("Signature did not work with provided key. Key exchange request aborted."); return; } InfoServiceInfoRequestKeyExchange ke = new InfoServiceInfoRequestKeyExchange(assocParticipant.getId(),pmi.getParticipant().getId()); ManagementMessageInfoRequest mi = new ManagementMessageInfoRequest(ke); try { sendMessage(mi.getMessage()); dck.setSate(DCKey.KEY_REQUESTED); } catch (IOException e) { e.printStackTrace(); } } /** * A {@link Participant} can call this method to request a list of all * passive participants from the {@link Server}. */ public void requestPassiveConnections(){ ManagementMessage m = new ManagementMessageInfoRequest(new InfoServiceInfoReqPassiveParticipantList()); try { sendMessage(m.getMessage()); } catch (IOException e) { e.printStackTrace(); } } /** * As every connection is a thread, this method implements the main task for * these threads. First of it sends out a * {@link ManagementMessageWelcome2Service} management message (by calling * welcome2Service()), and sets the associated participant manager. * * It then enteres a loop which is performed until isStopped() returns a * true, or some other unfixable erros are detected. In this loop it waits * for arriving {@link ManagementMessage}s and tries to handle them, by * calling the setSatus() method. * * If a connection is going down, it tries to do some cleanup-work. * */ public void run() { try { input = new DataInputStream(clientSocket.getInputStream()); output = new DataOutputStream(clientSocket.getOutputStream()); byte[] messagetypeb = new byte[2]; byte[] lengthb = new byte[2]; if (servermode) this.welcome2Service(server); else assocParticipantManager = getAssociatedParticipantManager(); // TODO Maybe timeout for a socket, on unresponsiveness of a // client!? while (!isStopped()) { try { input.readFully(messagetypeb, 0, 2); // Read the first 2 // bytes // (management message type) input.readFully(lengthb, 0, 2); // next two bytes are // message length; // payload length) int messagetype = Util .stuffBytesIntoUInt(messagetypeb); int length = Util.stuffBytesIntoUInt(lengthb); byte[] messageb = new byte[length]; // We can gain // performance // if we can initialize // messageb before entering // the while loop // already with 2^16. message passing has to be adjusted // TODO input.readFully(messageb, 0, length); log.debug("received message from" + clientSocket.toString()); log.trace(Arrays.toString(messageb)); try { ManagementMessage m = ManagementMessage.parseMessage( messagetype, messageb); if (m != null) setStatus(m); } catch (IllegalArgumentException e) { log.warn(e.toString()); log.warn("Could not handle message correctly: " + String.valueOf(messagetype) + "," + String.valueOf(length)); log.trace(Arrays.toString(messageb)); log.warn(e.toString()); } } catch(EOFException e){ log.warn("Remote connection ended unexpectedly. Trying to clean up that mess... :( "); handleEarlyQuitConnectionClosed(); break; } catch (NullPointerException e) { log.error("Experiencing problems with the connection: "); log.error(e.toString()); handleEarlyQuitConnectionUnresponsive(); break; } catch (IndexOutOfBoundsException e) { log.error(e.toString()); handleEarlyQuitConnectionUnresponsive(); break; } catch (IOException e) { log.error(e.toString()); log.info("Remote part " + this.toString() + " seems to be disappeared"); handleEarlyQuitConnectionUnresponsive(); break; } } } catch (IOException e) { log.error("IOExcheption caught"); e.printStackTrace(); } finally { if (clientSocket != null) try { synchronized (clientSocket) { clientSocket.close(); } } catch (IOException e) { } } } /** * Sends byte array r to the other end of the connection * * @param r * @throws IOException */ public synchronized void sendMessage(byte[] r) throws IOException { if (output == null) throw new IOException("No output stream ready to send response"); log.debug("Sending message: " + Arrays.toString(r)); try { output.write(r, 0, r.length); // output.flush(); } catch (IOException e) { stopped = true; handleEarlyQuitConnectionClosed(); log.error("Output error: " + e.toString()); log.error("Closing Connection"); } } /** * Standard setter. * * Associates a {@link KeyManager} to this {@link Connection}. * @param k the KeyManager that you want to associate. */ public void setAssocKeyManager(KeyManager k){ assocKeyManager = k; } /** * Standard setter. * * Associates a {@link ParticipantManager} to this connection. * * @param p * the ParticipantManager that you want to associate. */ public void setAssocParticipantManager(ParticipantManager p){ assocParticipantManager = p; } /** * Standard setter. * * Associates a {@link WorkCycleManager} to this connection. * @param r the {@link WorkCycleManager}, that you want to associate. */ public void setAssocWorkCycleManager(WorkCycleManager r){ assocWorkCycleManager = r; } /** * Standard setter. * * Can be used to set the expected entry work cycle number which is usually * indicated by the server with the {@link ManagementMessageWelcome2WorkCycle} * management message. * * @param r * work cycle number after which the participant is expected to * actively take part in work cycles. */ public void setExpectedEntryWorkCycle(long r) { exptectedEntryWC = r; } public void setExpectedLeavingWorkCycle(long r){ expectedLeavingWC = r; } /** * Each connection should have an associated participant * * @param p the participant that shall be associated to the connection. */ public void setParticipant(Participant p) { this.assocParticipant = p; } public void setMode(int m){ currentMode = m; } /** * This method is one of the most important methods in the connection class. * After new {@link ManagementMessage}s arrive, they are crafted and then * passed here for correct evaluation. * * This method realizes the transitions of the connection, as well, as it * calls the right actions that follow on these messages (i.e. it invokes * the right handling methods). * * @param m The ManagementMessage that influences the connection. */ public void setStatus(ManagementMessage m){ m.setArrivalTime(System.currentTimeMillis()); this.lastMessage = m; // before actually make the mode transition, notify the others. // this should be the other way work cycle, but then an early return would not be possible setChanged(); notifyObservers(m); // Fist check only messages, that can go from Participants -> Server if (servermode){ //REGISTERATSERVICE if ((m instanceof ManagementMessageRegisterAtService) && (currentMode == MODE_BROKEN)){ this.regsiterAtService = (ManagementMessageRegisterAtService) m; this.currentMode = MODE_WELCOME2SERVICE; // On the server side ignite the acception process. server.accept4Service(this, (ManagementMessageRegisterAtService) m); return; } //JOINWORKCYCLE else if ((m instanceof ManagementMessageJoinWorkCycle) && assocParticipantManager.getParticipantMgmntInfoFor(this).isPassive() && !assocParticipantManager.getParticipantMgmntInfoFor(this).isActive() ){ this.joinWorkCycle = (ManagementMessageJoinWorkCycle) m; this.server.joinWorkCycleRequested(this); this.currentMode = MODE_ACTIVE; return; } //ADD else if((m instanceof ManagementMessageAdd) && (currentMode == MODE_ACTIVE)){ this.lastAdd = (ManagementMessageAdd) m; log.debug("ADD message arrived"); assocWorkCycleManager.addMessageArrived(this, lastAdd); return; } //LEAVEWORKCYCLE else if((m instanceof ManagementMessageLeaveWorkCycle) && (currentMode == MODE_ACTIVE)){ this.leaveWorkCycle = (ManagementMessageLeaveWorkCycle) m; log.debug("LEAVEWORKCYCLE message arrived. Good bye and Thanks for participation."); server.leaveWorkCycleRequested(this, leaveWorkCycle); return; } //LEAVESERVICE else if((m instanceof ManagementMessageQuitService)){ this.lastQuit = (ManagementMessageQuitService) m; server.quitServiceRequest(this); return; } } // only messages that can go from Server -> Participant else if ( !servermode ) { //WELCOME2SERVICE if ( m instanceof ManagementMessageWelcome2Service ){ this.welcome2Service = (ManagementMessageWelcome2Service) m; this.currentMode = MODE_REGISTERATSERVICE; if (!assocParticipant.getManualSetup()) assocParticipant.registerAtService(this); return; } //ACCEPTED4SERVICE else if ((m instanceof ManagementMessageAccepted4Service) && (currentMode == MODE_REGISTERATSERVICE)){ this.acceptedForService = (ManagementMessageAccepted4Service) m; this.currentMode = MODE_PASSIVE; getAssociatedParticipantManager().setParticipantPassive(assocParticipant); if (!assocParticipant.getManualSetup()) assocParticipant.joinWorkCycle(this); return; } //WELCOME2WORKCYCLE else if ((m instanceof ManagementMessageWelcome2WorkCycle) && (currentMode == MODE_PASSIVE)) { this.welcome2WorkCycle = (ManagementMessageWelcome2WorkCycle) m; if (welcome2WorkCycle.isAccepted()) { this.currentMode = MODE_ACTIVE; this.setExpectedEntryWorkCycle(welcome2WorkCycle .getWorkCycle()); assocParticipantManager.setParticipantActiveAfterWorkCycle( assocParticipant, welcome2WorkCycle.getWorkCycle()); assocParticipantManager .setParticipantsActive(welcome2WorkCycle .getActiveParticipantIDs()); // Exchange keys if mode is automatic if (welcome2Service.getKeXMethod() == KeyExchangeManager.KEX_FULLY_AUTOMATIC) { for (int i = 0; i < welcome2WorkCycle.getActiveParticipantIDs().size(); i++){ if (!assocParticipantManager .getParticipantMgmntInfoByParticipantID( welcome2WorkCycle .getActiveParticipantIDs() .get(i).getId()) .hasExchangedKey()) { assocParticipantManager .getParticipantMgmntInfoByParticipantID( welcome2WorkCycle .getActiveParticipantIDs() .get(i).getId()) .getKey().setSate(DCKey.KEY_REQUESTED); commitKeyExchange(welcome2WorkCycle .getActiveParticipantIDs().get(i) .getId()); } } } } return; } // TICK else if (m instanceof ManagementMessageTick) { lastTick = (ManagementMessageTick) m; assocParticipantManager.update(lastTick.getWorkCycleNumber()); // Only let active connections proceed. if (assocParticipantManager.getParticipantMgmntInfoFor( assocParticipant).isActive()) { if (assocWorkCycleManager != null) { assocWorkCycleManager .tickArrived(lastTick.getWorkCycleNumber()); } else { assocWorkCycleManager = new WorkCycleManager( welcome2Service.getKeGMethod(), lastTick.getWorkCycleNumber(), welcome2Service.getCharLength(), welcome2Service.getFeatureMessageLength(), welcome2Service.getEarlyQuitReaction()); assocWorkCycleManager.setParticipant(assocParticipant); assocWorkCycleManager .setAssocParticipantManager(assocParticipantManager); // feed with the messages that we already have while (sendMessageBuffer.size() > 0) { feedWorkCycleManager(sendMessageBuffer.removeFirst()); } assocWorkCycleManager.addExpectedConnection(this); assocWorkCycleManager .tickArrived(lastTick.getWorkCycleNumber()); } } return; } // ADDED else if ((m instanceof ManagementMessageAdded)){ lastAdded = (ManagementMessageAdded) m; if (assocWorkCycleManager == null){ log.warn("ADDED message arrived before the first tick. Skipping message."); return; } assocWorkCycleManager.addedMessageArrived(lastAdded); return; } // K THX BYE else if ((m instanceof ManagementMessageKThxBye)){ kThxBye = (ManagementMessageKThxBye) m; if(kThxBye.getQuitOK() == ManagementMessageKThxBye.QUITOK_ALL_OK){ kThxBye = null; stop(true); } else if (kThxBye.getQuitOK() == ManagementMessageKThxBye.QUITOK_LEAVE_WC_FIRST) assocParticipant.leaveWorkCycle(this); return; } } // only messages that could go to both directions could go here. // or messages that were send in a bad state. if (m instanceof ManagementMessageInfoRequest){ ManagementMessageInfoRequest w = (ManagementMessageInfoRequest) m; w.getInfoServiceRequest().handleRequest(this); return; } else if (m instanceof ManagementMessageInfo){ ManagementMessageInfo w = (ManagementMessageInfo) m; w.getInfo().handleInfo(this); return; } // error handling - if a certain amount of errors occur in a period of time, // we stop the connection log.info("Could not react correctly on message: " + m.toString()); long n = System.currentTimeMillis(); if(n - firstError < 1000){ errorCount++; if(errorCount > 2) { stopped = true; log.error("There were too many connection erros in the last few moments. Stopping connection"); } } else{ firstError = n; errorCount = 0; } return; } /** * Set whether a connection shall be stopped. * * @param s * : stop it, or not */ public void stop(boolean s) { this.stopped = s; } public void tellGoodByeFromService(final short quitOK){ ManagementMessageKThxBye m = new ManagementMessageKThxBye(quitOK); try{ sendMessage(m.getMessage()); if (quitOK == ManagementMessageKThxBye.QUITOK_ALL_OK) { stop(true); clientSocket.close(); } } catch (IOException e){ log.error(e.toString()); } } /** * This method has to be called on {@link Participant} side after a * {@link ManagementMessageTick} arrived. * * @param workCycleNumber * the work cycle number that was transferred with the TICK management * message. */ public void tickArrived(long workCycleNumber){ assocWorkCycleManager.tickArrived(workCycleNumber); } /** * This was overwritten to enhance the debug / logging output. * It future it would be nice to use log4java and throw this crutch away. */ public String toString() { return ident + "/con:" + String.valueOf(clientSocket.getPort()) + ":" + String.valueOf(clientSocket.getLocalPort()); } /** * generate a WELCOME2WORKCYLE message and send it. * * @param a * accept/reject * @param r * work cycle number * @param t * timeout (0 == no timeout) */ public void welcome2WorkCycle(int a, long r, int t) { if (! assocParticipantManager.getParticipantMgmntInfoFor(this).isPassive()) { log.warn("Bad transition. Not in PASSIVE status, to send WELCOME2WORKCYCLE message"); return; } exptectedEntryWC = r; ManagementMessageWelcome2WorkCycle m = new ManagementMessageWelcome2WorkCycle(a, r, t, server.getInfoService().getActiveParticipants()); try { this.sendMessage(m.getMessage()); } catch (IOException e) { log.error("Input error: " + e.toString()); } } /** * generate a WELCOME2SERVICE message and send it. * * @param s * Server for which the connection has to be prepared */ public void welcome2Service(Server s) { if (!(currentMode == MODE_BROKEN)) { log.warn("Bad transition. Not in broken status, to send WELCOME2SERVICE message"); return; } ManagementMessageWelcome2Service m = new ManagementMessageWelcome2Service(s); try { log.debug("Sending WELCOME2SERVER: " ); log.trace(Arrays.toString(m.getMessage())); this.sendMessage(m.getMessage()); } catch (IOException e) { log.error("Problemas occured with the WELCOME2SERVER message: " + e.toString()); } } }