/**
* This software is GPLv2.
* Take a look at the LICENSE file for more info.
*/
package de.tu.dresden.dud.dc.WorkCycle;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.concurrent.Semaphore;
import org.apache.log4j.Logger;
import de.tu.dresden.dud.dc.Connection;
import de.tu.dresden.dud.dc.ParticipantManager;
import de.tu.dresden.dud.dc.ParticipantMgmntInfo;
import de.tu.dresden.dud.dc.Util;
import de.tu.dresden.dud.dc.InfoService.InfoServiceInfo;
import de.tu.dresden.dud.dc.InfoService.InfoServiceInfoKeyExchangeCommit;
import de.tu.dresden.dud.dc.InfoService.InfoServiceUpdateActiveJoining;
import de.tu.dresden.dud.dc.InfoService.InfoServiceUpdateActiveLeaving;
import de.tu.dresden.dud.dc.KeyGenerators.KeyGenerator;
import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessage;
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.ManagementMessageTick;
/**
* @author klobs
*
* This class manages all phases of a work cycle:
* - Organizing participating clients
* - Reservation
* - Sending
* It checks at the WorkCycleManager, whether there is something to send.
* If so it tries to reserve a work cycle. If a work cycle could be reserved,
* it adds removes the payload from the WorkCycleManager and feeds the WorkCycleRound during the sending phase correspondingly.
*/
public class WorkCycle extends Observable implements Observer {
// Logging
private static Logger log = Logger.getLogger(WorkCycle.class);
// Events
public final static int WC_ROUND_ADDUP = 0;
public static final int WC_COUNT_CHANGED = 1;
public static final int WC_RESERVATION = 2; // actually nothing else than started
public static final int WC_SENDING = 3; // actually nothing else than reservation finished.
public static final int WC_FINISHED = 4;
public static final int WC_MIN_ACTIVE_KEYS = 0; // TODO find a good minimum here
protected LinkedList<ManagementMessageAdded> addedMessages = new LinkedList<ManagementMessageAdded>();
private byte[] addedMessagesBin = new byte[0];
protected Set<Connection> broadcastConnections = Collections.synchronizedSet(new LinkedHashSet<Connection>()); // Those connections will receive messages as a broadcast:
protected LinkedHashSet<Connection> confirmedConnections = new LinkedHashSet<Connection>(); // Connections that have actually sent packages during that work cycle.
protected LinkedHashSet<Connection> expectedConnections = new LinkedHashSet<Connection>(); //Connections that are expected to send packages during that work cycle.
private LinkedHashSet<Connection> notifyJoinConnections = new LinkedHashSet<Connection>(); // Connections that will join in a few work cycles
private LinkedHashSet<Connection> notifyLeaveConnections = new LinkedHashSet<Connection>();// Connections that will leave in a few work cycles
private LinkedHashSet<Connection> leavingConnections = new LinkedHashSet<Connection>(); // Those participants will leave this work cycle
private LinkedList<InfoServiceInfoKeyExchangeCommit> keyExchangeCommitMessages = new LinkedList<InfoServiceInfoKeyExchangeCommit>();
protected KeyGenerator assocKeyGenerator = null;
protected WorkCycleManager assocWorkCycleManag = null;
protected int currentPhase = 0;
protected int expectedRounds = 0;
protected int keyGenerationMethod = -1;
protected LinkedList<byte[]> payloads = new LinkedList<byte[]>();
protected LinkedList<Integer> individualPayloadLengths = new LinkedList<Integer>();
protected int relativeRound = -1;
private ServerReservationChecker reservationChecker = null;
protected long workcycleNumber = 0; // Long.MIN_VALUE;
protected WorkCycleReserving workCycleReserving = null;
protected WorkCycleSending workCycleSending = null;
protected boolean started = false;
protected int systemPayloadLength = 0;
protected int timeout = 0;
private Thread timeoutController = null;
private boolean trap_when_possible = false;
// Semaphore
protected final Semaphore sem = new Semaphore(0, true);
/**
* do not use this constructor.
*/
public WorkCycle(){
/**
* do not use this constructor.
*/
}
public WorkCycle(long workcycleNumber, int timeout, int payloadLength, WorkCycleManager r) {
this.workcycleNumber = workcycleNumber;
this.timeout = timeout;
this.systemPayloadLength = payloadLength;
this.assocWorkCycleManag = r;
this.keyGenerationMethod = r.getKeyGenerationMethod();
this.assocKeyGenerator = r.getKeyGenerator();
this.addObserver(this);
}
public synchronized void addKeyExchangeCommitMessages(InfoServiceInfoKeyExchangeCommit c){
keyExchangeCommitMessages.add(c);
}
public void addExpectedConnection(Connection c) {
expectedConnections.add(c);
setChanged();
notifyObservers(Integer.valueOf(WC_COUNT_CHANGED));
}
public void addExpectedConnections(Collection<Connection> l) {
expectedConnections.addAll(l);
}
public void addJoinerNotification(Connection c) {
notifyJoinConnections.add(c);
}
public void addLeavingConnection(Connection c) {
leavingConnections.add(c);
}
public void addLeavingConnections(Collection<Connection> c) {
leavingConnections.addAll(c);
}
public void addLeaverNotification(Connection c) {
notifyLeaveConnections.add(c);
}
public synchronized void addMessageArrived(Connection c, ManagementMessageAdd m){
switch (currentPhase){
case WC_RESERVATION:
if (timeoutController != null){
timeoutController.interrupt();
timeoutController = null;
}
workCycleReserving.addMessageArrived(c, m);
break;
case WC_SENDING:
workCycleSending.addMessageArrived(c, m);
break;
}
}
public synchronized void addedMessageArrived(ManagementMessageAdded m) {
if (KeyGenerator.isAsynchronous(keyGenerationMethod)) {
switch (currentPhase) {
case WC_RESERVATION:
m.setReservation(true);
addedMessages.add(m);
sem.release();
break;
case WC_SENDING:
case WC_FINISHED:
addedMessages.add(m); // redundancy rules
workCycleSending.addedMessageArrived(m);
break;
}
}
else if (KeyGenerator.isSynchronous(keyGenerationMethod)){
addedMessagesBin = Util.concatenate(addedMessagesBin, m.getPayload());
switch (currentPhase) {
case WC_RESERVATION:
m.setReservation(true);
addedMessages.add(m);
sem.release();
break;
case WC_SENDING:
case WC_FINISHED:
addedMessages.add(m); // redundancy rulez
workCycleSending.addedMessageArrived(m);
sem.release();
break;
}
}
}
/**
* Does not actually broadcast the key exchange commit message,
* but just sends it to involved participants.
*/
public void broadcastKeyCommits() {
while (keyExchangeCommitMessages.size() > 0) {
InfoServiceInfoKeyExchangeCommit kec = keyExchangeCommitMessages
.removeFirst();
ManagementMessageInfo m = new ManagementMessageInfo(kec);
ParticipantMgmntInfo pmi1 = getAssocParticipantManager()
.getParticipantMgmntInfoByParticipantID(kec.getP1());
ParticipantMgmntInfo pmi2 = getAssocParticipantManager()
.getParticipantMgmntInfoByParticipantID(kec.getP2());
if (pmi1 == null || pmi2 == null) {
log.warn("Did not find one of participants. Sorry. Can not acomplish Key Exchange Request");
continue;
}
try {
pmi1.getAssocConnection().sendMessage(m.getMessage());
pmi2.getAssocConnection().sendMessage(m.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
}
public boolean checkWhetherReservationIsFinishedOnServerSide(ManagementMessageAdded m){
if (reservationChecker.hasReservationFinished(m)){
expectedRounds = reservationChecker.getExpectedRounds();
if (assocWorkCycleManag.getMessageLengthMode() == WorkCycleManager.MESSAGE_LENGTHS_VARIABLE)
individualPayloadLengths = reservationChecker.getIndividualPayloadLengths();
currentPhase = WC_SENDING;
if (reservationChecker.getExpectedRounds() > 0) {
workCycleSending = new WorkCycleSending(this);
workCycleSending.addObserver(this);
} else {
setChanged();
notifyObservers(WC_FINISHED);
currentPhase = WC_FINISHED;
}
return true;
}
return false;
}
public byte[] consumePayload(){
if (trap_when_possible && assocWorkCycleManag.getPayloadList().size() <= 0){
return Util.fillAndMergeSending((new String("Trap").getBytes()), new byte [systemPayloadLength]);
}
else if (assocWorkCycleManag.getPayloadList().size() <= 0) return new byte[0];
return assocWorkCycleManag.getPayloadList().getFirst();
}
public Set<Connection> getBroadcastConnections(){
return this.broadcastConnections;
}
/**
* Returns the LinkedList with all the added messages.
* @return
*/
public LinkedList<ManagementMessageAdded> getAddedMessages(){
return this.addedMessages;
}
public ParticipantManager getAssocParticipantManager(){
return getAssocWorkCycleManager().getAssocParticipantManager();
}
public WorkCycleManager getAssocWorkCycleManager(){
return this.assocWorkCycleManag;
}
/**
* @return the all participants (expected and confirmed), thus not the
* notify ones
*/
public LinkedHashSet<Connection> getConnections() {
LinkedHashSet<Connection> l = expectedConnections;
l.addAll(confirmedConnections);
return l;
}
public synchronized int getCurrentPhase(){
return this.currentPhase;
}
/**
* @return The participants that are expected to take part in this work cycle.
*/
public LinkedHashSet<Connection> getExpectedConnections() {
return expectedConnections;
}
/**
* This value will be known only after reservation.
*
* @return
*/
public int getExpectedRounds() {
return this.expectedRounds;
}
public LinkedList<Integer> getIndividualMessageLenghts(){
if(assocWorkCycleManag.getMessageLengthMode() != WorkCycleManager.MESSAGE_LENGTHS_VARIABLE){
log.warn("Individual message lengths requested, but not in right mode for using them");
}
return individualPayloadLengths;
}
public int getKeyGenerationMethod(){
return this.keyGenerationMethod;
}
public byte[] getMessageBin(){
return addedMessagesBin;
}
public byte[] getNextPayload(){
if (trap_when_possible && assocWorkCycleManag.getPayloadList().size() <= 0){
return Util.fillAndMergeSending((new String("Trap").getBytes()), new byte [systemPayloadLength]);
}
else if (assocWorkCycleManag.getPayloadList().size() <= 0) return new byte[0];
return assocWorkCycleManag.getPayloadList().getFirst();
}
public LinkedList<byte[]> getPayloads(){
return this.payloads;
}
/**
* this information will be available after reservation;
*
* @return
*/
public int getRelativeRound() {
return relativeRound;
}
/**
* @return the work cycle number
*/
public long getWorkCycleNumber() {
return workcycleNumber;
}
/**
* Semaphore is needed to solve the producer-consumer problem for
* DC+ and Reservations
* @return the semaphare
*/
public Semaphore getSemaphore(){
return sem;
}
public int getSystemPayloadLength() {
return systemPayloadLength;
}
public int getTimeout() {
return this.timeout;
}
public boolean hasPayload(){
if (trap_when_possible) return true;
else if (assocWorkCycleManag.getPayloadList().size() > 0) return true;
else return false;
}
public boolean hasWorkCycleBeenSuccessful(){
if (workCycleSending != null)
return workCycleSending.hasWorkCycleBeenSuccessful();
return false;
}
/**
* Actually does the work for a work cycle on the side of the participants
*/
public void performSendingOnParticipantSide() {
started = true;
currentPhase = WC_RESERVATION;
workCycleReserving = new WorkCycleReserving(this);
Thread t = new Thread(workCycleReserving, "WorkCycleReserving");
workCycleReserving.addObserver(this);
t.start();
}
/**
* Actually does the work for a work cycle on the side of the server
*/
public void performDCWorkCycleOnServerSide() {
started = true;
try {
ManagementMessage m = null;
InfoServiceInfo i = null;
updateBroadcastAndExpectedConnections();
getAssocParticipantManager().update(workcycleNumber);
// Tell participants about new key commits
broadcastKeyCommits();
// Notify all active participants about upcoming new participants
if (notifyJoinConnections.size() > 0){
i = new InfoServiceUpdateActiveJoining(notifyJoinConnections);
m = new ManagementMessageInfo(i);
for (Connection c : getBroadcastConnections()) {
c.sendMessage(m.getMessage());
}
}
if(notifyLeaveConnections.size() > 0) {
i = new InfoServiceUpdateActiveLeaving(notifyLeaveConnections);
m = new ManagementMessageInfo(i);
for (Connection c : getBroadcastConnections()) {
c.sendMessage(m.getMessage());
}
}
currentPhase = WC_RESERVATION;
// Zeichen geben, dass naechste Runde an der Reihe ist,
// aber vorher nochmal kurze Verschnaufpause.
try {
Thread.sleep(assocWorkCycleManag.getTickPause());
} catch (InterruptedException e) {
log.warn(e.toString());
}
m = new ManagementMessageTick(workcycleNumber);
for (Connection c : getBroadcastConnections()) {
c.sendMessage(m.getMessage());
}
// Reservierung Starten
reservationChecker = new ServerReservationChecker();
workCycleReserving = new WorkCycleReserving(this);
workCycleReserving.addObserver(this);
timeoutController = new Thread(new WorkCycleTimeoutController(
workCycleReserving, assocWorkCycleManag.getRealtimeMessageTimeout()), "WorkCycleTimeoutController-"
+ workcycleNumber);
timeoutController.start();
// rest gets done as soon as messages arrive...
} catch (IOException e) {
log.error(e.toString());
}
}
public boolean workCycleHasStarted() {
return started;
}
public void setBroadcastConnections(Set<Connection> l ){
this.broadcastConnections = l;
}
/**
* @param participants
* the participants to set
*/
public void setExpectedConnections(LinkedHashSet<Connection> participants) {
this.expectedConnections = participants;
}
/**
* @param workCycleNumber
* the workCycleNumber to set
*/
public void setWorkCycleNumber(int workCycleNumber) {
this.workcycleNumber = workCycleNumber;
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WorkCycleReserving) {
if (((Integer) arg).intValue() == WorkCycle.WC_SENDING) {
// Suck out those information
expectedRounds = ((WorkCycleReserving) o).getExpectedRounds();
relativeRound = ((WorkCycleReserving) o).getRelativeRound();
if (expectedRounds <= 0){
synchronized (assocWorkCycleManag) {
setChanged();
notifyObservers(WC_FINISHED);
}
return;
}
if(assocWorkCycleManag.getMessageLengthMode() == WorkCycleManager.MESSAGE_LENGTHS_VARIABLE){
individualPayloadLengths = ((WorkCycleReserving) o).getIndividualMessageLengths();
}
currentPhase = WC_SENDING;
workCycleSending = new WorkCycleSending(this);
workCycleSending.addObserver(this);
// Summierung Starten
if (KeyGenerator.isAsynchronous(keyGenerationMethod)) {
if (!assocWorkCycleManag.isServerMode())
workCycleSending.performDCRoundsParticipantSide();
} else if (KeyGenerator.isSynchronous(keyGenerationMethod)) {
if (!assocWorkCycleManag.isServerMode()) {
Thread t = new Thread(workCycleSending,
"WorkCycleSending");
t.start();
}
}
}
} else if (o instanceof WorkCycleSending
&& ((Integer) arg).intValue() == WorkCycle.WC_FINISHED) {
// TODO
// Statistiken: Durchschnittliche Rundendauer
// Durchschnittliche Teilnehmerzahl
// Runde zu ende
setChanged();
notifyObservers(WC_FINISHED);
currentPhase = WC_FINISHED;
}
}
private void updateBroadcastAndExpectedConnections(){
broadcastConnections.addAll(getConnections());
broadcastConnections.addAll(notifyJoinConnections);
broadcastConnections.removeAll(leavingConnections);
expectedConnections.removeAll(leavingConnections);
}
}