diff --git a/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothImpl.java b/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothImpl.java index a71dadd..92c1e5e 100644 --- a/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothImpl.java +++ b/voting-booth/src/main/java/meerkat/voting/controller/VotingBoothImpl.java @@ -24,29 +24,30 @@ import java.util.concurrent.LinkedBlockingQueue; */ public class VotingBoothImpl implements VotingBoothController { + private final Logger logger = LoggerFactory.getLogger(VotingBoothImpl.class);; + + // the component interfaces of the Voting Booth private BallotOutputDevice outputDevice; private VBCryptoManager crypto; private VotingBoothUI ui; private StorageManager storageManager; + + // election details and info private List questionsForChoosingChannel; private List questions; private QuestionSelector questionSelector; private Map systemMessages; - private LinkedBlockingQueue queue; - - private Logger logger; - + // state private ControllerState state; private volatile boolean shutDownHasBeenCalled; - + private LinkedBlockingQueue queue; protected final int MAX_REQUEST_IDENTIFIER = 100000; private static int requestCounter = 0; - + // a simple constructor public VotingBoothImpl () { - logger = LoggerFactory.getLogger(VotingBoothImpl.class); logger.info("A VotingBoothImpl is constructed"); shutDownHasBeenCalled = false; queue = new LinkedBlockingQueue<>(); @@ -59,11 +60,14 @@ public class VotingBoothImpl implements VotingBoothController { VotingBoothUI vbUI, StorageManager vbStorageManager) throws IOException { logger.info("init is called"); + + // keep pointers to the VB components this.outputDevice = outputDevice; this.crypto = vbCrypto; this.ui = vbUI; this.storageManager = vbStorageManager; + // store election details and info ElectionParams electionParams; try { logger.info("init: reading election params"); @@ -92,6 +96,10 @@ public class VotingBoothImpl implements VotingBoothController { } + /** + * a method for running the Voting flow of the VB (in contrast to the admin-setup flow + * It simply loops: takes the next command in its inner queue and handles it + */ private void runVotingFlow () { logger.info("entered the voting flow"); @@ -99,11 +107,11 @@ public class VotingBoothImpl implements VotingBoothController { while (! wasShutDownCalled()) { try { - ControllerCommand task = queue.take(); - handleSingleTask (task); + ControllerCommand Command = queue.take(); + handleSingleCommand(Command); } catch (InterruptedException e) { - logger.warn ("Interrupted while reading from task queue " + e); + logger.warn ("Interrupted while reading from command queue " + e); } } } @@ -117,41 +125,48 @@ public class VotingBoothImpl implements VotingBoothController { outputDevice.callShutDown(); } - private void handleSingleTask (ControllerCommand task) { - if (task.getBallotSerialNumber() != state.currentBallotSerialNumber && !(task instanceof RestartVotingCommand)) { + /** + * this method decides upon a given command if to ignore it (if it has an old serial number) or to handle it + * If we choose to handle it, then it simply calls the matching method which handles this type of command + * @param command a command to handle next (probably from the inner command queue) + */ + private void handleSingleCommand(ControllerCommand command) { + // check if the command is old and should be ignored + if (command.getBallotSerialNumber() != state.currentBallotSerialNumber && !(command instanceof RestartVotingCommand)) { // probably an old command relating to some old ballot serial number. Simply log it and ignore it. - String errorMessage = "handleSingleTask: received a task too old. " + - task.getBallotSerialNumber() + " " + state.currentBallotSerialNumber; + String errorMessage = "handleSingleCommand: received a task too old. " + + command.getBallotSerialNumber() + " " + state.currentBallotSerialNumber; logger.debug(errorMessage); return; } - if (task instanceof RestartVotingCommand) { + // decide which method to run according to the command type + if (command instanceof RestartVotingCommand) { doRestartVoting (); } - else if (task instanceof ChannelChoiceCommand) { + else if (command instanceof ChannelChoiceCommand) { doChooseChannel(); } - else if (task instanceof ChannelDeterminedCommand) { - doSetChannelAndAskQuestions ((ChannelDeterminedCommand)task); + else if (command instanceof ChannelDeterminedCommand) { + doSetChannelAndAskQuestions ((ChannelDeterminedCommand)command); } - else if (task instanceof ChooseFinalizeOptionCommand) { + else if (command instanceof ChooseFinalizeOptionCommand) { doChooseFinalizeOption(); } - else if (task instanceof CastCommand) { + else if (command instanceof CastCommand) { doFinalize(false); } - else if (task instanceof AuditCommand) { + else if (command instanceof AuditCommand) { doFinalize(true); } - else if (task instanceof EncryptAndCommitBallotCommand) { - doCommit ((EncryptAndCommitBallotCommand)task); + else if (command instanceof EncryptAndCommitBallotCommand) { + doCommit ((EncryptAndCommitBallotCommand)command); } - else if (task instanceof ReportErrorCommand) { - doReportErrorAndForceRestart((ReportErrorCommand)task); + else if (command instanceof ReportErrorCommand) { + doReportErrorAndForceRestart((ReportErrorCommand)command); } else { - logger.error("handleSingleTask: unknown type of ControllerCommand received: " + task.getClass().getName()); + logger.error("handleSingleCommand: unknown type of ControllerCommand received: " + command.getClass().getName()); doReportErrorAndForceRestart(systemMessages.get(StorageManager.SOMETHING_WRONG_MESSAGE)); } } @@ -166,17 +181,28 @@ public class VotingBoothImpl implements VotingBoothController { //TODO: add commands to actually shut down the machine } + /** + * a method to execute a Restart Voting Command + */ private void doRestartVoting () { queue.clear(); state.clearAndResetState(VBState.NEW_VOTER); - ui.startNewVoterSession(new NewVoterCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, this.queue)); } - private void doReportErrorAndForceRestart(ReportErrorCommand task) { - doReportErrorAndForceRestart(task.getErrorMessage()); + /** + * a (overloaded) method to execute a Report Error Command. + * It actually just runs the overloaded version of this method with the error message inside the command + * @param command the command has the info of the error message to report + */ + private void doReportErrorAndForceRestart(ReportErrorCommand command) { + doReportErrorAndForceRestart(command.getErrorMessage()); } + /** + * a (overloaded) method to report an error message to the voter + * @param errorMessage message to show the voter + */ private void doReportErrorAndForceRestart(UIElement errorMessage) { queue.clear(); state.clearAndResetState(VBState.FATAL_ERROR_FORCE_NEW_VOTER); @@ -187,6 +213,10 @@ public class VotingBoothImpl implements VotingBoothController { this.queue)); } + /** + * a method to execute a Channel Choice Command + * it notifies the UI to present the channel choice questions to the voter + */ private void doChooseChannel () { if (state.stateIdentifier == VBState.NEW_VOTER) { logger.debug("doing chooseChannel"); @@ -203,11 +233,17 @@ public class VotingBoothImpl implements VotingBoothController { } } - private void doSetChannelAndAskQuestions (ChannelDeterminedCommand task) { + /** + * a method to execute a Channel Determined Command + * (this actually sets the channel now after the voter has answered the channel choice questions) + * It then determines the race questions for the voter, and notifies the UI to present them to the voter + * @param command details of the voter's answers on the channel choice questions + */ + private void doSetChannelAndAskQuestions (ChannelDeterminedCommand command) { if (state.stateIdentifier == VBState.CHOOSE_CHANNEL) { logger.debug("doing set channel and ask questions"); state.stateIdentifier = VBState.ANSWER_QUESTIONS; - List channelChoiceAnswers = task.channelChoiceAnswers; + List channelChoiceAnswers = command.channelChoiceAnswers; state.channelIdentifier = questionSelector.getChannelIdentifier(channelChoiceAnswers); state.channelSpecificQuestions = questionSelector.selectQuestionsForVoter(state.channelIdentifier); ui.askVoterQuestions(state.channelSpecificQuestions, @@ -222,7 +258,10 @@ public class VotingBoothImpl implements VotingBoothController { } } - + /** + * a method to execute a Do-Finalzie-Option Command + * notifies the UI to present the cast-or-audit question to the voter + */ private void doChooseFinalizeOption() { if (state.stateIdentifier == VBState.COMMITTING_TO_BALLOT) { logger.debug("doChooseFinalizeOption"); @@ -237,11 +276,17 @@ public class VotingBoothImpl implements VotingBoothController { // ignore this request } } - private void doCommit (EncryptAndCommitBallotCommand task) { + + /** + * a method to execute a Encrypt-and-Commit Command + * It sends a notification to commit to the output device + * @param command details of the voter's answers on the ballot questions + */ + private void doCommit (EncryptAndCommitBallotCommand command) { if (state.stateIdentifier == VBState.ANSWER_QUESTIONS) { logger.debug("doing commit"); try { - setBallotData(task); + setBallotData(command); ui.notifyVoterToWaitForFinish(systemMessages.get(StorageManager.WAIT_FOR_COMMIT_MESSAGE), new WaitForFinishCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, @@ -257,6 +302,9 @@ public class VotingBoothImpl implements VotingBoothController { } catch (SignatureException | IOException e) { logger.error("doCommit: encryption failed. exception: " + e); + + // in case the encryption failed for some unknown reason, we send the UI a notification + // to ask the voter whether he wants to retry or cancel the ballot UIElement errorMessage = systemMessages.get(StorageManager.ENCRYPTION_FAILED_MESSAGE); UIElement[] buttons = new UIElement[]{ systemMessages.get(StorageManager.RETRY_BUTTON), @@ -274,25 +322,41 @@ public class VotingBoothImpl implements VotingBoothController { } } - private void setBallotData (EncryptAndCommitBallotCommand task) throws IOException, SignatureException{ - if (! (task instanceof RetryEncryptAndCommitBallotCommand)) { + /** + * encrypt the ballot, and keep all info (plaintext, encryption and secrets) in the state's attributes + * @param command either an EncryptAndCommitBallotCommand if we encrypt the plaintext for the first time, or a RetryEncryptAndCommitBallotCommand if we already got here but encryption failed before + * @throws IOException problems in the encryption process + * @throws SignatureException problems in the digital signature process + */ + private void setBallotData (EncryptAndCommitBallotCommand command) throws IOException, SignatureException{ + // a Retry command is given only if we first got here, and later the encryption failed but the voter chose to retry + // in such a case the plaintext is already set from previous attempt + if (! (command instanceof RetryEncryptAndCommitBallotCommand)) { // this is not a retry attempt, so the plaintext is not set yet // otherwise, we have the plaintext from the previous encryption attempt state.plaintextBallot = PlaintextBallot.newBuilder() - .setSerialNumber(task.getBallotSerialNumber()) - .addAllAnswers(task.getVotingAnswers()) + .setSerialNumber(command.getBallotSerialNumber()) + .addAllAnswers(command.getVotingAnswers()) .build(); } + + // keep the encryption and the secrets we used for it EncryptionAndSecrets encryptionAndSecrets = crypto.encrypt(state.plaintextBallot); state.signedEncryptedBallot = encryptionAndSecrets.getSignedEncryptedBallot(); state.secrets = encryptionAndSecrets.getSecrets(); } + /** + * a method to execute a Cast Command or an Audit Command + * according to the flag, chooses which finalize ballot task to send to the output device + * @param auditRequested true if we wish to finalize by auditing. false if we finalize by casting the ballot + */ private void doFinalize (boolean auditRequested) { if (state.stateIdentifier == VBState.CAST_OR_AUDIT) { logger.debug("finalizing"); state.stateIdentifier = VBState.FINALIZING; if (auditRequested) { + // finalize by auditing ui.notifyVoterToWaitForFinish(systemMessages.get(StorageManager.WAIT_FOR_AUDIT_MESSAGE), new WaitForFinishCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, @@ -305,6 +369,7 @@ public class VotingBoothImpl implements VotingBoothController { systemMessages.get(StorageManager.OUTPUT_DEVICE_FAILURE_MESSAGE))); } else { + // finalize by casting the ballot ui.notifyVoterToWaitForFinish(systemMessages.get(StorageManager.WAIT_FOR_CAST_MESSAGE), new WaitForFinishCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, @@ -325,8 +390,7 @@ public class VotingBoothImpl implements VotingBoothController { - - + // an enum to keep the step (of the voting process) in which the VB is currently in private enum VBState { NEW_VOTER, CHOOSE_CHANNEL, @@ -339,6 +403,15 @@ public class VotingBoothImpl implements VotingBoothController { } + /** + * a class to keep and directly access all the details of the VB controller state. + * naming: + * - the (enum) state identifier of the current step + * - the chosen channel + * - all details of the ballot (both plaintext and encryption) + * - last request identifier (to one of the other component interfaces) + * - serial number of the current ballot + */ private class ControllerState { public VBState stateIdentifier; public byte[] channelIdentifier; @@ -359,7 +432,6 @@ public class VotingBoothImpl implements VotingBoothController { currentBallotSerialNumber = 0; } - private void clearPlaintext () { plaintextBallot = null; } @@ -379,6 +451,11 @@ public class VotingBoothImpl implements VotingBoothController { } + /** + * Creates a new request identifier to identify any call to one of the other component interfaces. + * We limit its value to MAX_REQUEST_IDENTIFIER, so the identifier is kept short. + * @return a new request identifier + */ private int generateRequestIdentifier() { ++requestCounter; if (requestCounter >= MAX_REQUEST_IDENTIFIER) {