working on scanner API refactoring (doesn't compile yet)

android-scanner
Tal Moran 2017-06-22 17:56:59 +03:00
parent c7dd5bd663
commit ceba09e65c
16 changed files with 218 additions and 132 deletions

View File

@ -1,20 +0,0 @@
syntax = "proto3";
package meerkat;
import "meerkat/voting.proto";
option java_package = "meerkat.protobuf";
// Container for scanned data
message ScannedData {
bytes channel = 1;
SignedEncryptedBallot signed_encrypted_ballot = 2;
}
// Container for error messages
message ErrorMsg {
string msg = 1;
}

View File

@ -1,8 +1,11 @@
package meerkat.pollingstation;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.pollingstation.controller.PollingStationControllerInterface;
import meerkat.pollingstation.controller.callbacks.ScanDataCallback;
import meerkat.pollingstation.controller.commands.PollingStationCommand;
import meerkat.pollingstation.controller.commands.ReceivedScanCommand;
import meerkat.protobuf.PollingStation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -17,20 +20,31 @@ public class PollingStationMainController implements PollingStationControllerInt
private LinkedBlockingQueue<PollingStationCommand> queue;
private volatile boolean shutDownHasBeenCalled;
private static ReceiverScanHandler server;
private static final String ADDRESS = "http://localhost";
private static final String SUB_ADDRESS = "";
private static final int PORT = 8080;
public PollingStationMainController() {
queue = new LinkedBlockingQueue<PollingStationCommand>();
shutDownHasBeenCalled = false;
}
@Override
public void init(ReceiverScanHandler server) {
server.setControllerQueue(queue);
public void init(PollingStationScanner.Consumer server) {
server.subscribe(new FutureCallback<PollingStation.ScannedData>() {
@Override
public void onSuccess(PollingStation.ScannedData result) {
if (result.getDataCase().equals(PollingStation.ScannedData.DataCase.BALLOT)) {
// TODO: Set request ID correctly?
int requestID = 1;
long serialNumber = result.getBallot().getSignedEncryptedBallot().getEncryptedBallot().getSerialNumber();
queue.add(new ReceivedScanCommand(requestID, serialNumber));
} else if (result.getDataCase().equals(PollingStation.ScannedData.DataCase.ERROR)) {
// TODO: Deal with scan errors
}
}
@Override
public void onFailure(Throwable t) {
// TODO: deal with this? When is this called?
}
});
}
public void run() {

View File

@ -6,6 +6,7 @@ package meerkat.pollingstation;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.BoolValue;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.PollingStation;
import javax.annotation.PostConstruct;
@ -17,6 +18,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import java.io.IOException;
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_CONNECT_PATH;
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_ERROR_PATH;
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_SCAN_PATH;
import static meerkat.rest.Constants.MEDIATYPE_PROTOBUF;
@ -49,6 +51,15 @@ public class PollingStationScannerWebApp implements PollingStationScanner.Produc
}
@POST
@Path(POLLING_STATION_WEB_SCANNER_CONNECT_PATH)
@Consumes(MEDIATYPE_PROTOBUF)
@Produces(MEDIATYPE_PROTOBUF)
@Override
public BoolValue connect(PollingStation.ConnectionClientData clientData) {
return null;
}
@POST
@Path(POLLING_STATION_WEB_SCANNER_SCAN_PATH)
@Consumes(MEDIATYPE_PROTOBUF)
@ -58,6 +69,7 @@ public class PollingStationScannerWebApp implements PollingStationScanner.Produc
boolean handled = false;
// TODO:
for (FutureCallback<PollingStation.ScannedData> callback : callbacks){
callback.onSuccess(scannedData);
@ -71,26 +83,4 @@ public class PollingStationScannerWebApp implements PollingStationScanner.Produc
}
@POST
@Path(POLLING_STATION_WEB_SCANNER_ERROR_PATH)
@Consumes(MEDIATYPE_PROTOBUF)
@Produces(MEDIATYPE_PROTOBUF)
@Override
public BoolValue reportScanError(PollingStation.ErrorMsg errorMsg) {
boolean handled = false;
for (FutureCallback<PollingStation.ScannedData> callback : callbacks){
callback.onFailure(new IOException(errorMsg.getMsg()));
handled = true;
}
return BoolValue.newBuilder()
.setValue(handled)
.build();
}
}

View File

@ -1,16 +1,20 @@
package meerkat.pollingstation;
import meerkat.protobuf.PollingStation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by Laura on 3/20/2017.
*/
public class PollingStationToyRun {
final Logger logger = LoggerFactory.getLogger(getClass());
private static PollingStationScanner.Consumer scanner;
private static final String ADDRESS = "http://localhost";
private static final String SUB_ADDRESS = "";
private static final String CONTEXT_PATH = "/scan";
private static final int PORT = 8080;
public static void main(String [] args) {
public static void main(String [] args) throws Exception {
System.err.println("Setting up Scanner WebApp!");
// scanner = new ReceiverScanHandler(PORT, SUB_ADDRESS);
@ -22,14 +26,17 @@ public class PollingStationToyRun {
// System.err.println("Could not start server: " + e.getMessage());
// }
ReceiverScanHandler serverController = new ReceiverScanHandler(PORT, SUB_ADDRESS);
scanner = new PollingStationWebScanner(0, CONTEXT_PATH);
PollingStation.ConnectionServerData serverData = scanner.start(false);
PollingStationMainController controller = new PollingStationMainController();
controller.init(serverController);
controller.init(scanner);
Thread controllerThread = new Thread(controller);
controllerThread.setName("Meerkat PS-Controller Thread");
Thread serverThread = new Thread(serverController);
serverThread.setName("Meerkat PS-ServerScanner Thread");
// Thread serverThread = new Thread(serverController);
// serverThread.setName("Meerkat PS-ServerScanner Thread");
controllerThread.start();
serverThread.start();

View File

@ -1,9 +1,11 @@
package meerkat.pollingstation;
import java.security.SecureRandom;
import java.util.List;
import java.util.LinkedList;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.protobuf.PollingStation;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.*;
@ -17,20 +19,22 @@ import meerkat.rest.*;
* Created by Arbel on 05/05/2016.
*/
public class PollingStationWebScanner implements PollingStationScanner.Consumer{
public class PollingStationWebScanner implements PollingStationScanner.Consumer {
public final static String CALLBACKS_ATTRIBUTE_NAME = "controller";
private final Server server;
private final List<FutureCallback<ScannedData>> callbacks;
boolean verifyScanner;
byte nonce[];
public PollingStationWebScanner(int port, String subAddress) {
public PollingStationWebScanner(int port, String contextPath) {
callbacks = new LinkedList<>();
server = new Server(port);
ServletContextHandler servletContextHandler = new ServletContextHandler(server, subAddress);
ServletContextHandler servletContextHandler = new ServletContextHandler(server, contextPath);
servletContextHandler.setAttribute(CALLBACKS_ATTRIBUTE_NAME, (Iterable<FutureCallback<ScannedData>>) callbacks);
ResourceConfig resourceConfig = new ResourceConfig(PollingStationScannerWebApp.class);
@ -42,9 +46,17 @@ public class PollingStationWebScanner implements PollingStationScanner.Consumer{
servletContextHandler.addServlet(servletHolder, "/*");
}
@Override
public void start() throws Exception {
public PollingStation.ConnectionServerData start(boolean verifyScanner) throws Exception {
this.verifyScanner = verifyScanner;
nonce = new byte[32];
new SecureRandom().nextBytes(nonce);
server.start();
return null;
}
@Override

View File

@ -24,7 +24,7 @@ import java.util.concurrent.LinkedBlockingQueue;
/**
* Created by Laura on 3/20/2017.
*/
public class ReceiverScanHandler implements PollingStationScanner.Consumer, Runnable{
public class ReceiverScanHandler implements PollingStationScanner.Consumer, Runnable {
public final static String CALLBACKS_ATTRIBUTE_NAME = "controller";
@ -78,7 +78,7 @@ public class ReceiverScanHandler implements PollingStationScanner.Consumer, Runn
}
@Override
public void start() throws Exception {
public PollingStation.ConnectionServerData start(boolean verifyScanner) throws Exception {
server.start();
while (! wasShutDownCalled()) {
try {

View File

@ -5,6 +5,7 @@ import com.google.protobuf.BoolValue;
import meerkat.pollingstation.controller.callbacks.ScanCallback;
import meerkat.pollingstation.controller.callbacks.ScanDataCallback;
import meerkat.pollingstation.controller.callbacks.ScanErrorCallback;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.PollingStation;
import javax.annotation.PostConstruct;
@ -16,6 +17,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import java.util.concurrent.LinkedBlockingQueue;
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_CONNECT_PATH;
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_ERROR_PATH;
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_SCAN_PATH;
import static meerkat.rest.Constants.MEDIATYPE_PROTOBUF;
@ -48,12 +50,37 @@ public class ReceiverWebAPI implements PollingStationScanner.Producer {
}
@POST
@Path(POLLING_STATION_WEB_SCANNER_CONNECT_PATH)
@Consumes(MEDIATYPE_PROTOBUF)
@Produces(MEDIATYPE_PROTOBUF)
@Override
public BoolValue connect(PollingStation.ConnectionClientData clientData) {
boolean sent = false;
try {
ScanDataCallback callback = new ScanDataCallback(scannedData);
callbacks.add(callback);
sent = true;
} catch (Exception e) {
e.printStackTrace();
}
return BoolValue.newBuilder()
.setValue(sent)
.build();
}
@POST
@Path(POLLING_STATION_WEB_SCANNER_SCAN_PATH)
@Consumes(MEDIATYPE_PROTOBUF)
@Produces(MEDIATYPE_PROTOBUF)
@Override
public BoolValue newScan(PollingStation.ScannedData scannedData) {
public BoolValue newScan(PollingStation.ScannedData scannedData, Crypto.Signature scannedDataSignature) {
boolean sent = false;
@ -83,7 +110,7 @@ public class ReceiverWebAPI implements PollingStationScanner.Producer {
@Consumes(MEDIATYPE_PROTOBUF)
@Produces(MEDIATYPE_PROTOBUF)
@Override
public BoolValue reportScanError(PollingStation.ErrorMsg errorMsg) {
public BoolValue reportScanError(PollingStation.ErrorMsg errorMsg, Crypto.Signature errorMsgSignature) {
boolean handled = false;
@ -104,4 +131,5 @@ public class ReceiverWebAPI implements PollingStationScanner.Producer {
}
}

View File

@ -1,6 +1,6 @@
package meerkat.pollingstation.controller;
import meerkat.pollingstation.ReceiverScanHandler;
import meerkat.pollingstation.PollingStationScanner;
/**
* An interface for the controller component of the polling station
@ -11,12 +11,10 @@ public interface PollingStationControllerInterface extends Runnable {
* (see VotingBoothController)
*/
// TODO: 4/16/2017 complete with proper arguments and exceptions
public void init(ReceiverScanHandler server);
public void init(PollingStationScanner.Consumer server);
/**
* an asynchronous call from Admin Console (If there is such one implemented) to shut down the system
*/
public void callShutDown();
}

View File

@ -3,6 +3,6 @@ package meerkat.pollingstation.controller.callbacks;
/**
* Created by Laura on 3/20/2017.
*/
public abstract class ScanCallback {
public interface ScanCallback {
}

View File

@ -6,7 +6,7 @@ import meerkat.protobuf.PollingStation;
/**
* Created by Laura on 3/20/2017.
*/
public class ScanDataCallback extends ScanCallback implements FutureCallback<PollingStation.ScannedData> {
public class ScanDataCallback implements ScanCallback, FutureCallback<PollingStation.ScannedData> {
private final PollingStation.ScannedData expectedData;
private boolean dataIsAsExpected;
@ -22,7 +22,8 @@ public class ScanDataCallback extends ScanCallback implements FutureCallback<Pol
@Override
public void onSuccess(PollingStation.ScannedData result) {
dataIsAsExpected = result.getChannel().equals(expectedData.getChannel());
dataIsAsExpected = result.getBallot().getChannel().equals(expectedData.getBallot().getChannel());
}
@Override

View File

@ -2,6 +2,7 @@ package meerkat.pollingstation;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.ByteString;
import meerkat.protobuf.PollingStation;
import meerkat.protobuf.PollingStation.ErrorMsg;
import meerkat.protobuf.PollingStation.ScannedData;
import meerkat.rest.Constants;
@ -10,6 +11,8 @@ import meerkat.rest.ProtobufMessageBodyWriter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
@ -24,18 +27,16 @@ import static org.hamcrest.MatcherAssert.assertThat;
public class Receiver_ClientTest {
final Logger logger = LoggerFactory.getLogger(getClass());
private PollingStationScanner.Consumer scanner;
private static final int PSC_PORT = 8880;
private static final String PSC_HOST = "localhost";
private static final String PSC_SCANNER_PATH = "";
private static final String PSC_URL = "http://" + PSC_HOST + ":" + PSC_PORT + PSC_SCANNER_PATH;
private static final String CONTEXT_PATH = "/scan";
private Semaphore semaphore;
private Throwable thrown;
private boolean dataIsAsExpected;
PollingStation.ConnectionServerData serverData = null;
private class ScanHandler implements FutureCallback<ScannedData> {
private final ScannedData expectedData;
@ -58,43 +59,26 @@ public class Receiver_ClientTest {
}
}
private class ErrorHandler implements FutureCallback<ScannedData> {
private final String expectedErrorMessage;
public ErrorHandler(String expectedErrorMessage) {
this.expectedErrorMessage = expectedErrorMessage;
}
@Override
public void onSuccess(ScannedData result) {
dataIsAsExpected = false;
semaphore.release();
}
@Override
public void onFailure(Throwable t) {
dataIsAsExpected = t.getMessage().equals(expectedErrorMessage);
semaphore.release();
}
}
@Before
public void init() {
System.err.println("Setting up Scanner WebApp!");
scanner = new ReceiverScanHandler(PSC_PORT, PSC_SCANNER_PATH);
scanner = new PollingStationWebScanner(0, CONTEXT_PATH);
semaphore = new Semaphore(0);
thrown = null;
PollingStation.ConnectionServerData serverData = null;
try {
scanner.start();
serverData = scanner.start(false);
} catch (Exception e) {
assertThat("Could not start server: " + e.getMessage(), false);
}
logger.info("Started scanner web service. API endpoint: {}", serverData.getServerUrl());
}
@Test
@ -103,13 +87,17 @@ public class Receiver_ClientTest {
byte[] data = {(byte) 1, (byte) 2};
ScannedData scannedData = ScannedData.newBuilder()
.setChannel(ByteString.copyFrom(data))
.setBallot(PollingStation.ScannedBallot.newBuilder()
.setChannel(ByteString.copyFrom(data))
)
.build();
scanner.subscribe(new ScanHandler(scannedData));
ScannerClientAPI scannerClient = new ScannerClientAPI(PSC_URL + POLLING_STATION_WEB_SCANNER_SCAN_PATH);
scannerClient.sendScan(scannedData);
PollingStationScanner.Producer scannerClient = new ScannerClientAPI()
ScannerClientAPI(PSC_URL + POLLING_STATION_WEB_SCANNER_SCAN_PATH);
scannerClient.newScan(scannedData, null);
semaphore.acquire();
assertThat("Scanner has thrown an error", thrown == null);
@ -126,7 +114,10 @@ public class Receiver_ClientTest {
scanner.subscribe(new ErrorHandler(errorMsg.getMsg()));
ScannerClientAPI scannerClient = new ScannerClientAPI(PSC_URL + POLLING_STATION_WEB_SCANNER_ERROR_PATH);
ScannerClientAPI scannerClient = new ScannerClientAPI();
PSC_URL + POLLING_STATION_WEB_SCANNER_ERROR_PATH);
scannerClient.sendError(errorMsg);
semaphore.acquire();

View File

@ -106,7 +106,6 @@ idea {
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir"
// add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir)

View File

@ -9,5 +9,6 @@ public interface PollingStationConstants {
public static final String POLLING_STATION_WEB_SCANNER_SCAN_PATH = "/scan";
public static final String POLLING_STATION_WEB_SCANNER_ERROR_PATH = "/error";
public static final String POLLING_STATION_WEB_SCANNER_CONNECT_PATH = "/connect";
}

View File

@ -2,6 +2,7 @@ package meerkat.pollingstation;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.BoolValue;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.PollingStation.*;
/**
* Created by Arbel on 05/05/2016.
@ -17,10 +18,13 @@ public interface PollingStationScanner {
public interface Consumer {
/**
* Sets up the connection to the scanner and begins receiving scans
* Sets up the connection to the scanner and begins receiving scans.
* Returns the data the scanner client will need to connect
* If verifyScanner is true, will verify that scanner has the correct nonce before
* allowing connection, and verify signatures on scanned data.
* @throws Exception when the operation fails
*/
public void start() throws Exception;
public ConnectionServerData start(boolean verifyScanner) throws Exception;
/**
* Closes the connection to the scanner
@ -34,7 +38,6 @@ public interface PollingStationScanner {
* @param scanCallback is the handler for scanned data
*/
public void subscribe(FutureCallback<ScannedData> scanCallback);
}
/**
@ -42,6 +45,13 @@ public interface PollingStationScanner {
*/
public interface Producer {
/**
* Connect to the polling station server.
* The client data contains the scanner ID and optional verification data.
* @return
*/
public BoolValue connect(ConnectionClientData clientData);
/**
* Sends a scan to all subscribers
* @param scannedData contains the scanned data
@ -49,13 +59,6 @@ public interface PollingStationScanner {
*/
public BoolValue newScan(ScannedData scannedData);
/**
* Notifies subscribers about an error that occurred during scan
* @param errorMsg is the error that occurred
* @return a BoolValue containing TRUE iff the error has been sent to at least one subscriber
*/
public BoolValue reportScanError(ErrorMsg errorMsg);
}
}

View File

@ -1,7 +1,7 @@
package meerkat.pollingstation;
import com.google.protobuf.BoolValue;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.PollingStation;
import meerkat.rest.Constants;
import meerkat.rest.ProtobufMessageBodyReader;
@ -13,13 +13,10 @@ import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_ERROR_PATH;
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_SCAN_PATH;
/**
* Created by Laura on 3/20/2017.
*/
public class ScannerClientAPI {
public class ScannerClientAPI implements PollingStationScanner.Producer {
private Client client;
private WebTarget webTarget;
@ -30,27 +27,38 @@ public class ScannerClientAPI {
webTarget = client.target(url);
}
/**
* Sends the scanned data to the polling station server
* @param scannedData
* @return boolean value: true if the data was sent successfully, false otherwise
*/
public boolean sendScan(PollingStation.ScannedData scannedData) {
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(scannedData, Constants.MEDIATYPE_PROTOBUF));
@Override
public BoolValue newScan(PollingStation.ScannedData scannedData) {
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(scannedData, Constants.MEDIATYPE_PROTOBUF),
);
BoolValue res = response.readEntity(BoolValue.class);
response.close();
return res.getValue();
}
/**
* Sends the error message to the polling station server
* @param errorMsg
* @return boolean value: true if the error message was sent successfully, false otherwise
*/
public boolean sendError(PollingStation.ErrorMsg errorMsg) {
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(errorMsg, Constants.MEDIATYPE_PROTOBUF));
BoolValue res = response.readEntity(BoolValue.class);
response.close();
return res.getValue();
public void close() {
client.close();
}
@Override
public BoolValue connect(PollingStation.ConnectionClientData clientData) {
return null;
}
return null;
}
@Override
public BoolValue reportScanError(PollingStation.ErrorMsg errorMsg, Crypto.Signature errorMsgSignature) {
return null;
}
}

View File

@ -0,0 +1,54 @@
syntax = "proto3";
package meerkat;
option java_package = "meerkat.protobuf";
import "meerkat/crypto.proto";
import "meerkat/voting.proto";
// Data sent (out-of-band) by the server that the client will require to set up a connection
message ConnectionServerData {
// Web-API URL for the server
string server_url = 1;
// random nonce used for verification of the client.
bytes nonce = 2;
}
// Data sent by the client in order to set up a connection
message ConnectionClientData {
// Key used to identify the client
SignatureVerificationKey scannerPK = 1;
// Nonce sent by connection-server
bytes nonce = 2;
}
message ScannedBallot {
bytes channel = 1;
SignedEncryptedBallot signed_encrypted_ballot = 2;
}
// Container for error messages
message ScanError {
string msg = 1;
}
// Container for scanned data
message ScannedData {
oneof data {
ScannedBallot ballot = 2;
ScanError error = 3;
}
uint64 serial = 4; // Serial number of the message
bytes scannerId = 5; // hash of the scannerPK
Signature scannerSig = 6; // signature on the serialzed form of data.
}