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
parent
d1bc0d7c84
commit
e042779b15
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package meerkat.voting.controller.commands;
|
||||
|
||||
|
||||
public class RestartVotingCommand extends ControllerCommand {
|
||||
public RestartVotingCommand(int requestIdentifier, long ballotSerialNumber) {
|
||||
super(requestIdentifier, ballotSerialNumber);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue