diff --git a/voting-booth/src/main/java/meerkat/voting/SystemConsoleOutputDevice.java b/voting-booth/src/main/java/meerkat/voting/SystemConsoleOutputDevice.java deleted file mode 100644 index 7546d53..0000000 --- a/voting-booth/src/main/java/meerkat/voting/SystemConsoleOutputDevice.java +++ /dev/null @@ -1,56 +0,0 @@ -package meerkat.voting; - -import com.google.protobuf.ByteString; -import meerkat.protobuf.Voting.*; - -/** - * Created by hai on 15/02/16. - */ -public class SystemConsoleOutputDevice implements BallotOutputDevice { - - /* - * Returns the UTF8 decoding of byte-string data - */ - private static String bytesToString(ByteString data) { - return data.toStringUtf8(); - } - - @Override - public void commitToBallot(long sessionId, EncryptedBallot encryptedBallot, VotingBooth.Callback callback) { - - System.out.println("Ballot #" + encryptedBallot.getSerialNumber() + ": " - + bytesToString(encryptedBallot.getData().getData())); - - callback.ballotCommitResult(sessionId, true); - - } - - @Override - public void audit(long sessionId, EncryptedBallot encryptedBallot, BallotSecrets ballotSecrets, VotingBooth.Callback callback) { - - //TODO: generate standard form for this - System.out.println("Ballot #" + encryptedBallot.getSerialNumber() + ": " - + bytesToString(encryptedBallot.getData().getData())); - - callback.ballotAuditResult(sessionId, true); - - } - - @Override - public void castBallot(long sessionId, VotingBooth.Callback callback) { - - System.out.println("Ballot finalized!"); - - callback.ballotCastResult(sessionId, true); - - } - - @Override - public void cancelBallot(long sessionId, VotingBooth.Callback callback) { - - System.out.println("Ballot cancelled!"); - - callback.ballotCancelResult(sessionId, true); - - } -} diff --git a/voting-booth/src/main/java/meerkat/voting/VotingBooth.java b/voting-booth/src/main/java/meerkat/voting/VotingBooth.java deleted file mode 100644 index 9ef863c..0000000 --- a/voting-booth/src/main/java/meerkat/voting/VotingBooth.java +++ /dev/null @@ -1,40 +0,0 @@ -package meerkat.voting; - -import meerkat.protobuf.Voting.*; - -/** - * An interface for the controller component of the voting booth - */ -public interface VotingBooth { - - /** - * initialize using the BoothParams protobuf and sett all the different components of the Voting Booth to be recognized by this controller - * @param boothParams - * @param outputDevice the ballot output device. Naturally a printer and/or ethernet connection - * @param vbEncryptor the encryption module - * @param vbUI User interface in which the voter chooses his answers - * @param vbStorageManager storage component for handling files and USB sticks - */ - public void init (BoothParams boothParams, - BallotOutputDevice outputDevice, - VotingBoothEncryptor vbEncryptor, - VotingBoothUI vbUI, - StorageManager vbStorageManager); - - /** - * set the voting questions - * @param questions - */ - public void setBallotQuestions (BallotQuestion[] questions); - - /** - * a synchronous function. Starts running the controller component, and basically run the whole system for voting - */ - public void run (); - - /** - * an asynchronous call from Admin Console (If there is such one implemented) to shut down the system - */ - public void shutDown(); - -} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothController.java b/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothController.java new file mode 100644 index 0000000..ee51f78 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothController.java @@ -0,0 +1,61 @@ +package meerkat.voting.controller; + +import meerkat.protobuf.Voting.*; +import meerkat.voting.controller.selector.QuestionSelector; +import meerkat.voting.ui.VotingBoothUI; +import meerkat.voting.encryptor.VotingBoothEncryptor; +import meerkat.voting.output.BallotOutputDevice; +import meerkat.voting.storage.StorageManager; + +import java.util.ArrayList; + + +/** + * An interface for the controller component of the voting booth + */ +public interface VotingBoothController { + + /** + * initialize by setting all the different components of the Voting Booth to be recognized by this controller + * @param outputDevice the ballot output device. Naturally a printer and/or ethernet connection + * @param vbEncryptor the encryption module + * @param vbUI User interface in which the voter chooses his answers + * @param vbStorageManager storage component for handling files and USB sticks + */ + public void init (BallotOutputDevice outputDevice, + VotingBoothEncryptor vbEncryptor, + VotingBoothUI vbUI, + StorageManager vbStorageManager); + + /** + * set the voting questions + * @param questions + */ + public void setBallotChannelChoiceQuestions(BallotQuestion[] questions); + public void setBallotChannelChoiceQuestions(ArrayList questions); + + /** + * Set the channel question-selector (the component which matches the ballot questions to each user) + * @param selector the question selector instance + */ + public void setChannelQuestionSelector (QuestionSelector selector); + + /** + * set the voting race questions + * @param questions + */ + public void setBallotRaceQuestions(BallotQuestion[] questions); + public void setBallotRaceQuestions(ArrayList questions); + + /** + * a synchronous function. Starts running the controller component, and basically run the whole system for voting + */ + public void run (); + + /** + * an asynchronous call from Admin Console (If there is such one implemented) to shut down the system + */ + public void shutDown(); + + +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothImpl.java b/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothImpl.java new file mode 100644 index 0000000..fd67cac --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothImpl.java @@ -0,0 +1,363 @@ +package meerkat.voting.controller; + +import com.google.protobuf.ByteString; +import meerkat.protobuf.Voting.*; +import meerkat.voting.controller.callbacks.*; +import meerkat.voting.controller.commands.*; +import meerkat.voting.controller.selector.QuestionSelector; +import meerkat.voting.encryptor.VotingBoothEncryptor; +import meerkat.voting.output.BallotOutputDevice; +import meerkat.voting.storage.StorageManager; +import meerkat.voting.ui.VotingBoothUI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Created by hai on 28/03/16. + */ +public class VotingBoothImpl implements VotingBoothController, Runnable { + + private BallotOutputDevice outputDevice; + private VotingBoothEncryptor encryptor; + private VotingBoothUI ui; + private StorageManager storageManager; + private BallotQuestion[] questionsForChoosingChannel; + private BallotQuestion[] questions; + private QuestionSelector questionSelector; + + private LinkedBlockingQueue queue; + + private Logger logger; + + private ControllerState state; + private boolean shutDownHasBeenCalled; + + protected final int MAX_REQUEST_IDENTIFIER = 100000; + private static int requestCounter = 0; + + private UIElement waitForCommitMessage; + private UIElement waitForAuditMessage; + private UIElement waitForCastMessage; + + + + public VotingBoothImpl () { + logger = LoggerFactory.getLogger(VotingBoothImpl.class); + logger.info("A VotingBoothImpl is constructed"); + shutDownHasBeenCalled = false; + queue = new LinkedBlockingQueue<>(); + + waitForCommitMessage = UIElement.newBuilder() + .setType(UIElementDataType.TEXT) + .setData(ByteString.copyFromUtf8("Please wait while committing to ballot")) + .build(); + waitForAuditMessage = UIElement.newBuilder() + .setType(UIElementDataType.TEXT) + .setData(ByteString.copyFromUtf8("Please wait while auditing your ballot")) + .build(); + waitForCastMessage = UIElement.newBuilder() + .setType(UIElementDataType.TEXT) + .setData(ByteString.copyFromUtf8("Please wait while finalizing your ballot for voting")) + .build(); + + state = new VotingBoothImpl.ControllerState(); + + } + + @Override + public void init(BallotOutputDevice outputDevice, + VotingBoothEncryptor vbEncryptor, + VotingBoothUI vbUI, + StorageManager vbStorageManager) { + logger.info("init is called"); + this.outputDevice = outputDevice; + this.encryptor = vbEncryptor; + this.ui = vbUI; + this.storageManager = vbStorageManager; + } + + @Override + public void setBallotChannelChoiceQuestions(BallotQuestion[] questions) { + logger.info("setting questions"); + this.questionsForChoosingChannel = questions; + } + + @Override + public void setBallotChannelChoiceQuestions(ArrayList questions) { + BallotQuestion[] bqArr = new BallotQuestion[questions.size()]; + for (int i = 0; i < bqArr.length; ++i) { + bqArr[i] = questions.get(i); + } + setBallotChannelChoiceQuestions(bqArr); + } + + @Override + public void setChannelQuestionSelector(QuestionSelector selector) { + this.questionSelector = selector; + } + + @Override + public void setBallotRaceQuestions(BallotQuestion[] questions) { + logger.info("setting questions"); + this.questions = questions; + } + + @Override + public void setBallotRaceQuestions(ArrayList questions) { + // TODO: why does the toArray method not work?? + //BallotQuestion[] bqArr = (BallotQuestion[])(questions.toArray()); + BallotQuestion[] bqArr = new BallotQuestion[questions.size()]; + for (int i = 0; i < bqArr.length; ++i) { + bqArr[i] = questions.get(i); + } + setBallotRaceQuestions(bqArr); + } + + @Override + public void run() { + logger.info("run command has been called"); + runVotingFlow(); + doShutDown(); + + } + + @Override + public void shutDown() { + logger.info("shutDown command has been called"); + synchronized (this) { + shutDownHasBeenCalled = true; + } + + } + + + + private void runVotingFlow () { + logger.info("entered the voting flow"); + + queue.add(new RestartVotingCommand(generateRequestIdentifier(), state.currentBallotSerialNumber)); + + while (! wasShutDownCalled()) { + try { + ControllerCommand task = queue.take(); + handleSingleTask (task); + } + catch (InterruptedException e) { + logger.warn ("Interrupted while reading from task queue " + e); + } + } + } + + private void doShutDown () { + logger.info("running shutDown"); + } + + private void handleSingleTask (ControllerCommand task) { + if (task instanceof RestartVotingCommand) { + doRestartVoting (); + return; + } + if (task.getBallotSerialNumber() != state.currentBallotSerialNumber) { + String errorMessage = "handleSingleTask: received a task too old. " + + task.getBallotSerialNumber() + " " + state.currentBallotSerialNumber; + logger.warn (errorMessage); + return; + } + + if (task instanceof CastCommand) { + doCast(); + return; + } + else if (task instanceof AuditCommand) { + doAudit(); + return; + } + else if (task instanceof ChannelChoiceCommand) { + doChooseChannel(); + return; + } + else if (task instanceof ChannelDeterminedCommand) { + doSetChannelAndAskQuestions ((ChannelDeterminedCommand)task); + return; + } + else if (task instanceof EncryptAndCommitBallotCommand) { + doCommit ((EncryptAndCommitBallotCommand)task); + return; + } + else { + String errorMessage = "handleSingleTask: unknown type of ControllerCommand received: " + + task.getClass().getName(); + logger.error(errorMessage); + throw new RuntimeException(errorMessage); + } + } + + /* + Map taskHandlers = new Map<>() { +CaskTask.getClass().getName() : doCast(), +ChannelChoiceCommand.getClass().getName() : doChooseChannel(), +} + + + + */ + + private synchronized boolean wasShutDownCalled () { + return shutDownHasBeenCalled; + } + + private void doRestartVoting () { + queue.clear(); + state.clearPlaintext(); + state.clearCiphertext(); + state.stateIdentifier = VBState.NEW_VOTER; + state.currentBallotSerialNumber += 1; + + ui.startNewVoterSession(new NewVoterCallback(generateRequestIdentifier(), state.currentBallotSerialNumber , this.queue)); + } + + private void doChooseChannel () { + if (state.stateIdentifier == VBState.NEW_VOTER) { + logger.debug("doing chooseChannel"); + state.stateIdentifier = VBState.CHOOSE_CHANNEL; + ui.chooseChannel(this.questionsForChoosingChannel, + new ChannelChoiceCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, this.queue)); + } + else { + logger.warn("doChooseChannel: current state is " + state.stateIdentifier); + // ignore this request + } + } + + private void doSetChannelAndAskQuestions (ChannelDeterminedCommand task) { + if (state.stateIdentifier == VBState.CHOOSE_CHANNEL) { + logger.debug("doing set channel and ask questions"); + state.stateIdentifier = VBState.ANSWER_QUESTIONS; + BallotAnswer[] channelChoiceAnswers = task.channelChoiceAnswers; + state.channelIdentifier = questionSelector.getChannelIdentifier(channelChoiceAnswers); + state.channelSpecificQuestions = questionSelector.selectQuestionsForVoter(channelChoiceAnswers); + state.stateIdentifier = VBState.ANSWER_QUESTIONS; + ui.askVoterQuestions(state.channelSpecificQuestions, + new VotingCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, this.queue)); + } + else { + logger.warn("doSetChannelAndAskQuestions: current state is " + state.stateIdentifier); + // ignore this request + } + } + + + private void doCommit (EncryptAndCommitBallotCommand task) { + if (state.stateIdentifier == VBState.ANSWER_QUESTIONS) { + logger.debug("doing commit"); + state.stateIdentifier = VBState.COMMITTING_TO_BALLOT; + VotingBoothEncryptor.EncryptionAndSecrets encryptionAndSecrets = encryptor.encrypt(state.plaintextBallot); + state.encryptedBallot = encryptionAndSecrets.getEncryptedBallot(); + state.secrets = encryptionAndSecrets.getSecrets(); + ui.notifyVoterToWaitForFinish(waitForCommitMessage, + new WaitForFinishCallback(generateRequestIdentifier(), + state.currentBallotSerialNumber, + this.queue)); + outputDevice.commitToBallot(state.plaintextBallot, + state.encryptedBallot, + new OutputDeviceCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, this.queue)); + } + else { + logger.warn("doCommit: current state is " + state.stateIdentifier); + // ignore this request + } + } + + + private void doAudit () { + if (state.stateIdentifier == VBState.CAST_OR_AUDIT) { + logger.debug("doing audit"); + state.stateIdentifier = VBState.FINALIZING; + outputDevice.audit(state.secrets, + new OutputDeviceCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, this.queue)); + ui.notifyVoterToWaitForFinish(waitForAuditMessage, + new WaitForFinishCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, this.queue)); + } + else { + logger.warn("doAudit: current state is " + state.stateIdentifier); + // ignore this request + } + } + + private void doCast () { + if (state.stateIdentifier == VBState.CAST_OR_AUDIT) { + logger.debug("casting ballot"); + state.stateIdentifier = VBState.FINALIZING; + outputDevice.castBallot( + new OutputDeviceCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, this.queue)); + ui.notifyVoterToWaitForFinish(waitForCastMessage, + new WaitForFinishCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, this.queue)); + } + else { + logger.warn("doCast: current state is " + state.stateIdentifier); + // ignore this request + } + } + + + + + + private enum VBState { + NEW_VOTER, + CHOOSE_CHANNEL, + ANSWER_QUESTIONS, + COMMITTING_TO_BALLOT, + CAST_OR_AUDIT, + FINALIZING + } + + + private class ControllerState { + public VotingBoothImpl.VBState stateIdentifier; + public int channelIdentifier; + public BallotQuestion[] channelSpecificQuestions; + public PlaintextBallot plaintextBallot; + public EncryptedBallot encryptedBallot; + public BallotSecrets secrets; + public int lastRequestIdentifier; + public long currentBallotSerialNumber; + + public ControllerState () { + plaintextBallot = null; + encryptedBallot = null; + secrets = null; + lastRequestIdentifier = -1; + channelIdentifier = -1; + channelSpecificQuestions = null; + currentBallotSerialNumber = 0; + } + + + public void clearPlaintext () { + //TODO: Do we need safe erasure? + plaintextBallot = null; + } + + public void clearCiphertext () { + //TODO: Do we need safe erasure? + encryptedBallot = null; + secrets = null; + } + } + + + private int generateRequestIdentifier() { + ++requestCounter; + if (requestCounter >= MAX_REQUEST_IDENTIFIER) { + requestCounter = 1; + } + return requestCounter; + } + + +} + diff --git a/voting-booth/src/main/java/meerkat/voting/controller/callbacks/CastOrAuditCallback.java b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/CastOrAuditCallback.java new file mode 100644 index 0000000..0a5522f --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/CastOrAuditCallback.java @@ -0,0 +1,47 @@ +package meerkat.voting.controller.callbacks; + + +import meerkat.voting.controller.commands.*; +import meerkat.voting.controller.commands.ControllerCommand; +import meerkat.voting.controller.commands.RestartVotingCommand; +import meerkat.voting.ui.VotingBoothUI.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.LinkedBlockingQueue; + +public class CastOrAuditCallback extends ControllerCallback { + protected final static Logger logger = LoggerFactory.getLogger(CastOrAuditCallback.class); + + public CastOrAuditCallback(int requestId, + long ballotSerialNumber, + LinkedBlockingQueue controllerQueue) { + super(requestId, ballotSerialNumber, controllerQueue); + } + + @Override + public void onSuccess(Object result) { + assert (result instanceof FinalizeBallotChoice); + + if (result == FinalizeBallotChoice.CAST) { + controllerQueue.add(new CastCommand(getRequestIdentifier(), getBallotSerialNumber())); + } + else if (result == FinalizeBallotChoice.AUDIT) { + controllerQueue.add(new AuditCommand(getRequestIdentifier(), getBallotSerialNumber())); + } + else { + logger.error("CastOrAuditCallback got an unrecognized response: " + result); + controllerQueue.add(new RestartVotingCommand(getRequestIdentifier(), getBallotSerialNumber())); + } + + } + + @Override + public void onFailure(Throwable t) { + logger.error("CastOrAuditCallback got a failure: " + t); + controllerQueue.add(new RestartVotingCommand(getRequestIdentifier(), getBallotSerialNumber())); + } +} + + + diff --git a/voting-booth/src/main/java/meerkat/voting/controller/callbacks/ChannelChoiceCallback.java b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/ChannelChoiceCallback.java new file mode 100644 index 0000000..c6db8d4 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/ChannelChoiceCallback.java @@ -0,0 +1,43 @@ +package meerkat.voting.controller.callbacks; + +import meerkat.protobuf.Voting; +import meerkat.voting.controller.commands.ChannelDeterminedCommand; +import meerkat.voting.controller.commands.ControllerCommand; +import meerkat.voting.controller.commands.RestartVotingCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Created by hai on 11/04/16. + */ +public class ChannelChoiceCallback extends ControllerCallback { + protected final static Logger logger = LoggerFactory.getLogger(ChannelChoiceCallback.class); + + public ChannelChoiceCallback(int requestId, + long ballotSerialNumber, + LinkedBlockingQueue controllerQueue) { + super(requestId, ballotSerialNumber, controllerQueue); + } + + @Override + public void onSuccess(Object result) { + assert (result instanceof Voting.BallotAnswer[]); + if (((Voting.BallotAnswer[])result).length != 0) { + logger.debug("callback for channel choice returned success"); + controllerQueue.add( + new ChannelDeterminedCommand(getRequestIdentifier(), getBallotSerialNumber(), (Voting.BallotAnswer[]) result)); + } + else { + logger.debug("ChannelChoiceCallback got a cancellation response: " + result); + controllerQueue.add(new RestartVotingCommand(getRequestIdentifier(), getBallotSerialNumber())); + } + } + + @Override + public void onFailure(Throwable t) { + logger.error("channel choice initiated a failure: " + t); + controllerQueue.add(new RestartVotingCommand(getRequestIdentifier(), getBallotSerialNumber())); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/callbacks/ControllerCallback.java b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/ControllerCallback.java new file mode 100644 index 0000000..2c33cbc --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/ControllerCallback.java @@ -0,0 +1,27 @@ +package meerkat.voting.controller.callbacks; + +import com.google.common.util.concurrent.FutureCallback; +import meerkat.voting.controller.commands.ControllerCommand; + +import java.util.concurrent.LinkedBlockingQueue; + +public abstract class ControllerCallback implements FutureCallback { + private final int requestIdentifier; + private final long ballotSerialNumber; + protected LinkedBlockingQueue controllerQueue; + + protected ControllerCallback (int requestId, + long ballotSerialNumber, + LinkedBlockingQueue controllerQueue) { + this.requestIdentifier = requestId; + this.ballotSerialNumber = ballotSerialNumber; + this.controllerQueue = controllerQueue; + } + + protected int getRequestIdentifier () { + return requestIdentifier; + } + protected long getBallotSerialNumber () { + return ballotSerialNumber; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/callbacks/HaltCallback.java b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/HaltCallback.java new file mode 100644 index 0000000..c251a0f --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/HaltCallback.java @@ -0,0 +1,32 @@ +package meerkat.voting.controller.callbacks; + +import meerkat.voting.controller.commands.ControllerCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Created by hai on 21/04/16. + */ +public class HaltCallback extends ControllerCallback{ + protected final static Logger logger = LoggerFactory.getLogger(HaltCallback.class); + + public HaltCallback(int requestId, + long ballotSerialNumber, + LinkedBlockingQueue controllerQueue) { + super(requestId, ballotSerialNumber, controllerQueue); + } + + + @Override + public void onSuccess(Object result) { + throw new UnsupportedOperationException("HaltCallback: onSuccess: There cannot be a successful return for this task. Returned value is " + result); + } + + @Override + public void onFailure(Throwable t) { + logger.error("Halting initiated a failure: " + t); + } + +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/callbacks/NewVoterCallback.java b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/NewVoterCallback.java new file mode 100644 index 0000000..bdb53c3 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/NewVoterCallback.java @@ -0,0 +1,32 @@ +package meerkat.voting.controller.callbacks; + +import meerkat.voting.controller.commands.ChannelChoiceCommand; +import meerkat.voting.controller.commands.ControllerCommand; +import meerkat.voting.controller.commands.RestartVotingCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.LinkedBlockingQueue; + +public class NewVoterCallback extends ControllerCallback { + protected final static Logger logger = LoggerFactory.getLogger(NewVoterCallback.class); + + public NewVoterCallback(int requestId, + long ballotSerialNumber, + LinkedBlockingQueue controllerQueue) { + super(requestId, ballotSerialNumber, controllerQueue); + } + + @Override + public void onSuccess(Object v) { + logger.debug("callback for new voting returned success"); + assert v==null; + controllerQueue.add(new ChannelChoiceCommand(getRequestIdentifier(), getBallotSerialNumber())); + } + + @Override + public void onFailure(Throwable t) { + logger.error("New voting session got a failure: " + t); + controllerQueue.add(new RestartVotingCommand(getRequestIdentifier(), getBallotSerialNumber())); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/callbacks/OutputDeviceCallback.java b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/OutputDeviceCallback.java new file mode 100644 index 0000000..81a14c4 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/OutputDeviceCallback.java @@ -0,0 +1,29 @@ +package meerkat.voting.controller.callbacks; + +import meerkat.voting.controller.commands.ControllerCommand; +import meerkat.voting.controller.commands.RestartVotingCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.LinkedBlockingQueue; + +public class OutputDeviceCallback extends ControllerCallback { + protected final static Logger logger = LoggerFactory.getLogger(OutputDeviceCallback.class); + + public OutputDeviceCallback(int requestId, + long ballotSerialNumber, + LinkedBlockingQueue controllerQueue) { + super(requestId, ballotSerialNumber, controllerQueue); + } + + @Override + public void onSuccess(Object v) { + assert v instanceof Void; + } + + @Override + public void onFailure(Throwable t) { + logger.error("WaitForFinishCallback got a failure: " + t); + controllerQueue.add(new RestartVotingCommand(getRequestIdentifier(), getBallotSerialNumber())); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/callbacks/VotingCallback.java b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/VotingCallback.java new file mode 100644 index 0000000..5e0cc1a --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/VotingCallback.java @@ -0,0 +1,40 @@ +package meerkat.voting.controller.callbacks; + +import meerkat.protobuf.Voting; +import meerkat.voting.controller.commands.ControllerCommand; +import meerkat.voting.controller.commands.EncryptAndCommitBallotCommand; +import meerkat.voting.controller.commands.RestartVotingCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.LinkedBlockingQueue; + +public class VotingCallback extends ControllerCallback { + protected final static Logger logger = LoggerFactory.getLogger(VotingCallback.class); + + public VotingCallback(int requestId, + long ballotSerialNumber, + LinkedBlockingQueue controllerQueue) { + super(requestId, ballotSerialNumber, controllerQueue); + } + + @Override + public void onSuccess(Object result) { + assert result instanceof Voting.BallotAnswer[]; + if (((Voting.BallotAnswer[])result).length != 0) { + logger.debug("callback for voting returned success"); + controllerQueue.add( + new EncryptAndCommitBallotCommand(getRequestIdentifier(), getBallotSerialNumber(), (Voting.BallotAnswer[]) result)); + } + else { + logger.debug("VotingCallback got a cancellation response: " + result); + controllerQueue.add(new RestartVotingCommand(getRequestIdentifier(), getBallotSerialNumber())); + } + } + + @Override + public void onFailure(Throwable t) { + logger.error("voting initiated a failure: " + t); + controllerQueue.add(new RestartVotingCommand(getRequestIdentifier(), getBallotSerialNumber())); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/callbacks/WaitForFinishCallback.java b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/WaitForFinishCallback.java new file mode 100644 index 0000000..d56327d --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/callbacks/WaitForFinishCallback.java @@ -0,0 +1,29 @@ +package meerkat.voting.controller.callbacks; + +import meerkat.voting.controller.commands.ControllerCommand; +import meerkat.voting.controller.commands.RestartVotingCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.LinkedBlockingQueue; + +public class WaitForFinishCallback extends ControllerCallback { + protected final static Logger logger = LoggerFactory.getLogger(WaitForFinishCallback.class); + + public WaitForFinishCallback(int requestId, + long ballotSerialNumber, + LinkedBlockingQueue controllerQueue) { + super(requestId, ballotSerialNumber, controllerQueue); + } + + @Override + public void onSuccess(Object v) { + assert v instanceof Void; + } + + @Override + public void onFailure(Throwable t) { + logger.error("WaitForFinishCallback got a failure: " + t); + controllerQueue.add(new RestartVotingCommand(getRequestIdentifier(), getBallotSerialNumber())); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/commands/AuditCommand.java b/voting-booth/src/main/java/meerkat/voting/controller/commands/AuditCommand.java new file mode 100644 index 0000000..d2c99f2 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/commands/AuditCommand.java @@ -0,0 +1,10 @@ +package meerkat.voting.controller.commands; + +/** + * Created by hai on 11/04/16. + */ +public class AuditCommand extends ControllerCommand { + public AuditCommand(int requestIdentifier, long ballotSerialNumber) { + super(requestIdentifier, ballotSerialNumber); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/commands/CastCommand.java b/voting-booth/src/main/java/meerkat/voting/controller/commands/CastCommand.java new file mode 100644 index 0000000..0621efb --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/commands/CastCommand.java @@ -0,0 +1,10 @@ +package meerkat.voting.controller.commands; + +/** + * Created by hai on 11/04/16. + */ +public class CastCommand extends ControllerCommand { + public CastCommand(int requestIdentifier, long ballotSerialNumber) { + super(requestIdentifier, ballotSerialNumber); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/commands/ChannelChoiceCommand.java b/voting-booth/src/main/java/meerkat/voting/controller/commands/ChannelChoiceCommand.java new file mode 100644 index 0000000..f555e4b --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/commands/ChannelChoiceCommand.java @@ -0,0 +1,10 @@ +package meerkat.voting.controller.commands; + +/** + * Created by hai on 11/04/16. + */ +public class ChannelChoiceCommand extends ControllerCommand { + public ChannelChoiceCommand(int requestIdentifier, long ballotSerialNumber) { + super(requestIdentifier, ballotSerialNumber); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/commands/ChannelDeterminedCommand.java b/voting-booth/src/main/java/meerkat/voting/controller/commands/ChannelDeterminedCommand.java new file mode 100644 index 0000000..8fefbe7 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/commands/ChannelDeterminedCommand.java @@ -0,0 +1,15 @@ +package meerkat.voting.controller.commands; + +import meerkat.protobuf.Voting; + +/** + * Created by hai on 11/04/16. + */ +public class ChannelDeterminedCommand extends ControllerCommand { + public Voting.BallotAnswer[] channelChoiceAnswers; + + public ChannelDeterminedCommand(int requestIdentifier, long ballotSerialNumber, Voting.BallotAnswer[] answers) { + super(requestIdentifier, ballotSerialNumber); + channelChoiceAnswers = answers; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/commands/ControllerCommand.java b/voting-booth/src/main/java/meerkat/voting/controller/commands/ControllerCommand.java new file mode 100644 index 0000000..5bfc5c2 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/commands/ControllerCommand.java @@ -0,0 +1,19 @@ +package meerkat.voting.controller.commands; + +public abstract class ControllerCommand { + protected final int requestIdentifier; + protected final long ballotSerialNumber; + + protected ControllerCommand(int requestIdentifier, long ballotSerialNumber) { + this.requestIdentifier = requestIdentifier; + this.ballotSerialNumber = ballotSerialNumber; + } + + public long getBallotSerialNumber () { + return this.ballotSerialNumber; + } + + public int getRequestIdentifier () { + return this.requestIdentifier; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/commands/EncryptAndCommitBallotCommand.java b/voting-booth/src/main/java/meerkat/voting/controller/commands/EncryptAndCommitBallotCommand.java new file mode 100644 index 0000000..bad2ff7 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/commands/EncryptAndCommitBallotCommand.java @@ -0,0 +1,12 @@ +package meerkat.voting.controller.commands; + +import meerkat.protobuf.Voting; + +public class EncryptAndCommitBallotCommand extends ControllerCommand { + public Voting.BallotAnswer[] votingAnswers; + + public EncryptAndCommitBallotCommand(int requestIdentifier, long ballotSerialNumber, Voting.BallotAnswer[] answers) { + super(requestIdentifier, ballotSerialNumber); + votingAnswers = answers; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/commands/RestartVotingCommand.java b/voting-booth/src/main/java/meerkat/voting/controller/commands/RestartVotingCommand.java new file mode 100644 index 0000000..57906d2 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/commands/RestartVotingCommand.java @@ -0,0 +1,8 @@ +package meerkat.voting.controller.commands; + + +public class RestartVotingCommand extends ControllerCommand { + public RestartVotingCommand(int requestIdentifier, long ballotSerialNumber) { + super(requestIdentifier, ballotSerialNumber); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/selector/QuestionSelector.java b/voting-booth/src/main/java/meerkat/voting/controller/selector/QuestionSelector.java new file mode 100644 index 0000000..3fd2188 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/selector/QuestionSelector.java @@ -0,0 +1,20 @@ +package meerkat.voting.controller.selector; + +import meerkat.protobuf.Voting.*; + +/** + * Created by hai on 02/05/16. + */ +public abstract class QuestionSelector { + + protected Object selectionData; + protected BallotQuestion[] allBallotQuestions; + + public QuestionSelector(Object selectionData, BallotQuestion[] allBallotQuestions) { + this.selectionData = selectionData; + this.allBallotQuestions = allBallotQuestions; + } + + public abstract int getChannelIdentifier (BallotAnswer[] channelChoiceAnswers); + public abstract BallotQuestion[] selectQuestionsForVoter (BallotAnswer[] channelChoiceAnswers); +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/selector/SimpleCategoriesData.java b/voting-booth/src/main/java/meerkat/voting/controller/selector/SimpleCategoriesData.java new file mode 100644 index 0000000..83ae516 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/selector/SimpleCategoriesData.java @@ -0,0 +1,73 @@ +package meerkat.voting.controller.selector; + +import java.util.HashMap; +import java.lang.Math; + +/** + * Created by hai on 02/05/16. + */ +public class SimpleCategoriesData { + + private int[] sharedDefaults; + private HashMap questionsSelections; + private int maxQuestionNumber; + + public SimpleCategoriesData() { + sharedDefaults = new int[0]; + questionsSelections = new HashMap(); + maxQuestionNumber = -1; + } + + public void setSharedDefaults(int[] sharedDefaultsIndices) { + this.sharedDefaults = sharedDefaultsIndices; + maxQuestionNumber = Math.max(maxQuestionNumber, maxInt(sharedDefaultsIndices)); + } + + public int[] getSharedDefaults() { + return sharedDefaults; + } + + public void setItem(SingleChosenAnswer key, int[] questionIndices) { + questionsSelections.put(key, questionIndices); + maxQuestionNumber = Math.max(maxQuestionNumber, maxInt(questionIndices)); + } + + public int[] getItem(SingleChosenAnswer key) { + return questionsSelections.get(key); + } + + public int getMaxQuestionNumber() { + return maxQuestionNumber; + } + + + private static int maxInt(int[] arr) { + int m = -1; + for (int i: arr) { + m = Math.max(i , m); + } + return m; + } + + public String toString () { + String s = "SimpleCategoriesData:\n"; + s += "shared : " + arrayToString(sharedDefaults) + "\n"; + s += "map : \n"; + for (SingleChosenAnswer key : questionsSelections.keySet()) { + s += key + " : " + arrayToString(questionsSelections.get(key)) + "\n"; + } + return s; + } + + private String arrayToString (int[] a) { + if (a.length == 0) { + return "[]"; + } + String s = "[" + a[0]; + for (int i = 1; i < a.length; ++i) { + s += ", " + a[i]; + } + return s + "]"; + } + +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/selector/SimpleListCategoriesSelector.java b/voting-booth/src/main/java/meerkat/voting/controller/selector/SimpleListCategoriesSelector.java new file mode 100644 index 0000000..31911e6 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/selector/SimpleListCategoriesSelector.java @@ -0,0 +1,121 @@ +package meerkat.voting.controller.selector; + +import jdk.nashorn.internal.runtime.regexp.joni.exception.ValueException; +import meerkat.protobuf.Voting.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; + +/** + * Created by hai on 02/05/16. + */ +public class SimpleListCategoriesSelector extends QuestionSelector { + protected final static Logger logger = LoggerFactory.getLogger(SimpleListCategoriesSelector.class); + private boolean[] isSelected; + + public SimpleListCategoriesSelector(Object selectionData, BallotQuestion[] allBallotQuestions) { + super(selectionData, allBallotQuestions); + initializeAttributes (allBallotQuestions); + } + + public SimpleListCategoriesSelector(Object selectionData, ArrayList allBallotQuestions) { + super(selectionData, null); + BallotQuestion[] questionsArr = new BallotQuestion[allBallotQuestions.size()]; + for (int i = 0; i < questionsArr.length; ++i) { + questionsArr[i] = allBallotQuestions.get(i); + } + initializeAttributes (questionsArr); + } + + private void initializeAttributes (BallotQuestion[] allBallotQuestions) { + assert selectionData instanceof SimpleCategoriesData; + this.allBallotQuestions = allBallotQuestions; + SimpleCategoriesData data = (SimpleCategoriesData) selectionData; + int maxQuestionNumber = data.getMaxQuestionNumber(); + int length = allBallotQuestions.length; + if (maxQuestionNumber >= length) { + String errorMessage = "Selection data refers to question index " + maxQuestionNumber + " while we have only " + length + " questions totally"; + logger.error(errorMessage); + throw new ValueException(errorMessage); + } + isSelected = new boolean[allBallotQuestions.length]; + } + + + @Override + public int getChannelIdentifier(BallotAnswer[] channelChoiceAnswers) { + return 0; + } + + @Override + public BallotQuestion[] selectQuestionsForVoter(BallotAnswer[] channelChoiceAnswers) { + SimpleCategoriesData data = (SimpleCategoriesData)selectionData; + java.util.Arrays.fill(isSelected, false); + markSelected(data.getSharedDefaults()); + markSelectionsOfVoterChannelChoice (channelChoiceAnswers); + int[] questionIndices = extractSelectedIndices(); + BallotQuestion[] selectedQuestions = new BallotQuestion[questionIndices.length]; + for (int i = 0; i < questionIndices.length; ++i) { + selectedQuestions[i] = allBallotQuestions[questionIndices[i]]; + } + return selectedQuestions; + } + + private void markSelectionsOfVoterChannelChoice(BallotAnswer[] channelChoiceAnswers) { + SimpleCategoriesData data = (SimpleCategoriesData)selectionData; + for (int q = 0; q < channelChoiceAnswers.length; ++q) { + BallotAnswer ballotAnswer = channelChoiceAnswers[q]; + assertAnswerLengthIsOne(ballotAnswer, q); + SingleChosenAnswer key = new SingleChosenAnswer(q, ballotAnswer.getAnswer(0)); + assertKeyInSelectionData(key); + markSelected(data.getItem(key)); + } + } + + private void assertAnswerLengthIsOne (BallotAnswer ballotAnswer, int questionNumber) { + if (ballotAnswer.getAnswerCount() != 1) { + String errorMessage = "SimpleListCategoriesSelector expects a single answer for every channel choice question\n"; + errorMessage += "Answer to question number " + (questionNumber+1) + " is"; + for (long i : ballotAnswer.getAnswerList()) { + errorMessage += " " + i; + } + logger.error(errorMessage); + throw new ValueException(errorMessage); + } + } + + private void assertKeyInSelectionData(SingleChosenAnswer key) { + SimpleCategoriesData data = (SimpleCategoriesData)selectionData; + int[] questionListToAdd = data.getItem(key); + if (null == questionListToAdd) { + String errorMessage = "SimpleListCategoriesSelector could not resolve answer number " + key.answerNumber + " for question number " + key.questionNumber; + logger.error(errorMessage); + throw new ValueException(errorMessage); + } + } + + private void markSelected (int[] questionIndices) { + for (int i: questionIndices) { + isSelected[i] = true; + } + } + + private int[] extractSelectedIndices() { + int nSelected = 0; + for (boolean b: isSelected) { + if (b) { + nSelected++; + } + } + int[] res = new int[nSelected]; + int currIndex = 0; + for (int questionNumber = 0; questionNumber < isSelected.length; ++questionNumber) { + if (isSelected[questionNumber]) { + res[currIndex] = questionNumber; + currIndex++; + } + } + return res; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/controller/selector/SingleChosenAnswer.java b/voting-booth/src/main/java/meerkat/voting/controller/selector/SingleChosenAnswer.java new file mode 100644 index 0000000..fad377b --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/controller/selector/SingleChosenAnswer.java @@ -0,0 +1,31 @@ +package meerkat.voting.controller.selector; + +/** + * Created by hai on 02/05/16. + */ +public class SingleChosenAnswer { + public int questionNumber; + public long answerNumber; + + public SingleChosenAnswer(int questionNumber, long answerNumber) { + this.questionNumber = questionNumber; + this.answerNumber = answerNumber; + } + + public String toString() { + return "SingleChosenAnswer(" + questionNumber + ", " + answerNumber + ")"; + } + + public boolean equals (Object other) { + if (!(other instanceof SingleChosenAnswer)) { + return false; + } + SingleChosenAnswer o = (SingleChosenAnswer)other; + return (o.questionNumber == this.questionNumber) && (o.answerNumber == this.answerNumber); + } + + @Override + public int hashCode () { + return questionNumber*1000 + (int)answerNumber; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/encryptor/VBEncryptorImpl.java b/voting-booth/src/main/java/meerkat/voting/encryptor/VBEncryptorImpl.java new file mode 100644 index 0000000..51ffef0 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/encryptor/VBEncryptorImpl.java @@ -0,0 +1,14 @@ +package meerkat.voting.encryptor; + +import meerkat.protobuf.Voting.*; + +/** + * Created by hai on 26/04/16. + */ +public class VBEncryptorImpl implements VotingBoothEncryptor { + + @Override + public EncryptionAndSecrets encrypt(PlaintextBallot plaintextBallot) { + throw new UnsupportedOperationException(); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/VotingBoothEncryptor.java b/voting-booth/src/main/java/meerkat/voting/encryptor/VotingBoothEncryptor.java similarity index 79% rename from voting-booth/src/main/java/meerkat/voting/VotingBoothEncryptor.java rename to voting-booth/src/main/java/meerkat/voting/encryptor/VotingBoothEncryptor.java index cc9b7bc..e9a885f 100644 --- a/voting-booth/src/main/java/meerkat/voting/VotingBoothEncryptor.java +++ b/voting-booth/src/main/java/meerkat/voting/encryptor/VotingBoothEncryptor.java @@ -1,6 +1,5 @@ -package meerkat.voting; +package meerkat.voting.encryptor; -import com.sun.org.apache.xml.internal.security.encryption.EncryptedType; import meerkat.protobuf.Voting.*; /** @@ -20,8 +19,13 @@ public interface VotingBoothEncryptor { this.secrets = secrets; } - public EncryptedBallot getEncryptedBallot () { return encryptedBallot; } - public BallotSecrets getSecrets() { return secrets; } + public EncryptedBallot getEncryptedBallot () { + return encryptedBallot; + } + + public BallotSecrets getSecrets() { + return secrets; + } } diff --git a/voting-booth/src/main/java/meerkat/voting/BallotOutputDevice.java b/voting-booth/src/main/java/meerkat/voting/output/BallotOutputDevice.java similarity index 53% rename from voting-booth/src/main/java/meerkat/voting/BallotOutputDevice.java rename to voting-booth/src/main/java/meerkat/voting/output/BallotOutputDevice.java index 0199523..8d1f7f1 100644 --- a/voting-booth/src/main/java/meerkat/voting/BallotOutputDevice.java +++ b/voting-booth/src/main/java/meerkat/voting/output/BallotOutputDevice.java @@ -1,4 +1,4 @@ -package meerkat.voting; +package meerkat.voting.output; import com.google.common.util.concurrent.FutureCallback; import meerkat.protobuf.Voting.*; @@ -12,28 +12,29 @@ public interface BallotOutputDevice { /** * Output the encrypted ballot. This is a commitment before voter chooses casting or auditing * @param encryptedBallot - the encrypted ballot to commit to - * @param callback - a callback object through which to return a success flag + * @param callback - a callback object which expects no return value */ - public void commitToBallot(EncryptedBallot encryptedBallot, FutureCallback callback); + public void commitToBallot(PlaintextBallot plaintextBallot, + EncryptedBallot encryptedBallot, + FutureCallback callback); /** * Voter chose 'audit'. Output the ballot secrets to prove correctness of the encryption. - * @param encryptedBallot - the encrypted ballot * @param ballotSecrets - the secrtes of the encryption - * @param callback - a callback object through which to return a success flag + * @param callback - a callback object which expects no return value */ - public void audit(EncryptedBallot encryptedBallot, BallotSecrets ballotSecrets, FutureCallback callback); + public void audit(BallotSecrets ballotSecrets, FutureCallback callback); /** * Voter chose 'cast'. Finalize the ballot for use in the polling station - * @param callback - a callback object through which to return a success flag + * @param callback - a callback object which expects no return value */ - public void castBallot(FutureCallback callback); + public void castBallot(FutureCallback callback); /** * Cancelling the current ballot. This clears the state of the OutputDevice if the implementation has any such state. - * @param callback - a callback object through which to return a success flag + * @param callback - a callback object which expects no return value */ - public void cancelBallot(FutureCallback callback); + public void cancelBallot(FutureCallback callback); } diff --git a/voting-booth/src/main/java/meerkat/voting/output/SystemConsoleOutputDevice.java b/voting-booth/src/main/java/meerkat/voting/output/SystemConsoleOutputDevice.java new file mode 100644 index 0000000..e1556d6 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/output/SystemConsoleOutputDevice.java @@ -0,0 +1,98 @@ +package meerkat.voting.output; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.ByteString; +import meerkat.protobuf.Crypto.*; +import meerkat.protobuf.Voting.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A toy OutputDevice class + * outputs everything simply to the System console + */ +public class SystemConsoleOutputDevice implements BallotOutputDevice { + + private Logger logger; + + public SystemConsoleOutputDevice () { + logger = LoggerFactory.getLogger(SystemConsoleOutputDevice.class); + logger.info("A SystemConsoleOutputDevice is constructed"); + } + + /* + * Returns the UTF8 decoding of byte-string data + */ + private static String bytesToString(ByteString data) { + return data.toStringUtf8(); + } + + @Override + public void commitToBallot(PlaintextBallot plaintextBallot, + EncryptedBallot encryptedBallot, + FutureCallback callback) { + logger.debug("entered method commitToBallot"); + long plaintextSerialNumber = plaintextBallot.getSerialNumber(); + System.out.println("Commitment of Ballot #" + plaintextSerialNumber + " (plaintext):"); + long encryptedSerialNumber = encryptedBallot.getSerialNumber(); + System.out.println("Commitment of Ballot #" + encryptedSerialNumber + " (ciphertext):"); + if (plaintextSerialNumber != encryptedSerialNumber) { + logger.error("plaintext and encryption serial numbers do not match!! plaintext# = " + + plaintextSerialNumber + ", ciphertext# = " + encryptedSerialNumber); + } + ByteString encryptedData = encryptedBallot.getData().getData(); + System.out.println(bytesToString(encryptedData)); + callback.onSuccess(null); + } + + @Override + public void audit(BallotSecrets ballotSecrets, FutureCallback callback) { + logger.debug("entered method audit"); + System.out.println("Auditing"); + printPlaintextBallot (ballotSecrets.getPlaintextBallot()); + printEncryptionRandomness (ballotSecrets.getEncryptionRandomness()); + printRandomnessGenerationProof (ballotSecrets.getProof()); + callback.onSuccess(null); + } + + private void printPlaintextBallot (PlaintextBallot plaintextBallot) { + long plaintextSerialNumber = plaintextBallot.getSerialNumber(); + System.out.println("Plaintext serial number = " + plaintextSerialNumber); + + System.out.println("Answers = "); + for (BallotAnswer ballotAnswer : plaintextBallot.getAnswersList()) { + String printableAnswer = ""; + for (long n : ballotAnswer.getAnswerList()) { + printableAnswer += n + " "; + } + System.out.println(printableAnswer); + } + } + + private void printEncryptionRandomness (EncryptionRandomness encryptionRandomness) { + System.out.println("Encryption Randomness = "); + ByteString data = encryptionRandomness.getData(); + System.out.println(bytesToString(data)); + } + + private void printRandomnessGenerationProof (RandomnessGenerationProof proof) { + System.out.println("Proof of randomness generation:"); + ByteString data = proof.getData(); + System.out.println(bytesToString(data)); + } + + + @Override + public void castBallot(FutureCallback callback) { + logger.debug("entered method castBallot"); + System.out.println("Ballot finalized for casting!"); + callback.onSuccess(null); + } + + @Override + public void cancelBallot(FutureCallback callback) { + logger.debug("entered method cancelBallot"); + System.out.println("Ballot cancelled!"); + callback.onSuccess(null); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/StorageManager.java b/voting-booth/src/main/java/meerkat/voting/storage/StorageManager.java similarity index 51% rename from voting-booth/src/main/java/meerkat/voting/StorageManager.java rename to voting-booth/src/main/java/meerkat/voting/storage/StorageManager.java index 0fd18ca..9bf7764 100644 --- a/voting-booth/src/main/java/meerkat/voting/StorageManager.java +++ b/voting-booth/src/main/java/meerkat/voting/storage/StorageManager.java @@ -1,7 +1,9 @@ -package meerkat.voting; +package meerkat.voting.storage; import meerkat.protobuf.Voting.*; +import java.io.IOException; + /** * An interface for the storage component of the voting booth */ @@ -14,8 +16,17 @@ public interface StorageManager { public boolean isAdminHardwareKeyInserted(); /** - * load the election params from a file. + * load the election params from the storage. * @return the current election params + * @throws IOException */ - public ElectionParams readElectionParams (); + public ElectionParams readElectionParams () throws IOException; + + /** + * write the election parameters protobuf to the storage + * @param params ElectionParams protobuf to save + * @throws IOException + */ + public void writeElectionParams(ElectionParams params) throws IOException; + } diff --git a/voting-booth/src/main/java/meerkat/voting/storage/StorageManagerMockup.java b/voting-booth/src/main/java/meerkat/voting/storage/StorageManagerMockup.java new file mode 100644 index 0000000..23c89cf --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/storage/StorageManagerMockup.java @@ -0,0 +1,67 @@ +package meerkat.voting.storage; + +import meerkat.protobuf.Voting.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * A mockup for the StorageManager interface + * Currently keeps the ElectionParams in a file in the user's home-directory + */ +public class StorageManagerMockup implements StorageManager { + + private boolean adminHardwareKeyInserted; + private Logger logger; + private String electionParamFullFilename = "~/meerkat_election_params_tempfile.dat"; + + public StorageManagerMockup () { + logger = LoggerFactory.getLogger(StorageManagerMockup.class); + logger.info("A StorageManagerMockup is constructed"); + this.adminHardwareKeyInserted = false; + } + + @Override + public boolean isAdminHardwareKeyInserted() { + logger.info("Entered method isAdminHardwareKeyInserted"); + logger.warn("isAdminHardwareKeyInserted is not yet fully implemented. It does not check the file system. " + + "Rather it just returns a private boolean member"); + return adminHardwareKeyInserted; + } + + @Override + public ElectionParams readElectionParams() throws IOException { + logger.info("Entered method readElectionParams"); + ElectionParams params; + try { + FileInputStream inputStream = new FileInputStream(electionParamFullFilename); + params = ElectionParams.parseFrom(inputStream); + inputStream.close(); + logger.info ("Successfully read election parameter protobuf from a file"); + } + catch (IOException e) { + logger.error("Could not read from the election parameter file: '" + electionParamFullFilename + "'."); + throw e; + } + return params; + } + + @Override + public void writeElectionParams(ElectionParams params) throws IOException { + logger.info("Entered method writeElectionParams"); + try { + FileOutputStream output = new FileOutputStream(electionParamFullFilename); + params.writeTo(output); + output.close(); + logger.info ("Successfully wrote election parameter protobuf to a file"); + } + catch (IOException e) { + logger.error("Could not write to the election parameter file: '" + electionParamFullFilename + "'."); + throw e; + } + } + +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/SystemConsoleUI.java b/voting-booth/src/main/java/meerkat/voting/ui/SystemConsoleUI.java new file mode 100644 index 0000000..4b6486e --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/SystemConsoleUI.java @@ -0,0 +1,393 @@ +package meerkat.voting.ui; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.ByteString; +import meerkat.protobuf.Voting.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Date; +import java.util.StringTokenizer; +import java.util.Timer; +import java.util.concurrent.LinkedBlockingQueue; + +import meerkat.voting.controller.callbacks.*; +import meerkat.voting.ui.uicommands.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.lang.System.in; + + +public class SystemConsoleUI implements VotingBoothUI, Runnable { + + private BufferedReader bufferedReader; + private LinkedBlockingQueue queue; + private final Logger logger; + private final int tickDurationInMillisec = 10; + private Date startWaitingTime; + + + public SystemConsoleUI() { + logger = LoggerFactory.getLogger(SystemConsoleUI.class); + logger.info("A VB UI console is constructed"); + queue = new LinkedBlockingQueue<>(); + bufferedReader = new BufferedReader(new InputStreamReader(in)); + + startWaitingTime = null; + Timer timer = new Timer(); + timer.scheduleAtFixedRate(new TickerTimerTask(queue), new Date(), tickDurationInMillisec); + } + + + public void run () { + logger.info("entered the voting flow"); + while (true) { + try { + UICommand task = queue.take(); + handleSingleTask (task); + } + catch (InterruptedException e) { + logger.warn ("Interrupted while reading from task queue " + e); + } + } + } + + + private void handleSingleTask (UICommand task) { + if (!(task instanceof TickCommand)) { + if (startWaitingTime != null) { + stopWaiting(); + } + } + + if (task instanceof StartSessionUICommand) { + doShowWelcomeScreen((StartSessionUICommand)task); + return; + } + else if (task instanceof ChannelChoiceUICommand) { + doAskChannelChoiceQuestions((ChannelChoiceUICommand)task); + return; + } + else if (task instanceof RaceVotingUICommand) { + doAskVotingQuestions((RaceVotingUICommand)task); + return; + } + else if (task instanceof CastOrAuditUICommand) { + doCastOrAudit ((CastOrAuditUICommand)task); + return; + } + else if (task instanceof HaltCommand) { + doHalt((HaltCommand)task); + return; + } + else if (task instanceof WaitForFinishCommand) { + doWaitForFinish((WaitForFinishCommand)task); + return; + } + else if (task instanceof TickCommand) { + doTick (); + return; + } + else { + String errorMessage = "handleSingleTask: unknown type of UICommand received: " + + task.getClass().getName(); + logger.error(errorMessage); + throw new RuntimeException(errorMessage); + } + } + + + @Override + public void startNewVoterSession(FutureCallback callback) { + logger.debug("UI interface call to startNewVoterSession"); + queue.add(new StartSessionUICommand((ControllerCallback)callback)); + } + + private void doShowWelcomeScreen(StartSessionUICommand task) { + logger.debug("UI entered doShowWelcomeScreen"); + System.out.println("Welcome, new voter!"); + waitForEnter(null); + ControllerCallback callback = task.getCallback(); + assert (callback instanceof NewVoterCallback); + ((NewVoterCallback)callback).onSuccess(null); + } + + private void stopWaiting () { + System.out.println (); + startWaitingTime = null; + + } + private void waitForEnter(String message) { + if (message != null) { + System.out.println(message); + } + System.out.println("\nPress ENTER to proceed.\n"); + String t = null; + while (t == null) { + try { + t = readInputLine(); + } + catch (IOException e) { + String errorMessage = "waitForEnter: threw an IOException: " + e; + logger.error(errorMessage); + System.err.println(errorMessage); + } + } + } + + @Override + public void chooseChannel(BallotQuestion[] questions, FutureCallback callback) { + logger.debug("UI interface call to chooseChannel"); + ChannelChoiceUICommand task = new ChannelChoiceUICommand(questions, (ControllerCallback)callback); + queue.add(task); + } + + private void doAskChannelChoiceQuestions (ChannelChoiceUICommand task) { + logger.debug("UI: doAskChannelChoiceQuestions"); + System.out.println("Showing questions for choosing channel:\n"); + try { + BallotAnswer[] answers = askVoterForAnswers(task.getQuestions()); + task.getCallback().onSuccess(answers); + } + catch (IOException e) { + String errorMessage = "Channel choice failed due to IOException: " + e; + logger.error (errorMessage); + System.err.println(errorMessage); + task.getCallback().onFailure(e); + } + } + + @Override + public void askVoterQuestions(BallotQuestion[] questions, FutureCallback callback) { + logger.debug("UI interface call to chooseChannel"); + queue.add(new RaceVotingUICommand(questions, (ControllerCallback)callback)); + } + + private void doAskVotingQuestions (RaceVotingUICommand task) { + logger.debug("UI: doAskVotingQuestions"); + System.out.println("Showing questions for race voting:\n"); + try { + BallotAnswer[] answers = askVoterForAnswers(task.getQuestions()); + task.getCallback().onSuccess(answers); + } + catch (IOException e) { + String errorMessage = "Asking voting questions failed due to IOException: " + e; + logger.error (errorMessage); + System.err.println(errorMessage); + task.getCallback().onFailure(e); + } + } + + @Override + public void castOrAudit(FutureCallback callback) { + logger.debug("UI interface call to castOrAudit"); + queue.add(new CastOrAuditUICommand((ControllerCallback)callback)); + } + + private void doCastOrAudit(CastOrAuditUICommand task) { + logger.debug("UI entered doCastOrAudit"); + System.out.println ("Finalizing your vote. Do you wish to (C)ast or (A)udit?"); + + FinalizeBallotChoice fChoice; + + try { + String s = readInputLine(); + if (s.equals("cast") || s.equals("c")) { + fChoice = FinalizeBallotChoice.CAST; + } + else if (s.equals("audit") || s.equals("a")) { + fChoice = FinalizeBallotChoice.AUDIT; + } + else { + throw new IllegalArgumentException("UI could not understand the answer for cast/audit question '" + s + "'"); + } + ControllerCallback callback = task.getCallback(); + assert (callback instanceof CastOrAuditCallback); + ((CastOrAuditCallback)callback).onSuccess(fChoice); + } + catch (IllegalArgumentException|IOException e) { + String errorMessage = "doCastOrAudit: some error with reading input from console. details: " + e; + logger.error(errorMessage); + task.getCallback().onFailure(e); + } + } + + + @Override + public void notifyVoterToWaitForFinish(UIElement message, FutureCallback callback) { + logger.debug("UI interface call to notifyVoterToWaitForFinish"); + queue.add(new WaitForFinishCommand(message, (ControllerCallback)callback)); + } + + public void doWaitForFinish (WaitForFinishCommand task) { + logger.debug("UI entered doWaitForFinish"); + + startWaitingTime = new Date(); + + UIElement message = task.getMessage(); + String messageString; + if (message.getType() != UIElementDataType.TEXT) { + messageString = "Default message: encountered an error. System halting"; + } else { + messageString = bytesToString(message.getData()); + } + System.out.println(messageString); + System.out.print ("Waiting : ."); + } + + @Override + public void showErrorMessageAndHalt(UIElement errorMessage, FutureCallback callback) { + logger.debug("UI interface call to showErrorMessageAndHalt"); + queue.add(new HaltCommand(errorMessage, (ControllerCallback)callback)); + } + + private void doHalt (HaltCommand task) { + logger.debug("UI entered doHalt"); + + UIElement errorMessage = task.getErrorMessage(); + String errorMessageString; + if (errorMessage.getType() != UIElementDataType.TEXT) { + errorMessageString = "Default message: encountered an error. System halting"; + } else { + errorMessageString = bytesToString(errorMessage.getData()); + } + System.out.println(errorMessageString); + + while (true) { + try { + wait(); + } catch (InterruptedException e) { + logger.error("UI halt has been interrupted. Details: " + e); + ControllerCallback callback = task.getCallback(); + assert callback instanceof HaltCallback; + ((HaltCallback) callback).onFailure(e); + } + } + } + + + @Override + public void showErrorMessageWithButtons(UIElement errorMessage, UIElement[] buttonLabels, FutureCallback callback) { + throw new UnsupportedOperationException(); + } + + private void doTick () { + if (startWaitingTime != null) { + System.out.print ("."); // still waiting + } + } + + private String readInputLine() throws IOException{ + String s; + try { + s = this.bufferedReader.readLine(); + if (null == s) { + throw new IOException(); + } + } catch (IOException e) { + String errorMessage = "readInputLine: some error with reading input from console. details: " + e; + logger.error(errorMessage); + throw new IOException(e); + } + return s; + } + + + private void assertQuestionsAreValid (BallotQuestion[] questions) { + for (int index = 0; index < questions.length; ++index) { + BallotQuestion question = questions[index]; + if (question.getIsMandatory()) { + String errorMessage = "askVoterQuestions: question number " + index + " is marked as mandatory"; + logger.error(errorMessage); + throw new UnsupportedOperationException(errorMessage); + } + if (!isQuestionOnlyText(question)) { + String errorMessage = "askVoterQuestions: question number " + index + " is not only text"; + logger.error(errorMessage); + throw new UnsupportedOperationException(errorMessage); + } + } + } + + private BallotAnswer[] askVoterForAnswers(BallotQuestion[] questions) throws IOException { + + assertQuestionsAreValid (questions); + + BallotAnswer answers[] = new BallotAnswer[questions.length]; + int index = 0; + while (index < questions.length) { + BallotQuestion question = questions[index]; + System.out.println("Question number " + index); + showQuestionOnScreen(question); + + System.out.println("UI screen: Enter your answer. You can also type '(b)ack' or '(c)ancel' or '(s)kip"); + String s = readInputLine(); + + if ((s.equals("cancel") || s.equals("c")) || (index == 0 && (s.equals("back") || s.equals("b")))) { + return null; + } + else if (s.equals("back") || s.equals("b")) { + --index; + } + else if (s.equals("skip") || s.equals("s")) { + answers[index] = translateStringAnswerToProtoBufMessageAnswer(""); + ++index; + } + else { + answers[index] = translateStringAnswerToProtoBufMessageAnswer(s); + ++index; + } + } + return answers; + } + + //show question on the screen for the voter + private void showQuestionOnScreen(BallotQuestion question) { + if (!isQuestionOnlyText(question)) { + System.err.println("debug: an element in question is not of TEXT type"); + throw new UnsupportedOperationException(); + } + + System.out.println("Question text: " + bytesToString(question.getQuestion().getData())); + System.out.println("Description: " + bytesToString(question.getDescription().getData())); + int answerIndex = 0; + for (UIElement answer : question.getAnswerList()) { + ++answerIndex; + System.out.println("Answer " + answerIndex + ": " + bytesToString(answer.getData())); + } + } + + private boolean isQuestionOnlyText (BallotQuestion question) { + boolean isText = true; + if (question.getQuestion().getType() != UIElementDataType.TEXT + || question.getDescription().getType() != UIElementDataType.TEXT) { + isText = false; + } + for (UIElement answer : question.getAnswerList()) { + if (answer.getType() != UIElementDataType.TEXT) { + isText = false; + } + } + return isText; + } + + + private BallotAnswer translateStringAnswerToProtoBufMessageAnswer(String s) { + BallotAnswer.Builder bab = BallotAnswer.newBuilder(); + StringTokenizer st = new StringTokenizer(s); + while (st.hasMoreTokens()) { + bab.addAnswer(Integer.parseInt(st.nextToken())); + } + return bab.build(); + } + + /* + * Returns the UTF8 decoding of byte-string data + */ + private static String bytesToString(ByteString data) { + return data.toStringUtf8(); + } + +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/TickerTimerTask.java b/voting-booth/src/main/java/meerkat/voting/ui/TickerTimerTask.java new file mode 100644 index 0000000..a293e5f --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/TickerTimerTask.java @@ -0,0 +1,25 @@ +package meerkat.voting.ui; + +import meerkat.voting.ui.uicommands.TickCommand; +import meerkat.voting.ui.uicommands.UICommand; + +import java.util.TimerTask; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Created by hai on 25/04/16. + */ +class TickerTimerTask extends TimerTask { + private LinkedBlockingQueue uiQueue; + public TickerTimerTask (LinkedBlockingQueue uiQueue) { + this.uiQueue = uiQueue; + } + + @Override + public void run() { + UICommand t = uiQueue.peek(); + if ((t != null) && !(t instanceof TickCommand)) { + uiQueue.add(new TickCommand(null)); + } + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/VotingBoothUI.java b/voting-booth/src/main/java/meerkat/voting/ui/VotingBoothUI.java similarity index 83% rename from voting-booth/src/main/java/meerkat/voting/VotingBoothUI.java rename to voting-booth/src/main/java/meerkat/voting/ui/VotingBoothUI.java index c2ccfea..ae96f7b 100644 --- a/voting-booth/src/main/java/meerkat/voting/VotingBoothUI.java +++ b/voting-booth/src/main/java/meerkat/voting/ui/VotingBoothUI.java @@ -1,30 +1,31 @@ -package meerkat.voting; +package meerkat.voting.ui; import com.google.common.util.concurrent.FutureCallback; import meerkat.protobuf.Voting.*; + /** * An interface for the user interface component of the voting booth */ public interface VotingBoothUI { - public static enum FinalizeBallotChoice { + public enum FinalizeBallotChoice { CAST, AUDIT } /** * Starts a new session for a voter. Presents whatever initial info is decided to show at the beginning - * @param callback - a boolean future callback to return success when done + * @param callback - a boolean future callback to return when done */ - public void startNewVoterSession (FutureCallback callback); + public void startNewVoterSession (FutureCallback callback); /** * Present a question to the voter to decide on his voting channel. - * @param question a question to determine the right voting channel for this voter - * @param callback that's where we store the channel for the current voter + * @param questions questions to determine the right voting channel for this voter + * @param callback that's where we store the answers to decide channel upon for the current voter */ - public void chooseChannel (BallotQuestion question, FutureCallback callback); + public void chooseChannel (BallotQuestion[] questions, FutureCallback callback); /** * Presents the set of questions to the voter. Collect all his responses. diff --git a/voting-booth/src/main/java/meerkat/voting/ui/uicommands/CastOrAuditUICommand.java b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/CastOrAuditUICommand.java new file mode 100644 index 0000000..993f64b --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/CastOrAuditUICommand.java @@ -0,0 +1,12 @@ +package meerkat.voting.ui.uicommands; + +import meerkat.voting.controller.callbacks.ControllerCallback; + +/** + * Created by hai on 21/04/16. + */ +public class CastOrAuditUICommand extends UICommand { + public CastOrAuditUICommand(ControllerCallback callback) { + super(callback); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/uicommands/ChannelChoiceUICommand.java b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/ChannelChoiceUICommand.java new file mode 100644 index 0000000..22d2ff3 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/ChannelChoiceUICommand.java @@ -0,0 +1,22 @@ +package meerkat.voting.ui.uicommands; + +import meerkat.protobuf.Voting.*; +import meerkat.voting.controller.callbacks.ControllerCallback; + +/** + * Created by hai on 18/04/16. + */ +public class ChannelChoiceUICommand extends UICommand { + + private final BallotQuestion[] questions; + + public ChannelChoiceUICommand(BallotQuestion[] questions, ControllerCallback callback) + { + super(callback); + this.questions = questions; + } + + public BallotQuestion[] getQuestions () { + return this.questions; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/uicommands/HaltCommand.java b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/HaltCommand.java new file mode 100644 index 0000000..5213354 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/HaltCommand.java @@ -0,0 +1,22 @@ +package meerkat.voting.ui.uicommands; + +import meerkat.protobuf.Voting.*; +import meerkat.voting.controller.callbacks.ControllerCallback; + +/** + * Created by hai on 18/04/16. + */ +public class HaltCommand extends UICommand { + + private final UIElement errorMessage; + + public HaltCommand(UIElement errorMessage, ControllerCallback callback) + { + super(callback); + this.errorMessage = errorMessage; + } + + public UIElement getErrorMessage () { + return this.errorMessage; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/uicommands/RaceVotingUICommand.java b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/RaceVotingUICommand.java new file mode 100644 index 0000000..31d2706 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/RaceVotingUICommand.java @@ -0,0 +1,22 @@ +package meerkat.voting.ui.uicommands; + +import meerkat.protobuf.Voting.*; +import meerkat.voting.controller.callbacks.ControllerCallback; + +/** + * Created by hai on 18/04/16. + */ +public class RaceVotingUICommand extends UICommand { + + private final BallotQuestion[] questions; + + public RaceVotingUICommand(BallotQuestion[] questions, ControllerCallback callback) + { + super(callback); + this.questions = questions; + } + + public BallotQuestion[] getQuestions () { + return this.questions; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/uicommands/StartSessionUICommand.java b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/StartSessionUICommand.java new file mode 100644 index 0000000..f1bc146 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/StartSessionUICommand.java @@ -0,0 +1,13 @@ +package meerkat.voting.ui.uicommands; + +import meerkat.voting.controller.callbacks.ControllerCallback; + +/** + * Created by hai on 18/04/16. + */ +public class StartSessionUICommand extends UICommand { + + public StartSessionUICommand(ControllerCallback callback) { + super(callback); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/uicommands/TickCommand.java b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/TickCommand.java new file mode 100644 index 0000000..3192f4c --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/TickCommand.java @@ -0,0 +1,14 @@ +package meerkat.voting.ui.uicommands; + +import meerkat.voting.controller.callbacks.ControllerCallback; + +/** + * Created by hai on 18/04/16. + */ +public class TickCommand extends UICommand { + + public TickCommand(ControllerCallback callback) { + super(callback); + assert null == callback; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/uicommands/UICommand.java b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/UICommand.java new file mode 100644 index 0000000..b22fdef --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/UICommand.java @@ -0,0 +1,16 @@ +package meerkat.voting.ui.uicommands; + +import meerkat.voting.controller.callbacks.ControllerCallback; + +//TODO: make this class generic +public abstract class UICommand { + protected final ControllerCallback callback; + + protected UICommand(ControllerCallback callback) { + this.callback = callback; + } + + public ControllerCallback getCallback () { + return this.callback; + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/uicommands/WaitForFinishCommand.java b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/WaitForFinishCommand.java new file mode 100644 index 0000000..fe9b1cc --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/uicommands/WaitForFinishCommand.java @@ -0,0 +1,23 @@ +package meerkat.voting.ui.uicommands; + +import meerkat.protobuf.Voting.*; +import meerkat.voting.controller.callbacks.ControllerCallback; + +/** + * Created by hai on 18/04/16. + */ + +public class WaitForFinishCommand extends UICommand { + + private final UIElement message; + + public WaitForFinishCommand(UIElement message, ControllerCallback callback) + { + super(callback); + this.message = message; + } + + public UIElement getMessage () { + return this.message; + } +}