Refactor: add comments to VotingBoothImpl.java, and rename tasks to commands (because they were two names of the same thing)

Signed-off-by: Hai Brenner <haibrenner@gmail.com>
vbdev2
Hai Brenner 2016-07-12 15:23:46 +03:00
parent 42d68b7ce8
commit 0956fa98d3
1 changed files with 118 additions and 41 deletions

View File

@ -24,29 +24,30 @@ import java.util.concurrent.LinkedBlockingQueue;
*/ */
public class VotingBoothImpl implements VotingBoothController { public class VotingBoothImpl implements VotingBoothController {
private final Logger logger = LoggerFactory.getLogger(VotingBoothImpl.class);;
// the component interfaces of the Voting Booth
private BallotOutputDevice outputDevice; private BallotOutputDevice outputDevice;
private VBCryptoManager crypto; private VBCryptoManager crypto;
private VotingBoothUI ui; private VotingBoothUI ui;
private StorageManager storageManager; private StorageManager storageManager;
// election details and info
private List<BallotQuestion> questionsForChoosingChannel; private List<BallotQuestion> questionsForChoosingChannel;
private List<BallotQuestion> questions; private List<BallotQuestion> questions;
private QuestionSelector questionSelector; private QuestionSelector questionSelector;
private Map<String, UIElement> systemMessages; private Map<String, UIElement> systemMessages;
private LinkedBlockingQueue<ControllerCommand> queue; // state
private Logger logger;
private ControllerState state; private ControllerState state;
private volatile boolean shutDownHasBeenCalled; private volatile boolean shutDownHasBeenCalled;
private LinkedBlockingQueue<ControllerCommand> queue;
protected final int MAX_REQUEST_IDENTIFIER = 100000; protected final int MAX_REQUEST_IDENTIFIER = 100000;
private static int requestCounter = 0; private static int requestCounter = 0;
// a simple constructor
public VotingBoothImpl () { public VotingBoothImpl () {
logger = LoggerFactory.getLogger(VotingBoothImpl.class);
logger.info("A VotingBoothImpl is constructed"); logger.info("A VotingBoothImpl is constructed");
shutDownHasBeenCalled = false; shutDownHasBeenCalled = false;
queue = new LinkedBlockingQueue<>(); queue = new LinkedBlockingQueue<>();
@ -59,11 +60,14 @@ public class VotingBoothImpl implements VotingBoothController {
VotingBoothUI vbUI, VotingBoothUI vbUI,
StorageManager vbStorageManager) throws IOException { StorageManager vbStorageManager) throws IOException {
logger.info("init is called"); logger.info("init is called");
// keep pointers to the VB components
this.outputDevice = outputDevice; this.outputDevice = outputDevice;
this.crypto = vbCrypto; this.crypto = vbCrypto;
this.ui = vbUI; this.ui = vbUI;
this.storageManager = vbStorageManager; this.storageManager = vbStorageManager;
// store election details and info
ElectionParams electionParams; ElectionParams electionParams;
try { try {
logger.info("init: reading election params"); 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 () { private void runVotingFlow () {
logger.info("entered the voting flow"); logger.info("entered the voting flow");
@ -99,11 +107,11 @@ public class VotingBoothImpl implements VotingBoothController {
while (! wasShutDownCalled()) { while (! wasShutDownCalled()) {
try { try {
ControllerCommand task = queue.take(); ControllerCommand Command = queue.take();
handleSingleTask (task); handleSingleCommand(Command);
} }
catch (InterruptedException e) { 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(); 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. // 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. " + String errorMessage = "handleSingleCommand: received a task too old. " +
task.getBallotSerialNumber() + " " + state.currentBallotSerialNumber; command.getBallotSerialNumber() + " " + state.currentBallotSerialNumber;
logger.debug(errorMessage); logger.debug(errorMessage);
return; return;
} }
if (task instanceof RestartVotingCommand) { // decide which method to run according to the command type
if (command instanceof RestartVotingCommand) {
doRestartVoting (); doRestartVoting ();
} }
else if (task instanceof ChannelChoiceCommand) { else if (command instanceof ChannelChoiceCommand) {
doChooseChannel(); doChooseChannel();
} }
else if (task instanceof ChannelDeterminedCommand) { else if (command instanceof ChannelDeterminedCommand) {
doSetChannelAndAskQuestions ((ChannelDeterminedCommand)task); doSetChannelAndAskQuestions ((ChannelDeterminedCommand)command);
} }
else if (task instanceof ChooseFinalizeOptionCommand) { else if (command instanceof ChooseFinalizeOptionCommand) {
doChooseFinalizeOption(); doChooseFinalizeOption();
} }
else if (task instanceof CastCommand) { else if (command instanceof CastCommand) {
doFinalize(false); doFinalize(false);
} }
else if (task instanceof AuditCommand) { else if (command instanceof AuditCommand) {
doFinalize(true); doFinalize(true);
} }
else if (task instanceof EncryptAndCommitBallotCommand) { else if (command instanceof EncryptAndCommitBallotCommand) {
doCommit ((EncryptAndCommitBallotCommand)task); doCommit ((EncryptAndCommitBallotCommand)command);
} }
else if (task instanceof ReportErrorCommand) { else if (command instanceof ReportErrorCommand) {
doReportErrorAndForceRestart((ReportErrorCommand)task); doReportErrorAndForceRestart((ReportErrorCommand)command);
} }
else { 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)); 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 //TODO: add commands to actually shut down the machine
} }
/**
* a method to execute a Restart Voting Command
*/
private void doRestartVoting () { private void doRestartVoting () {
queue.clear(); queue.clear();
state.clearAndResetState(VBState.NEW_VOTER); state.clearAndResetState(VBState.NEW_VOTER);
ui.startNewVoterSession(new NewVoterCallback(generateRequestIdentifier(), state.currentBallotSerialNumber, this.queue)); 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) { private void doReportErrorAndForceRestart(UIElement errorMessage) {
queue.clear(); queue.clear();
state.clearAndResetState(VBState.FATAL_ERROR_FORCE_NEW_VOTER); state.clearAndResetState(VBState.FATAL_ERROR_FORCE_NEW_VOTER);
@ -187,6 +213,10 @@ public class VotingBoothImpl implements VotingBoothController {
this.queue)); 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 () { private void doChooseChannel () {
if (state.stateIdentifier == VBState.NEW_VOTER) { if (state.stateIdentifier == VBState.NEW_VOTER) {
logger.debug("doing chooseChannel"); 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) { if (state.stateIdentifier == VBState.CHOOSE_CHANNEL) {
logger.debug("doing set channel and ask questions"); logger.debug("doing set channel and ask questions");
state.stateIdentifier = VBState.ANSWER_QUESTIONS; state.stateIdentifier = VBState.ANSWER_QUESTIONS;
List<BallotAnswer> channelChoiceAnswers = task.channelChoiceAnswers; List<BallotAnswer> channelChoiceAnswers = command.channelChoiceAnswers;
state.channelIdentifier = questionSelector.getChannelIdentifier(channelChoiceAnswers); state.channelIdentifier = questionSelector.getChannelIdentifier(channelChoiceAnswers);
state.channelSpecificQuestions = questionSelector.selectQuestionsForVoter(state.channelIdentifier); state.channelSpecificQuestions = questionSelector.selectQuestionsForVoter(state.channelIdentifier);
ui.askVoterQuestions(state.channelSpecificQuestions, 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() { private void doChooseFinalizeOption() {
if (state.stateIdentifier == VBState.COMMITTING_TO_BALLOT) { if (state.stateIdentifier == VBState.COMMITTING_TO_BALLOT) {
logger.debug("doChooseFinalizeOption"); logger.debug("doChooseFinalizeOption");
@ -237,11 +276,17 @@ public class VotingBoothImpl implements VotingBoothController {
// ignore this request // 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) { if (state.stateIdentifier == VBState.ANSWER_QUESTIONS) {
logger.debug("doing commit"); logger.debug("doing commit");
try { try {
setBallotData(task); setBallotData(command);
ui.notifyVoterToWaitForFinish(systemMessages.get(StorageManager.WAIT_FOR_COMMIT_MESSAGE), ui.notifyVoterToWaitForFinish(systemMessages.get(StorageManager.WAIT_FOR_COMMIT_MESSAGE),
new WaitForFinishCallback(generateRequestIdentifier(), new WaitForFinishCallback(generateRequestIdentifier(),
state.currentBallotSerialNumber, state.currentBallotSerialNumber,
@ -257,6 +302,9 @@ public class VotingBoothImpl implements VotingBoothController {
} }
catch (SignatureException | IOException e) { catch (SignatureException | IOException e) {
logger.error("doCommit: encryption failed. exception: " + 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 errorMessage = systemMessages.get(StorageManager.ENCRYPTION_FAILED_MESSAGE);
UIElement[] buttons = new UIElement[]{ UIElement[] buttons = new UIElement[]{
systemMessages.get(StorageManager.RETRY_BUTTON), 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 // this is not a retry attempt, so the plaintext is not set yet
// otherwise, we have the plaintext from the previous encryption attempt // otherwise, we have the plaintext from the previous encryption attempt
state.plaintextBallot = PlaintextBallot.newBuilder() state.plaintextBallot = PlaintextBallot.newBuilder()
.setSerialNumber(task.getBallotSerialNumber()) .setSerialNumber(command.getBallotSerialNumber())
.addAllAnswers(task.getVotingAnswers()) .addAllAnswers(command.getVotingAnswers())
.build(); .build();
} }
// keep the encryption and the secrets we used for it
EncryptionAndSecrets encryptionAndSecrets = crypto.encrypt(state.plaintextBallot); EncryptionAndSecrets encryptionAndSecrets = crypto.encrypt(state.plaintextBallot);
state.signedEncryptedBallot = encryptionAndSecrets.getSignedEncryptedBallot(); state.signedEncryptedBallot = encryptionAndSecrets.getSignedEncryptedBallot();
state.secrets = encryptionAndSecrets.getSecrets(); 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) { private void doFinalize (boolean auditRequested) {
if (state.stateIdentifier == VBState.CAST_OR_AUDIT) { if (state.stateIdentifier == VBState.CAST_OR_AUDIT) {
logger.debug("finalizing"); logger.debug("finalizing");
state.stateIdentifier = VBState.FINALIZING; state.stateIdentifier = VBState.FINALIZING;
if (auditRequested) { if (auditRequested) {
// finalize by auditing
ui.notifyVoterToWaitForFinish(systemMessages.get(StorageManager.WAIT_FOR_AUDIT_MESSAGE), ui.notifyVoterToWaitForFinish(systemMessages.get(StorageManager.WAIT_FOR_AUDIT_MESSAGE),
new WaitForFinishCallback(generateRequestIdentifier(), new WaitForFinishCallback(generateRequestIdentifier(),
state.currentBallotSerialNumber, state.currentBallotSerialNumber,
@ -305,6 +369,7 @@ public class VotingBoothImpl implements VotingBoothController {
systemMessages.get(StorageManager.OUTPUT_DEVICE_FAILURE_MESSAGE))); systemMessages.get(StorageManager.OUTPUT_DEVICE_FAILURE_MESSAGE)));
} }
else { else {
// finalize by casting the ballot
ui.notifyVoterToWaitForFinish(systemMessages.get(StorageManager.WAIT_FOR_CAST_MESSAGE), ui.notifyVoterToWaitForFinish(systemMessages.get(StorageManager.WAIT_FOR_CAST_MESSAGE),
new WaitForFinishCallback(generateRequestIdentifier(), new WaitForFinishCallback(generateRequestIdentifier(),
state.currentBallotSerialNumber, 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 { private enum VBState {
NEW_VOTER, NEW_VOTER,
CHOOSE_CHANNEL, 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 { private class ControllerState {
public VBState stateIdentifier; public VBState stateIdentifier;
public byte[] channelIdentifier; public byte[] channelIdentifier;
@ -359,7 +432,6 @@ public class VotingBoothImpl implements VotingBoothController {
currentBallotSerialNumber = 0; currentBallotSerialNumber = 0;
} }
private void clearPlaintext () { private void clearPlaintext () {
plaintextBallot = null; 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() { private int generateRequestIdentifier() {
++requestCounter; ++requestCounter;
if (requestCounter >= MAX_REQUEST_IDENTIFIER) { if (requestCounter >= MAX_REQUEST_IDENTIFIER) {