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 {
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<BallotQuestion> questionsForChoosingChannel;
private List<BallotQuestion> questions;
private QuestionSelector questionSelector;
private Map<String, UIElement> systemMessages;
private LinkedBlockingQueue<ControllerCommand> queue;
private Logger logger;
// state
private ControllerState state;
private volatile boolean shutDownHasBeenCalled;
private LinkedBlockingQueue<ControllerCommand> 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<BallotAnswer> channelChoiceAnswers = task.channelChoiceAnswers;
List<BallotAnswer> 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) {