Initial code for the Voting Booth.

Still missing components:
1. An implementation of the encryptor (currently program crashes when trying to encrypt the PlaintextBallot)
2. The output device implementation should change to a runnable thread (with commands queue as the ui)

Also needs to add comments EVERYWHERE.
vbdev2
Hai Brenner 2016-05-04 17:46:05 +03:00
parent d1bc0d7c84
commit e042779b15
40 changed files with 1814 additions and 120 deletions

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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<BallotQuestion> 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<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();
}

View File

@ -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<ControllerCommand> 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<BallotQuestion> 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<BallotQuestion> 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<string, funcImpl> 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;
}
}

View File

@ -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<ControllerCommand> 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()));
}
}

View File

@ -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<ControllerCommand> 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()));
}
}

View File

@ -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<ControllerCommand> controllerQueue;
protected ControllerCallback (int requestId,
long ballotSerialNumber,
LinkedBlockingQueue<ControllerCommand> controllerQueue) {
this.requestIdentifier = requestId;
this.ballotSerialNumber = ballotSerialNumber;
this.controllerQueue = controllerQueue;
}
protected int getRequestIdentifier () {
return requestIdentifier;
}
protected long getBallotSerialNumber () {
return ballotSerialNumber;
}
}

View File

@ -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<ControllerCommand> 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);
}
}

View File

@ -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<ControllerCommand> 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()));
}
}

View File

@ -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<ControllerCommand> 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()));
}
}

View File

@ -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<ControllerCommand> 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()));
}
}

View File

@ -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<ControllerCommand> 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()));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,8 @@
package meerkat.voting.controller.commands;
public class RestartVotingCommand extends ControllerCommand {
public RestartVotingCommand(int requestIdentifier, long ballotSerialNumber) {
super(requestIdentifier, ballotSerialNumber);
}
}

View File

@ -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);
}

View File

@ -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<SingleChosenAnswer, int[]> 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 + "]";
}
}

View File

@ -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<BallotQuestion> 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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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<Boolean> callback);
public void commitToBallot(PlaintextBallot plaintextBallot,
EncryptedBallot encryptedBallot,
FutureCallback<Void> 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<Boolean> callback);
public void audit(BallotSecrets ballotSecrets, FutureCallback<Void> 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<Boolean> callback);
public void castBallot(FutureCallback<Void> 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<Boolean> callback);
public void cancelBallot(FutureCallback<Void> callback);
}

View File

@ -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<Void> 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<Void> 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<Void> callback) {
logger.debug("entered method castBallot");
System.out.println("Ballot finalized for casting!");
callback.onSuccess(null);
}
@Override
public void cancelBallot(FutureCallback<Void> callback) {
logger.debug("entered method cancelBallot");
System.out.println("Ballot cancelled!");
callback.onSuccess(null);
}
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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<UICommand> 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<Void> 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<BallotAnswer[]> 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<BallotAnswer[]> 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<FinalizeBallotChoice> 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<Boolean> 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<Boolean> 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<Integer> 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();
}
}

View File

@ -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<UICommand> uiQueue;
public TickerTimerTask (LinkedBlockingQueue<UICommand> uiQueue) {
this.uiQueue = uiQueue;
}
@Override
public void run() {
UICommand t = uiQueue.peek();
if ((t != null) && !(t instanceof TickCommand)) {
uiQueue.add(new TickCommand(null));
}
}
}

View File

@ -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<Boolean> callback);
public void startNewVoterSession (FutureCallback<Void> 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<BallotAnswer> callback);
public void chooseChannel (BallotQuestion[] questions, FutureCallback<BallotAnswer[]> callback);
/**
* Presents the set of questions to the voter. Collect all his responses.

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}