diff --git a/build.gradle b/build.gradle index 2791de4..9f070e3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,4 @@ - subprojects { proj -> proj.afterEvaluate { // Used to generate initial maven-dir layout diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJob.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJob.java new file mode 100644 index 0000000..aca98d4 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJob.java @@ -0,0 +1,82 @@ +package meerkat.bulletinboard; + +import com.google.protobuf.Message; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + * + * This class specifies the job that is required of a Bulletin Board Client Worker + */ +public class BulletinClientJob { + + public static enum JobType{ + POST_MESSAGE, // Post a message to servers + READ_MESSAGES, // Read messages according to some given filter (any server will do) + GET_REDUNDANCY // Check the redundancy of a specific message in the databases + } + + private List serverAddresses; + + private int minServers; // The minimal number of servers the job must be successful on for the job to be completed + + private final JobType jobType; + + private final Message payload; // The information associated with the job type + + private int maxRetry; // Number of retries for this job; set to -1 for infinite retries + + public BulletinClientJob(List serverAddresses, int minServers, JobType jobType, Message payload, int maxRetry) { + this.serverAddresses = serverAddresses; + this.minServers = minServers; + this.jobType = jobType; + this.payload = payload; + this.maxRetry = maxRetry; + } + + public void updateServerAddresses(List newServerAdresses) { + this.serverAddresses = newServerAdresses; + } + + public List getServerAddresses() { + return serverAddresses; + } + + public int getMinServers() { + return minServers; + } + + public JobType getJobType() { + return jobType; + } + + public Message getPayload() { + return payload; + } + + public int getMaxRetry() { + return maxRetry; + } + + public void shuffleAddresses() { + Collections.shuffle(serverAddresses); + } + + public void decMinServers(){ + minServers--; + } + + public void decMaxRetry(){ + if (maxRetry > 0) { + maxRetry--; + } + } + + public boolean isRetry(){ + return (maxRetry != 0); + } + +} \ No newline at end of file diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJobResult.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJobResult.java new file mode 100644 index 0000000..be0501b --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJobResult.java @@ -0,0 +1,29 @@ +package meerkat.bulletinboard; + +import com.google.protobuf.Message; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + * + * This class contains the end status and result of a Bulletin Board Client Job. + */ +public final class BulletinClientJobResult { + + private final BulletinClientJob job; // Stores the job the result refers to + + private final Message result; // The result of the job; valid only if success==true + + public BulletinClientJobResult(BulletinClientJob job, Message result) { + this.job = job; + this.result = result; + } + + public BulletinClientJob getJob() { + return job; + } + + public Message getResult() { + return result; + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientWorker.java new file mode 100644 index 0000000..51599d5 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientWorker.java @@ -0,0 +1,217 @@ +package meerkat.bulletinboard; + +import com.google.protobuf.Message; +import meerkat.comm.CommunicationException; +import meerkat.crypto.Digest; +import meerkat.crypto.concrete.SHA256Digest; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.rest.Constants; +import meerkat.rest.ProtobufMessageBodyReader; +import meerkat.rest.ProtobufMessageBodyWriter; + +import javax.ws.rs.ProcessingException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + * + * This class implements the actual communication with the Bulletin Board Servers. + * It is meant to be used in a multi-threaded environment. + */ +//TODO: Maybe make this abstract and inherit from it. +public class BulletinClientWorker implements Callable { + + private final BulletinClientJob job; // The requested job to be handled + + public BulletinClientWorker(BulletinClientJob job){ + this.job = job; + } + + // This resource enabled creation of a single Client per thread. + private static final ThreadLocal clientLocal = + new ThreadLocal () { + @Override protected Client initialValue() { + Client client; + client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + + return client; + } + }; + + // This resource enables creation of a single Digest per thread. + private static final ThreadLocal digestLocal = + new ThreadLocal () { + @Override protected Digest initialValue() { + Digest digest; + digest = new SHA256Digest(); //TODO: Make this generic. + + return digest; + } + }; + + /** + * This method carries out the actual communication with the servers via HTTP Post + * It accesses the servers according to the job it received and updates said job as it goes + * The method will only iterate once through the server list, removing servers from the list when they are no longer required + * In a POST_MESSAGE job: successful post to a server results in removing the server from the list + * In a GET_REDUNDANCY job: no server is removed from the list and the (absolute) number of servers in which the message was found is returned + * In a READ_MESSAGES job: successful retrieval from any server terminates the method and returns the received values; The list is not changed + * @return The original job, modified to fit the current state and the required output (if any) of the operation + * @throws IllegalArgumentException + * @throws CommunicationException + */ + public BulletinClientJobResult call() throws IllegalArgumentException, CommunicationException{ + + Client client = clientLocal.get(); + Digest digest = digestLocal.get(); + + WebTarget webTarget; + Response response; + + String requestPath; + Message msg; + + List serverAddresses = new LinkedList(job.getServerAddresses()); + + Message payload = job.getPayload(); + + BulletinBoardMessageList msgList; + + int count = 0; // Used to count number of servers which contain the required message in a GET_REDUNDANCY request. + + job.shuffleAddresses(); // This is done to randomize the order of access to servers primarily for READ operations + + // Prepare the request. + switch(job.getJobType()) { + + case POST_MESSAGE: + // Make sure the payload is a BulletinBoardMessage + if (!(payload instanceof BulletinBoardMessage)) { + throw new IllegalArgumentException("Cannot post an object that is not an instance of BulletinBoardMessage"); + } + + msg = payload; + requestPath = Constants.POST_MESSAGE_PATH; + break; + + case READ_MESSAGES: + // Make sure the payload is a MessageFilterList + if (!(payload instanceof MessageFilterList)) { + throw new IllegalArgumentException("Read failed: an instance of MessageFilterList is required as payload for a READ_MESSAGES operation"); + } + + msg = payload; + requestPath = Constants.READ_MESSAGES_PATH; + break; + + case GET_REDUNDANCY: + // Make sure the payload is a MessageId + if (!(payload instanceof MessageID)) { + throw new IllegalArgumentException("Cannot search for an object that is not an instance of MessageID"); + } + + requestPath = Constants.READ_MESSAGES_PATH; + + msg = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.MSG_ID) + .setId(((MessageID) payload).getID()) + .build() + ).build(); + + break; + + default: + throw new IllegalArgumentException("Unsupported job type"); + + } + + // Iterate through servers + + Iterator addressIterator = serverAddresses.iterator(); + + while (addressIterator.hasNext()) { + + // Send request to Server + String address = addressIterator.next(); + webTarget = client.target(address).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(requestPath); + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); + + // Retrieve answer + switch(job.getJobType()) { + + case POST_MESSAGE: + try { + + response.readEntity(BoolMsg.class); // If a BoolMsg entity is returned: the post was successful + addressIterator.remove(); // Post to this server succeeded: remove server from list + job.decMinServers(); + + } catch (ProcessingException | IllegalStateException e) {} // Post to this server failed: retry next time + finally { + response.close(); + } + break; + + case GET_REDUNDANCY: + try { + msgList = response.readEntity(BulletinBoardMessageList.class); // If a BulletinBoardMessageList is returned: the read was successful + + if (msgList.getMessageList().size() > 0){ // Message was found in the server. + count++; + } + } catch (ProcessingException | IllegalStateException e) {} // Read failed: try with next server + finally { + response.close(); + } + break; + + case READ_MESSAGES: + try { + msgList = response.readEntity(BulletinBoardMessageList.class); // If a BulletinBoardMessageList is returned: the read was successful + return new BulletinClientJobResult(job, msgList); // Return the result + } catch (ProcessingException | IllegalStateException e) {} // Read failed: try with next server + finally { + response.close(); + } + break; + + } + + } + + // Return result (if haven't done so yet) + switch(job.getJobType()) { + + case POST_MESSAGE: + // The job now contains the information required to ascertain whether enough server posts have succeeded + // It will also contain the list of servers in which the post was not successful + job.updateServerAddresses(serverAddresses); + return new BulletinClientJobResult(job, null); + + case GET_REDUNDANCY: + // Return the number of servers in which the message was found + // The job now contains the list of these servers + return new BulletinClientJobResult(job, IntMsg.newBuilder().setValue(count).build()); + + case READ_MESSAGES: + // A successful operation would have already returned an output + // Therefore: no server access was successful + throw new CommunicationException("Could not access any server"); + + default: // This is required for successful compilation + throw new IllegalArgumentException("Unsupported job type"); + + } + } +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java new file mode 100644 index 0000000..f340cae --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java @@ -0,0 +1,161 @@ +package meerkat.bulletinboard; + +import com.google.protobuf.ByteString; +import meerkat.comm.CommunicationException; +import meerkat.crypto.Digest; +import meerkat.crypto.concrete.SHA256Digest; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Voting; +import meerkat.protobuf.Voting.BulletinBoardClientParams; +import meerkat.rest.*; + +import java.util.List; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + */ +public class SimpleBulletinBoardClient{ //implements BulletinBoardClient { + + private List meerkatDBs; + + private Client client; + + private Digest digest; + + /** + * Stores database locations and initializes the web Client + * @param clientParams contains the data needed to access the DBs + */ +// @Override + public void init(Voting.BulletinBoardClientParams clientParams) { + + meerkatDBs = clientParams.getBulletinBoardAddressList(); + + client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + + digest = new SHA256Digest(); + + } + + /** + * Post message to all DBs + * Make only one try per DB. + * @param msg is the message, + * @return the message ID for later retrieval + * @throws CommunicationException + */ +// @Override + public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { + + WebTarget webTarget; + Response response; + + // Post message to all databases + try { + for (String db : meerkatDBs) { + webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.POST_MESSAGE_PATH); + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); + + // Only consider valid responses + if (response.getStatusInfo() == Response.Status.OK + || response.getStatusInfo() == Response.Status.CREATED) { + response.readEntity(BoolMsg.class).getValue(); + } + } + } catch (Exception e) { // Occurs only when server replies with valid status but invalid data + throw new CommunicationException("Error accessing database: " + e.getMessage()); + } + + // Calculate the correct message ID and return it + digest.reset(); + digest.update(msg.getMsg()); + return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build(); + } + + /** + * Access each database and search for a given message ID + * Return the number of databases in which the message was found + * Only try once per DB + * Ignore communication exceptions in specific databases + * @param id is the requested message ID + * @return the number of DBs in which retrieval was successful + */ +// @Override + public float getRedundancy(MessageID id) { + WebTarget webTarget; + Response response; + + MessageFilterList filterList = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.MSG_ID) + .setId(id.getID()) + .build()) + .build(); + + float count = 0; + + for (String db : meerkatDBs) { + try { + webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH); + + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); + + if (response.readEntity(BulletinBoardMessageList.class).getMessageCount() > 0){ + count++; + } + + } catch (Exception e) {} + } + + return count / ((float) meerkatDBs.size()); + } + + /** + * Go through the DBs and try to retrieve messages according to the specified filter + * If at the operation is successful for some DB: return the results and stop iterating + * If no operation is successful: return null (NOT blank list) + * @param filterList return only messages that match the filters (null means no filtering). + * @return + */ +// @Override + public List readMessages(MessageFilterList filterList) { + WebTarget webTarget; + Response response; + BulletinBoardMessageList messageList; + + // Replace null filter list with blank one. + if (filterList == null){ + filterList = MessageFilterList.newBuilder().build(); + } + + for (String db : meerkatDBs) { + try { + webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH); + + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); + + messageList = response.readEntity(BulletinBoardMessageList.class); + + if (messageList != null){ + return messageList.getMessageList(); + } + + } catch (Exception e) {} + } + + return null; + } + +// @Override +// public void registerNewMessageCallback(MessageCallback callback, MessageFilterList filterList) { +// callback.handleNewMessage(readMessages(filterList)); +// } +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java new file mode 100644 index 0000000..bb46c32 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java @@ -0,0 +1,131 @@ +package meerkat.bulletinboard; + +import com.google.common.util.concurrent.*; +import com.google.protobuf.ByteString; +import meerkat.bulletinboard.callbacks.GetRedundancyFutureCallback; +import meerkat.bulletinboard.callbacks.PostMessageFutureCallback; +import meerkat.bulletinboard.callbacks.ReadMessagesFutureCallback; +import meerkat.comm.CommunicationException; +import meerkat.crypto.Digest; +import meerkat.crypto.concrete.SHA256Digest; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Voting; + +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + * Thread-based implementation of a Bulletin Board Client. + * Features: + * 1. Handles tasks concurrently. + * 2. Retries submitting + */ +public class ThreadedBulletinBoardClient implements BulletinBoardClient { + + private final static int THREAD_NUM = 10; + ListeningExecutorService listeningExecutor; + + private Digest digest; + + private List meerkatDBs; + private String postSubAddress; + private String readSubAddress; + + private final static int READ_MESSAGES_RETRY_NUM = 1; + + private int minAbsoluteRedundancy; + + /** + * Stores database locations and initializes the web Client + * Stores the required minimum redundancy. + * Starts the Thread Pool. + * @param clientParams contains the required information + */ + @Override + public void init(Voting.BulletinBoardClientParams clientParams) { + + meerkatDBs = clientParams.getBulletinBoardAddressList(); + + minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * meerkatDBs.size()); + + listeningExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(THREAD_NUM)); + + digest = new SHA256Digest(); + + } + + /** + * Post message to all DBs + * Retry failed DBs + * @param msg is the message, + * @return the message ID for later retrieval + * @throws CommunicationException + */ + @Override + public MessageID postMessage(BulletinBoardMessage msg, ClientCallback callback){ + + // Create job + BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.POST_MESSAGE, msg, -1); + + // Submit job and create callback + Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new PostMessageFutureCallback(listeningExecutor, callback)); + + // Calculate the correct message ID and return it + digest.reset(); + digest.update(msg.getMsg()); + return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build(); + } + + /** + * Access each database and search for a given message ID + * Return the number of databases in which the message was found + * Only try once per DB + * Ignore communication exceptions in specific databases + * @param id is the requested message ID + * @return the number of DBs in which retrieval was successful + */ + @Override + public void getRedundancy(MessageID id, ClientCallback callback) { + + // Create job + BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.GET_REDUNDANCY, id, 1); + + // Submit job and create callback + Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new GetRedundancyFutureCallback(listeningExecutor, callback)); + + } + + /** + * Go through the DBs and try to retrieve messages according to the specified filter + * If at the operation is successful for some DB: return the results and stop iterating + * If no operation is successful: return null (NOT blank list) + * @param filterList return only messages that match the filters (null means no filtering). + * @return + */ + @Override + public void readMessages(MessageFilterList filterList, ClientCallback> callback) { + + // Create job + BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.READ_MESSAGES, + filterList, READ_MESSAGES_RETRY_NUM); + + // Submit job and create callback + Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new ReadMessagesFutureCallback(listeningExecutor, callback)); + + } + + @Override + public void close() { + try { + listeningExecutor.shutdown(); + while (! listeningExecutor.isShutdown()) { + listeningExecutor.awaitTermination(10, TimeUnit.SECONDS); + } + } catch (InterruptedException e) { + System.err.println(e.getCause() + " " + e.getMessage()); + } + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ClientFutureCallback.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ClientFutureCallback.java new file mode 100644 index 0000000..7c5b7b0 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ClientFutureCallback.java @@ -0,0 +1,25 @@ +package meerkat.bulletinboard.callbacks; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListeningExecutorService; +import meerkat.bulletinboard.BulletinClientJob; +import meerkat.bulletinboard.BulletinClientJobResult; +import meerkat.bulletinboard.BulletinClientWorker; +import meerkat.protobuf.BulletinBoardAPI; + +import java.util.List; + +/** + * This is a future callback used to listen to workers and run on job finish + * Depending on the type of job and the finishing status of the worker: a decision is made whether to retry or return an error + */ +public abstract class ClientFutureCallback implements FutureCallback { + + protected ListeningExecutorService listeningExecutor; + + ClientFutureCallback(ListeningExecutorService listeningExecutor) { + this.listeningExecutor = listeningExecutor; + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/GetRedundancyFutureCallback.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/GetRedundancyFutureCallback.java new file mode 100644 index 0000000..518ed77 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/GetRedundancyFutureCallback.java @@ -0,0 +1,38 @@ +package meerkat.bulletinboard.callbacks; + +import com.google.common.util.concurrent.ListeningExecutorService; +import meerkat.bulletinboard.BulletinBoardClient; +import meerkat.bulletinboard.BulletinClientJobResult; +import meerkat.protobuf.BulletinBoardAPI.*; + +import java.util.List; + +/** + * This is a future callback used to listen to workers and run on job finish + * Depending on the type of job and the finishing status of the worker: a decision is made whether to retry or return an error + */ +public class GetRedundancyFutureCallback extends ClientFutureCallback { + + private BulletinBoardClient.ClientCallback callback; + + public GetRedundancyFutureCallback(ListeningExecutorService listeningExecutor, + BulletinBoardClient.ClientCallback callback) { + super(listeningExecutor); + this.callback = callback; + } + + @Override + public void onSuccess(BulletinClientJobResult result) { + + int absoluteRedundancy = ((IntMsg) result.getResult()).getValue(); + int totalServers = result.getJob().getServerAddresses().size(); + + callback.handleCallback( ((float) absoluteRedundancy) / ((float) totalServers) ); + + } + + @Override + public void onFailure(Throwable t) { + callback.handleFailure(t); + } +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/PostMessageFutureCallback.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/PostMessageFutureCallback.java new file mode 100644 index 0000000..221ae1a --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/PostMessageFutureCallback.java @@ -0,0 +1,46 @@ +package meerkat.bulletinboard.callbacks; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListeningExecutorService; +import meerkat.bulletinboard.BulletinBoardClient; +import meerkat.bulletinboard.BulletinClientJob; +import meerkat.bulletinboard.BulletinClientJobResult; +import meerkat.bulletinboard.BulletinClientWorker; +import meerkat.protobuf.BulletinBoardAPI; + +import java.util.List; + +/** + * This is a future callback used to listen to workers and run on job finish + * Depending on the type of job and the finishing status of the worker: a decision is made whether to retry or return an error + */ +public class PostMessageFutureCallback extends ClientFutureCallback { + + private BulletinBoardClient.ClientCallback callback; + + public PostMessageFutureCallback(ListeningExecutorService listeningExecutor, + BulletinBoardClient.ClientCallback callback) { + super(listeningExecutor); + this.callback = callback; + } + + @Override + public void onSuccess(BulletinClientJobResult result) { + + BulletinClientJob job = result.getJob(); + + job.decMaxRetry(); + + // If redundancy is below threshold: retry + if (job.getMinServers() > 0 && job.isRetry()) { + Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), this); + } + + callback.handleCallback(null); + } + + @Override + public void onFailure(Throwable t) { + callback.handleFailure(t); + } +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ReadMessagesFutureCallback.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ReadMessagesFutureCallback.java new file mode 100644 index 0000000..4c43ba2 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ReadMessagesFutureCallback.java @@ -0,0 +1,38 @@ +package meerkat.bulletinboard.callbacks; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListeningExecutorService; +import meerkat.bulletinboard.BulletinBoardClient; +import meerkat.bulletinboard.BulletinClientJob; +import meerkat.bulletinboard.BulletinClientJobResult; +import meerkat.bulletinboard.BulletinClientWorker; +import meerkat.protobuf.BulletinBoardAPI; + +import java.util.List; + +/** + * This is a future callback used to listen to workers and run on job finish + * Depending on the type of job and the finishing status of the worker: a decision is made whether to retry or return an error + */ +public class ReadMessagesFutureCallback extends ClientFutureCallback { + + private BulletinBoardClient.ClientCallback> callback; + + public ReadMessagesFutureCallback(ListeningExecutorService listeningExecutor, + BulletinBoardClient.ClientCallback> callback) { + super(listeningExecutor); + this.callback = callback; + } + + @Override + public void onSuccess(BulletinClientJobResult result) { + + callback.handleCallback(((BulletinBoardAPI.BulletinBoardMessageList) result.getResult()).getMessageList()); + + } + + @Override + public void onFailure(Throwable t) { + callback.handleFailure(t); + } +} diff --git a/bulletin-board-client/src/test/java/BulletinBoardClientIntegrationTest.java b/bulletin-board-client/src/test/java/BulletinBoardClientIntegrationTest.java new file mode 100644 index 0000000..dda76c7 --- /dev/null +++ b/bulletin-board-client/src/test/java/BulletinBoardClientIntegrationTest.java @@ -0,0 +1,214 @@ +import com.google.protobuf.ByteString; +import meerkat.bulletinboard.BulletinBoardClient; +import meerkat.bulletinboard.BulletinBoardClient.ClientCallback; +import meerkat.bulletinboard.ThreadedBulletinBoardClient; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Crypto; + +import meerkat.protobuf.Voting.*; +import meerkat.util.BulletinBoardMessageComparator; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.number.OrderingComparison.*; + +import java.util.*; +import java.util.concurrent.Semaphore; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + */ +public class BulletinBoardClientIntegrationTest { + + Semaphore jobSemaphore; + Vector thrown; + + private class PostCallback implements ClientCallback{ + + @Override + public void handleCallback(Object msg) { + System.err.println("Post operation completed"); + jobSemaphore.release(); + } + + @Override + public void handleFailure(Throwable t) { + thrown.add(t); + jobSemaphore.release(); + } + } + + private class RedundancyCallback implements ClientCallback{ + + private float minRedundancy; + + public RedundancyCallback(float minRedundancy) { + this.minRedundancy = minRedundancy; + } + + @Override + public void handleCallback(Float redundancy) { + System.err.println("Redundancy found is: " + redundancy); + jobSemaphore.release(); + assertThat(redundancy, greaterThanOrEqualTo(minRedundancy)); + } + + @Override + public void handleFailure(Throwable t) { + thrown.add(t); + jobSemaphore.release(); + } + } + + private class ReadCallback implements ClientCallback>{ + + private List expectedMsgList; + + public ReadCallback(List expectedMsgList) { + this.expectedMsgList = expectedMsgList; + } + + @Override + public void handleCallback(List messages) { + + System.err.println(messages); + jobSemaphore.release(); + + BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator(); + + assertThat(messages.size(), is(expectedMsgList.size())); + + Iterator expectedMessageIterator = expectedMsgList.iterator(); + Iterator receivedMessageIterator = messages.iterator(); + + while (expectedMessageIterator.hasNext()) { + assertThat(msgComparator.compare(expectedMessageIterator.next(), receivedMessageIterator.next()), is(0)); + } + + } + + @Override + public void handleFailure(Throwable t) { + thrown.add(t); + jobSemaphore.release(); + } + } + + private BulletinBoardClient bulletinBoardClient; + + private PostCallback postCallback; + private RedundancyCallback redundancyCallback; + private ReadCallback readCallback; + + private static String PROP_GETTY_URL = "gretty.httpBaseURI"; + private static String DEFAULT_BASE_URL = "http://localhost:8081"; + private static String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL); + + @Before + public void init(){ + + bulletinBoardClient = new ThreadedBulletinBoardClient(); + + List testDB = new LinkedList(); + testDB.add(BASE_URL); + + bulletinBoardClient.init(BulletinBoardClientParams.newBuilder() + .addBulletinBoardAddress("http://localhost:8081") + .setMinRedundancy((float) 1.0) + .build()); + + postCallback = new PostCallback(); + redundancyCallback = new RedundancyCallback((float) 1.0); + + thrown = new Vector<>(); + jobSemaphore = new Semaphore(0); + + } + + @Test + public void postTest() { + + byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4}; + byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14}; + byte[] b3 = {(byte) 21, (byte) 22, (byte) 23, (byte) 24}; + byte[] b4 = {(byte) 4, (byte) 5, (byte) 100, (byte) -50, (byte) 0}; + + BulletinBoardMessage msg; + + MessageFilterList filterList; + List msgList; + + MessageID messageID; + + Comparator msgComparator = new BulletinBoardMessageComparator(); + + msg = BulletinBoardMessage.newBuilder() + .setMsg(UnsignedBulletinBoardMessage.newBuilder() + .addTag("Signature") + .addTag("Trustee") + .setData(ByteString.copyFrom(b1)) + .build()) + .addSig(Crypto.Signature.newBuilder() + .setType(Crypto.SignatureType.DSA) + .setData(ByteString.copyFrom(b2)) + .setSignerId(ByteString.copyFrom(b3)) + .build()) + .addSig(Crypto.Signature.newBuilder() + .setType(Crypto.SignatureType.ECDSA) + .setData(ByteString.copyFrom(b3)) + .setSignerId(ByteString.copyFrom(b2)) + .build()) + .build(); + + messageID = bulletinBoardClient.postMessage(msg,postCallback); + + try { + jobSemaphore.acquire(); + } catch (InterruptedException e) { + System.err.println(e.getCause() + " " + e.getMessage()); + } + + bulletinBoardClient.getRedundancy(messageID,redundancyCallback); + + filterList = MessageFilterList.newBuilder() + .addFilter( + MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag("Signature") + .build() + ) + .addFilter( + MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag("Trustee") + .build() + ) + .build(); + + msgList = new LinkedList(); + msgList.add(msg); + + readCallback = new ReadCallback(msgList); + + bulletinBoardClient.readMessages(filterList, readCallback); + try { + jobSemaphore.acquire(2); + } catch (InterruptedException e) { + System.err.println(e.getCause() + " " + e.getMessage()); + } + + bulletinBoardClient.close(); + + for (Throwable t : thrown) { + System.err.println(t.getMessage()); + } + if (thrown.size() > 0) { + assert false; + } + + } + +} diff --git a/bulletin-board-server/build.gradle b/bulletin-board-server/build.gradle index 790f0d3..eb197c9 100644 --- a/bulletin-board-server/build.gradle +++ b/bulletin-board-server/build.gradle @@ -2,9 +2,10 @@ plugins { id "us.kirchmeier.capsule" version "1.0.1" id 'com.google.protobuf' version '0.7.0' - id "org.akhikhl.gretty" version "1.2.4" + id 'org.akhikhl.gretty' version "1.2.4" } +apply plugin: 'org.akhikhl.gretty' apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' @@ -44,8 +45,15 @@ dependencies { // Jersey for RESTful API compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.22.+' + + // JDBC connections + compile 'org.springframework:spring-jdbc:4.2.+' compile 'org.xerial:sqlite-jdbc:3.7.+' - + compile 'mysql:mysql-connector-java:5.1.+' + compile 'com.h2database:h2:1.0.+' + + // Servlets + compile 'javax.servlet:javax.servlet-api:3.0.+' // Logging compile 'org.slf4j:slf4j-api:1.7.7' @@ -65,16 +73,22 @@ dependencies { test { + exclude '**/*SQLite*Test*' + exclude '**/*H2*Test*' + exclude '**/*MySql*Test' exclude '**/*IntegrationTest*' } -task debugIntegrationTest(type: Test){ - include '**/*IntegrationTest*' - debug = true +task dbTest(type: Test) { + include '**/*H2*Test*' + include '**/*MySql*Test' } task integrationTest(type: Test) { include '**/*IntegrationTest*' +// debug = true + outputs.upToDateWhen { false } + } gretty { @@ -82,6 +96,7 @@ gretty { contextPath = '/' integrationTestTask = 'integrationTest' loggingLevel = 'TRACE' + debugPort = 5006 } diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/httpserver/BulletinBoardHttpServer.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/httpserver/BulletinBoardHttpServer.java deleted file mode 100644 index 032ea27..0000000 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/httpserver/BulletinBoardHttpServer.java +++ /dev/null @@ -1,104 +0,0 @@ -package meerkat.bulletinboard.httpserver; - -import java.io.File; -import java.io.IOException; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import meerkat.bulletinboard.BulletinBoardServer; -import meerkat.bulletinboard.sqlserver.SQLiteBulletinBoardServer; -import meerkat.comm.CommunicationException; -import meerkat.protobuf.BulletinBoardAPI.*; - -public class BulletinBoardHttpServer extends HttpServlet { - - public final static File DEFAULT_MEERKAT_DB = new File("local-instances/meerkat.db"); - - /** - * Auto-generated UID. - */ - private static final long serialVersionUID = -1263665607729456165L; - - BulletinBoardServer bbs; - - @Override - public void init(ServletConfig config) throws ServletException { - //TODO: Make this generic - bbs = new SQLiteBulletinBoardServer(); - - try { - bbs.init(DEFAULT_MEERKAT_DB); - } catch (CommunicationException e) { - // TODO Log error - throw new ServletException("Servlet failed to initialize: " + e.getMessage()); - } - } - - /** - * This procedure handles (POST) requests to post messages to the Bulletin Board. - */ - @Override - protected void doPost( HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - - BulletinBoardMessage message; - - try{ - message = BulletinBoardMessage.newBuilder() - .mergeFrom(request.getInputStream()) - .build(); - } catch(Exception e){ - //TODO: Log invalid request - return; - } - - try { - bbs.postMessage(message); - } catch (CommunicationException e) { - // TODO Log DB communication error - } - - } - - /** - * This procedure handles (GET) requests which request data from the Bulletin Board. - */ - @Override - protected void doGet( HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - - BulletinBoardMessage message; - - try{ - message = BulletinBoardMessage.newBuilder() - .mergeFrom(request.getInputStream()) - .build(); - } catch(Exception e){ - //TODO: Log invalid request - return; - } - - try { - bbs.postMessage(message); - } catch (CommunicationException e) { - // TODO Log DB communication error - } - - } - - @Override - public void destroy() { - - try { - bbs.close(); - } catch (CommunicationException e) { - // TODO Log DB communication error - } - - } - -} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java index 4cb8f38..52bf42b 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java @@ -1,18 +1,14 @@ package meerkat.bulletinboard.sqlserver; -import java.io.File; -import java.util.Arrays; -import java.util.List; +import java.sql.*; +import java.util.*; import com.google.protobuf.ProtocolStringList; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - import meerkat.bulletinboard.BulletinBoardServer; +import meerkat.bulletinboard.sqlserver.mappers.EntryNumMapper; +import meerkat.bulletinboard.sqlserver.mappers.MessageMapper; +import meerkat.bulletinboard.sqlserver.mappers.SignatureMapper; import meerkat.comm.CommunicationException; import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.Crypto.Signature; @@ -20,9 +16,182 @@ import meerkat.protobuf.Crypto.SignatureVerificationKey; import meerkat.crypto.Digest; import meerkat.crypto.concrete.SHA256Digest; -public abstract class BulletinBoardSQLServer implements BulletinBoardServer{ - - protected Connection connection; +import javax.sql.DataSource; + +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; + +/** + * This is a generic SQL implementation of the BulletinBoardServer API. + */ +public class BulletinBoardSQLServer implements BulletinBoardServer{ + + /** + * This interface provides the required implementation-specific data to enable an access to an actual SQL server. + * It accounts for differences in languages between SQL DB types and for the different addresses needed to access them. + */ + public interface SQLQueryProvider { + + /** + * Allowed query types. + * Note that each query returned has to comply with the parameter names specified ny getParamNames + */ + public static enum QueryType { + + FIND_MSG_ID(new String[] {"MsgId"}), + INSERT_MSG(new String[] {"MsgId","Msg"}), + INSERT_NEW_TAG(new String[] {"Tag"}), + CONNECT_TAG(new String[] {"EntryNum","Tag"}), + ADD_SIGNATURE(new String[] {"EntryNum","SignerId","Signature"}), + GET_SIGNATURES(new String[] {"EntryNum"}), + GET_MESSAGES(new String[] {}); + + private String[] paramNames; + + private QueryType(String[] paramNames) { + this.paramNames = paramNames; + } + + public String[] getParamNames() { + return paramNames; + } + + } + + /** + * This enum provides the standard translation between a filter type and the corresponding parameter name in the SQL query + */ + public static enum FilterTypeParam { + + ENTRY_NUM("EntryNum", Types.INTEGER), + MSG_ID("MsgId", Types.BLOB), + SIGNER_ID("SignerId", Types.BLOB), + TAG("Tag", Types.VARCHAR), + LIMIT("Limit", Types.INTEGER); + + private FilterTypeParam(String paramName, int paramType) { + this.paramName = paramName; + this.paramType = paramType; + } + + private String paramName; + private int paramType; + + public static FilterTypeParam getFilterTypeParamName(FilterType filterType) { + switch (filterType) { + + case MSG_ID: + return MSG_ID; + + case EXACT_ENTRY: // Go through + case MAX_ENTRY: + return ENTRY_NUM; + + case SIGNER_ID: + return SIGNER_ID; + + case TAG: + return TAG; + + case MAX_MESSAGES: + return LIMIT; + + default: + return null; + } + } + + public String getParamName() { + return paramName; + } + + public int getParamType() { + return paramType; + } + } + + /** + * This function translates a QueryType into an actual SQL query. + * @param queryType is the type of query requested + * @return a string representation of the query for the specific type of SQL database implemented. + */ + public String getSQLString(QueryType queryType) throws IllegalArgumentException; + + /** + * Used to retrieve a condition to add to an SQL statement that will make the result comply with the filter type + * @param filterType is the filter type + * @param serialNum is a unique number used to identify the condition variables from other condition instances + * @return The SQL string for the condition + * @throws IllegalArgumentException if the filter type used is not supported + */ + public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException; + + public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException; + + /** + * @return the string needed in order to connect to the DB. + */ + public DataSource getDataSource(); + + /** + * This is used to get a list of queries that together create the schema needed for the DB. + * Note that these queries should not assume anything about the current state of the DB. + * In particular: they should not erase any existing tables and/or entries. + * @return The list of queries. + */ + public List getSchemaCreationCommands(); + + /** + * This is used to get a list of queries that together delete the schema needed for the DB. + * This is useful primarily for tests, in which we want to make sure we start with a clean DB. + * @return The list of queries. + */ + public List getSchemaDeletionCommands(); + + } + + private Object getParam(MessageFilter messageFilter) { + + switch (messageFilter.getType()) { + + case MSG_ID: // Go through + case SIGNER_ID: + return messageFilter.getId().toByteArray(); + + case EXACT_ENTRY: // Go through + case MAX_ENTRY: + return messageFilter.getEntry(); + + case TAG: + return messageFilter.getTag(); + + case MAX_MESSAGES: + return messageFilter.getMaxMessages(); + + default: + return null; + } + + } + + /** + * This class implements a comparator for the MessageFilter class + * The comparison is done solely by comparing the type of the filter + * This is used to sort the filters by type + */ + public class FilterTypeComparator implements Comparator { + + @Override + public int compare(MessageFilter filter1, MessageFilter filter2) { + return filter1.getTypeValue() - filter2.getTypeValue(); + } + } + + protected SQLQueryProvider sqlQueryProvider; + + protected NamedParameterJdbcTemplate jdbcTemplate; protected Digest digest; @@ -31,18 +200,47 @@ public abstract class BulletinBoardSQLServer implements BulletinBoardServer{ protected List> pollingCommitteeSignatureVerificationKeyArray; protected int minCommiteeSignatures; - + /** - * This method initializes the signatures but does not implement the DB connection. - * Any full (non-abstract) extension of this class should - * 1. Establish a DB connection, and - * 2. Call this procedure + * This constructor sets the type of SQL language in use. + * @param sqlQueryProvider is the provider of the SQL query strings required for actual operation of the server. + */ + public BulletinBoardSQLServer(SQLQueryProvider sqlQueryProvider) { + this.sqlQueryProvider = sqlQueryProvider; + } + + /** + * This method creates the schema in the given DB to prepare for future transactions + * It does not assume anything about the current state of the database + * @throws SQLException + */ + private void createSchema() throws SQLException { + + final int TIMEOUT = 20; + + for (String command : sqlQueryProvider.getSchemaCreationCommands()) { + jdbcTemplate.update(command,(Map) null); + } + + } + + /** + * This method initializes the signatures, connects to the DB and creates the schema (if required). */ @Override - public void init(File meerkatDB) throws CommunicationException { + public void init(String meerkatDB) throws CommunicationException { // TODO write signature reading part. digest = new SHA256Digest(); + + jdbcTemplate = new NamedParameterJdbcTemplate(sqlQueryProvider.getDataSource()); + + try { + createSchema(); + } catch (SQLException e) { + throw new CommunicationException("Couldn't create schema " + e.getMessage()); + } + } /** @@ -62,7 +260,21 @@ public abstract class BulletinBoardSQLServer implements BulletinBoardServer{ * This procedure makes sure that all tags in the given list have an entry in the tags table. * @param tags */ - protected abstract void insertNewTags(String[] tags) throws SQLException; + protected void insertNewTags(String[] tags) throws SQLException { + + String sql; + + sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.INSERT_NEW_TAG); + Map namedParameters[] = new HashMap[tags.length]; + + for (int i = 0 ; i < tags.length ; i++){ + namedParameters[i] = new HashMap(); + namedParameters[i].put("Tag", tags[i]); + } + + jdbcTemplate.batchUpdate(sql, namedParameters); + + } /** * This procedure is used to convert a boolean to a BoolMsg. @@ -74,17 +286,17 @@ public abstract class BulletinBoardSQLServer implements BulletinBoardServer{ .setValue(b) .build(); } - + @Override public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException { if (!verifyMessage(msg)) { return boolToBoolMsg(false); } - - PreparedStatement pstmt; - ResultSet rs; + String sql; + Map[] namedParameterArray; + byte[] msgID; long entryNum; @@ -102,36 +314,28 @@ public abstract class BulletinBoardSQLServer implements BulletinBoardServer{ msgID = digest.digest(); // Add message to table if needed and store entry number of message. - - try { + - sql = "SELECT EntryNum From MsgTable WHERE MsgId = ?"; - pstmt = connection.prepareStatement(sql); - pstmt.setBytes(1, msgID); - rs = pstmt.executeQuery(); - - if (rs.next()){ - - entryNum = rs.getLong(1); - - } else{ - - sql = "INSERT INTO MsgTable (MsgId, Msg) VALUES(?,?)"; - pstmt = connection.prepareStatement(sql); - pstmt.setBytes(1, msgID); - pstmt.setBytes(2, msg.getMsg().toByteArray()); - pstmt.executeUpdate(); - - rs = pstmt.getGeneratedKeys(); - rs.next(); - entryNum = rs.getLong(1); - - } - - pstmt.close(); - - } catch (SQLException e) { - throw new CommunicationException("Error inserting into MsgTable: " + e.getMessage()); + sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.FIND_MSG_ID); + Map namedParameters = new HashMap(); + namedParameters.put("MsgId",msgID); + + List entryNums = jdbcTemplate.query(sql, new MapSqlParameterSource(namedParameters), new EntryNumMapper()); + + if (entryNums.size() > 0){ + + entryNum = entryNums.get(0); + + } else{ + + sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.INSERT_MSG); + namedParameters.put("Msg", msg.getMsg().toByteArray()); + + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(sql,new MapSqlParameterSource(namedParameters),keyHolder); + + entryNum = keyHolder.getKey().longValue(); + } // Retrieve tags and store new ones in tag table. @@ -149,24 +353,18 @@ public abstract class BulletinBoardSQLServer implements BulletinBoardServer{ } // Connect message to tags. - - try{ - sql = "INSERT OR IGNORE INTO MsgTagTable (TagId, EntryNum) SELECT TagTable.TagId, ? AS EntryNum FROM TagTable WHERE Tag = ?"; - pstmt = connection.prepareStatement(sql); - - pstmt.setLong(1, entryNum); - - for (String tag : tags){ - pstmt.setString(2, tag); - pstmt.addBatch(); - } - - pstmt.executeBatch(); - pstmt.close(); - - } catch (SQLException e) { - throw new CommunicationException("Error Linking tags: " + e.getMessage()); + + sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.CONNECT_TAG); + + namedParameterArray = new HashMap[tags.length]; + + for (int i = 0 ; i < tags.length ; i++) { + namedParameterArray[i] = new HashMap(); + namedParameterArray[i].put("EntryNum", entryNum); + namedParameterArray[i].put("Tag", tags[i]); } + + jdbcTemplate.batchUpdate(sql, namedParameterArray); // Retrieve signatures. @@ -175,64 +373,112 @@ public abstract class BulletinBoardSQLServer implements BulletinBoardServer{ signatures = signatureList.toArray(signatures); // Connect message to signatures. - - try{ - sql = "INSERT OR IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (?,?,?)"; - pstmt = connection.prepareStatement(sql); - - pstmt.setLong(1, entryNum); - - for (Signature sig : signatures){ - - pstmt.setBytes(2, sig.getSignerId().toByteArray()); - pstmt.setBytes(3, sig.toByteArray()); - pstmt.addBatch(); - } - - pstmt.executeBatch(); - pstmt.close(); - - } catch (SQLException e) { - throw new CommunicationException("Error Linking tags: " + e.getMessage()); + + sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.ADD_SIGNATURE); + + namedParameterArray = new HashMap[signatures.length]; + + for (int i = 0 ; i < signatures.length ; i++) { + namedParameterArray[i] = new HashMap(); + namedParameterArray[i].put("EntryNum", entryNum); + namedParameterArray[i].put("SignerId", signatures[i].getSignerId().toByteArray()); + namedParameterArray[i].put("Signature", signatures[i].toByteArray()); } + jdbcTemplate.batchUpdate(sql,namedParameterArray); + return boolToBoolMsg(true); } - - public String testPrint(){ - - String s = ""; - - try { - - Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery("select * from MsgTable"); - while (rs.next()) { - // read the result set - s += "entry = " + rs.getInt("EntryNum") + " \n"; - s += "id = " + Arrays.toString(rs.getBytes("MsgId")) + " \n"; - s += "msg = " + Arrays.toString(rs.getBytes("Msg")) + " \n"; - s += "signer ID = " + Arrays.toString(rs.getBytes("SignerId")) + "\t\n
"; + + @Override + public BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException { + + BulletinBoardMessageList.Builder resultListBuilder = BulletinBoardMessageList.newBuilder(); + + // SQL length is roughly 50 characters per filter + 50 for the query itself + StringBuilder sqlBuilder = new StringBuilder(50 * (filterList.getFilterCount() + 1)); + + MapSqlParameterSource namedParameters; + int paramNum; + + MessageMapper messageMapper = new MessageMapper(); + SignatureMapper signatureMapper = new SignatureMapper(); + + List filters = new ArrayList(filterList.getFilterList()); + + boolean isFirstFilter = true; + + Collections.sort(filters, new FilterTypeComparator()); + + // Check if Tag/Signature tables are required for filtering purposes + + sqlBuilder.append(sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.GET_MESSAGES)); + // Add conditions + + namedParameters = new MapSqlParameterSource(); + + if (!filters.isEmpty()) { + sqlBuilder.append(" WHERE "); + + for (paramNum = 0 ; paramNum < filters.size() ; paramNum++) { + + MessageFilter filter = filters.get(paramNum); + + if (filter.getType().getNumber() != FilterType.MAX_MESSAGES_VALUE) { + if (isFirstFilter) { + isFirstFilter = false; + } else { + sqlBuilder.append(" AND "); + } + } + + sqlBuilder.append(sqlQueryProvider.getCondition(filter.getType(), paramNum)); + + SQLQueryProvider.FilterTypeParam filterTypeParam = SQLQueryProvider.FilterTypeParam.getFilterTypeParamName(filter.getType()); + + namedParameters.addValue( + filterTypeParam.getParamName() + Integer.toString(paramNum), + getParam(filter), + filterTypeParam.getParamType(), + sqlQueryProvider.getConditionParamTypeName(filter.getType())); + } - - rs = statement.executeQuery("select * from TagTable"); - while (rs.next()) { - // read the result set - s += "Tag = " + rs.getString("Tag") + " \n"; - s += "TagId = " + rs.getInt("TagId") + "\t\n
"; - } - - rs = statement.executeQuery("select * from MsgTagTable"); - while (rs.next()) { - // read the result set - s += "MsgId = " + Arrays.toString(rs.getBytes("MsgId")) + " \n"; - s += "TagId = " + rs.getInt("TagId") + "\t\n
"; - } - } catch(SQLException e){ - s += "Error reading from DB"; + } - - return s; + + // Run query + + List msgBuilders = jdbcTemplate.query(sqlBuilder.toString(), namedParameters, messageMapper); + + // Compile list of messages + + for (BulletinBoardMessage.Builder msgBuilder : msgBuilders) { + + // Retrieve signatures + + namedParameters = new MapSqlParameterSource(); + namedParameters.addValue("EntryNum", msgBuilder.getEntryNum()); + + List signatures = jdbcTemplate.query( + sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.GET_SIGNATURES), + namedParameters, + signatureMapper); + + // Append signatures + msgBuilder.addAllSig(signatures); + + // Finalize message and add to message list. + + resultListBuilder.addMessage(msgBuilder.build()); + + } + + //Combine results and return. + return resultListBuilder.build(); + } + @Override + public void close() {} + } diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java new file mode 100644 index 0000000..fa2b146 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java @@ -0,0 +1,164 @@ +package meerkat.bulletinboard.sqlserver; + +import meerkat.protobuf.BulletinBoardAPI.FilterType; +import org.h2.jdbcx.JdbcDataSource; +import javax.naming.Context; +import javax.naming.InitialContext; + +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + */ + +public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider { + + private String dbName; + + public H2QueryProvider(String dbName) { + this.dbName = dbName; + } + + + @Override + public String getSQLString(QueryType queryType) throws IllegalArgumentException{ + + switch(queryType) { + case ADD_SIGNATURE: + return "INSERT INTO SignatureTable (EntryNum, SignerId, Signature)" + + " SELECT DISTINCT :EntryNum AS Entry, :SignerId AS Id, :Signature AS Sig FROM UtilityTable AS Temp" + + " WHERE NOT EXISTS" + + " (SELECT 1 FROM SignatureTable AS SubTable WHERE SubTable.SignerId = :SignerId AND SubTable.EntryNum = :EntryNum)"; + + case CONNECT_TAG: + return "INSERT INTO MsgTagTable (TagId, EntryNum)" + + " SELECT DISTINCT TagTable.TagId, :EntryNum AS NewEntry FROM TagTable WHERE Tag = :Tag" + + " AND NOT EXISTS (SELECT 1 FROM MsgTagTable AS SubTable WHERE SubTable.TagId = TagTable.TagId" + + " AND SubTable.EntryNum = :EntryNum)"; + + case FIND_MSG_ID: + return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; + + case GET_MESSAGES: + return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; + + case GET_SIGNATURES: + return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum"; + + case INSERT_MSG: + return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId,:Msg)"; + + case INSERT_NEW_TAG: + return "INSERT INTO TagTable(Tag) SELECT DISTINCT :Tag AS NewTag FROM UtilityTable WHERE" + + " NOT EXISTS (SELECT 1 FROM TagTable AS SubTable WHERE SubTable.Tag = :Tag)"; + + default: + throw new IllegalArgumentException("Cannot serve a query of type " + queryType); + } + + } + + @Override + public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { + + String serialString = Integer.toString(serialNum); + + switch(filterType) { + case EXACT_ENTRY: + return "MsgTable.EntryNum = :EntryNum" + serialString; + case MAX_ENTRY: + return "MsgTable.EntryNum <= :EntryNum" + serialString; + case MAX_MESSAGES: + return "LIMIT :Limit" + serialString; + case MSG_ID: + return "MsgTable.MsgId = MsgId" + serialString; + case SIGNER_ID: + return "EXISTS (SELECT 1 FROM SignatureTable" + + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; + case TAG: + return "EXISTS (SELECT 1 FROM TagTable" + + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" + + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { + + switch(filterType) { + case EXACT_ENTRY: // Go through + case MAX_ENTRY: // Go through + case MAX_MESSAGES: + return "INT"; + + case MSG_ID: // Go through + case SIGNER_ID: + return "TINYBLOB"; + + case TAG: + return "VARCHAR"; + + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public DataSource getDataSource() { + + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL("jdbc:h2:~/" + dbName); + + return dataSource; + } + + + @Override + public List getSchemaCreationCommands() { + List list = new LinkedList(); + + list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY, MsgId TINYBLOB UNIQUE, Msg BLOB)"); + + list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50) UNIQUE)"); + + list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum INT, TagId INT," + + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum)," + + " FOREIGN KEY (TagId) REFERENCES TagTable(TagId)," + + " UNIQUE (EntryNum, TagID))"); + + list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB UNIQUE," + + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); + + list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); + list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId, EntryNum)"); + + // This is used to create a simple table with one entry. + // It is used for implementing a workaround for the missing INSERT IGNORE syntax + list.add("CREATE TABLE IF NOT EXISTS UtilityTable (Entry INT)"); + list.add("INSERT INTO UtilityTable (Entry) VALUES (1)"); + + return list; + } + + @Override + public List getSchemaDeletionCommands() { + List list = new LinkedList(); + + list.add("DROP TABLE IF EXISTS UtilityTable"); + list.add("DROP INDEX IF EXISTS SignerIdIndex"); + list.add("DROP TABLE IF EXISTS MsgTagTable"); + list.add("DROP TABLE IF EXISTS SignatureTable"); + list.add("DROP TABLE IF EXISTS TagTable"); + list.add("DROP TABLE IF EXISTS MsgTable"); + + return list; + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java new file mode 100644 index 0000000..c00c044 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java @@ -0,0 +1,148 @@ +package meerkat.bulletinboard.sqlserver; + +import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; +import meerkat.protobuf.BulletinBoardAPI.FilterType; + +import javax.sql.DataSource; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + */ + +public class MySQLQueryProvider implements SQLQueryProvider { + + private String dbAddress; + private int dbPort; + private String dbName; + private String username; + private String password; + + public MySQLQueryProvider(String dbAddress, int dbPort, String dbName, String username, String password) { + this.dbAddress = dbAddress; + this.dbPort = dbPort; + this.dbName = dbName; + this.username = username; + this.password = password; + } + + @Override + public String getSQLString(QueryType queryType) throws IllegalArgumentException{ + + switch(queryType) { + case ADD_SIGNATURE: + return "INSERT IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum, :SignerId, :Signature)"; + case CONNECT_TAG: + return "INSERT IGNORE INTO MsgTagTable (TagId, EntryNum)" + + " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag"; + case FIND_MSG_ID: + return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; + case GET_MESSAGES: + return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; + case GET_SIGNATURES: + return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum"; + case INSERT_MSG: + return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId, :Msg)"; + case INSERT_NEW_TAG: + return "INSERT IGNORE INTO TagTable(Tag) VALUES (:Tag)"; + default: + throw new IllegalArgumentException("Cannot serve a query of type " + queryType); + } + + } + + @Override + public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { + + String serialString = Integer.toString(serialNum); + + switch(filterType) { + case EXACT_ENTRY: + return "MsgTable.EntryNum = :EntryNum" + serialString; + case MAX_ENTRY: + return "MsgTable.EntryNum <= :EntryNum" + serialString; + case MAX_MESSAGES: + return "LIMIT :Limit" + serialString; + case MSG_ID: + return "MsgTable.MsgId = :MsgId" + serialString; + case SIGNER_ID: + return "EXISTS (SELECT 1 FROM SignatureTable" + + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; + case TAG: + return "EXISTS (SELECT 1 FROM TagTable" + + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" + + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { + + switch(filterType) { + case EXACT_ENTRY: // Go through + case MAX_ENTRY: // Go through + case MAX_MESSAGES: + return "INT"; + + case MSG_ID: // Go through + case SIGNER_ID: + return "TINYBLOB"; + + case TAG: + return "VARCHAR"; + + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public DataSource getDataSource() { + MysqlDataSource dataSource = new MysqlDataSource(); + + dataSource.setServerName(dbAddress); + dataSource.setPort(dbPort); + dataSource.setDatabaseName(dbName); + dataSource.setUser(username); + dataSource.setPassword(password); + + return dataSource; + } + + @Override + public List getSchemaCreationCommands() { + List list = new LinkedList(); + + list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY, MsgId TINYBLOB, Msg BLOB, UNIQUE(MsgId(50)))"); + + list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50), UNIQUE(Tag))"); + + list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum INT, TagId INT," + + " CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum)," + + " CONSTRAINT FOREIGN KEY (TagId) REFERENCES TagTable(TagId)," + + " CONSTRAINT UNIQUE (EntryNum, TagID))"); + + list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB," + + " INDEX(SignerId(32)), CONSTRAINT Uni UNIQUE(SignerId(32), EntryNum), CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); + + return list; + } + + @Override + public List getSchemaDeletionCommands() { + List list = new LinkedList(); + + list.add("DROP TABLE IF EXISTS MsgTagTable"); + list.add("DROP TABLE IF EXISTS SignatureTable"); + list.add("DROP TABLE IF EXISTS TagTable"); + list.add("DROP TABLE IF EXISTS MsgTable"); + + return list; + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteBulletinBoardServer.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteBulletinBoardServer.java deleted file mode 100644 index 95f1948..0000000 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteBulletinBoardServer.java +++ /dev/null @@ -1,267 +0,0 @@ -package meerkat.bulletinboard.sqlserver; - -import java.io.File; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.List; - -import com.google.protobuf.InvalidProtocolBufferException; - -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.protobuf.Crypto.Signature; -import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; -import meerkat.comm.CommunicationException; - -public class SQLiteBulletinBoardServer extends BulletinBoardSQLServer { - - protected static final int TIMEOUT = 20; - - /** - * This procedure initializes: - * 1. The database connection - * 2. The database tables (if they do not yet exist). - */ - - private void createSchema() throws SQLException { - Statement statement = connection.createStatement(); - statement.setQueryTimeout(TIMEOUT); - - statement.executeUpdate("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INTEGER PRIMARY KEY, MsgId BLOB UNIQUE, Msg BLOB)"); - - statement.executeUpdate("CREATE TABLE IF NOT EXISTS TagTable (TagId INTEGER PRIMARY KEY, Tag varchar(50) UNIQUE)"); - statement.executeUpdate("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum BLOB, TagId INTEGER, FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum), FOREIGN KEY (TagId) REFERENCES TagTable(TagId), UNIQUE (EntryNum, TagID))"); - - statement.executeUpdate("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum BLOB, SignerId BLOB, Signature BLOB UNIQUE, FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); - statement.executeUpdate("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); - - statement.close(); - } - - @Override - public void init(File meerkatDB) throws CommunicationException { - - try{ - String dbString = "jdbc:sqlite:" + meerkatDB.getPath(); - connection = DriverManager.getConnection(dbString); - createSchema(); - - super.init(meerkatDB); - - } catch (SQLException e) { - - throw new CommunicationException("Couldn't form a connection with the database" + e.getMessage()); - - } - - } - - public void close() throws CommunicationException{ - - try{ - connection.close(); - } catch (SQLException e) { - - throw new CommunicationException("Couldn't close connection to the database"); - - } - - } - - @Override - protected void insertNewTags(String[] tags) throws SQLException { - - PreparedStatement pstmt; - String sql; - - try { - - sql = "INSERT OR IGNORE INTO TagTable(Tag) VALUES (?)"; - pstmt = connection.prepareStatement(sql); - - for (String tag : tags){ - pstmt.setString(1, tag); - pstmt.addBatch(); - } - - pstmt.executeBatch(); - pstmt.close(); - - } catch (SQLException e){ - throw new SQLException("Error adding new tags to table: " + e.getMessage()); - } - - } - - @Override - public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException { - return super.postMessage(msg); - } - - @Override - public BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException{ - - PreparedStatement pstmt; - ResultSet messages, signatures; - - long entryNum; - BulletinBoardMessageList.Builder resultListBuilder = BulletinBoardMessageList.newBuilder(); - BulletinBoardMessage.Builder messageBuilder; - - String sql; - String sqlSuffix = ""; - - List filters = filterList.getFilterList(); - int i; - - boolean tagsRequired = false; - boolean signaturesRequired = false; - - boolean isFirstFilter = true; - - // Check if Tag/Signature tables are required for filtering purposes. - - for (MessageFilter filter : filters){ - if (filter.getType() == FilterType.TAG){ - tagsRequired = true; - } else if (filter.getType() == FilterType.SIGNER_ID){ - signaturesRequired = true; - } - } - - sql = "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; - - if (tagsRequired){ - sql += " INNER JOIN MsgTagTable ON MsgTable.EntryNum = MsgTagTable.EntryNum"; - sql += " INNER JOIN TagTable ON TagTable.TagId = MsgTagTable.TagId"; - } - - if (signaturesRequired){ - sql += " INNER JOIN SignatureTable ON SignatureTable.EntryNum = MsgTable.EntryNum"; - } - - // Add conditions. - - if (!filters.isEmpty()){ - sql += " WHERE"; - - for (MessageFilter filter : filters){ - - if (filter.getType().getNumber() != FilterType.MAX_MESSAGES_VALUE){ - if (isFirstFilter){ - isFirstFilter = false; - } else{ - sql += " AND"; - } - } - - switch (filter.getType().getNumber()){ - case FilterType.EXACT_ENTRY_VALUE: - sql += " MsgTable.EntryNum = ?"; - break; - case FilterType.MAX_ENTRY_VALUE: - sql += " MsgTable.EntryNum <= ?"; - break; - case FilterType.MAX_MESSAGES_VALUE: - sqlSuffix += " LIMIT = ?"; - break; - case FilterType.MSG_ID_VALUE: - sql += " MsgTableMsgId = ?"; - break; - case FilterType.SIGNER_ID_VALUE: - sql += " SignatureTable.SignerId = ?"; - break; - case FilterType.TAG_VALUE: - sql += " TagTable.Tag = ?"; - break; - } - } - - sql += sqlSuffix; - } - - // Make query. - - try { - pstmt = connection.prepareStatement(sql); - - // Specify values for filters. - - i = 1; - for (MessageFilter filter : filters){ - - switch (filter.getType().getNumber()){ - - case FilterType.EXACT_ENTRY_VALUE: // Go through. - case FilterType.MAX_ENTRY_VALUE: - pstmt.setLong(i, filter.getEntry()); - i++; - break; - - case FilterType.MSG_ID_VALUE: // Go through. - case FilterType.SIGNER_ID_VALUE: - pstmt.setBytes(i, filter.getId().toByteArray()); - i++; - break; - - case FilterType.TAG_VALUE: - pstmt.setString(i, filter.getTag()); - break; - - // The max-messages condition is applied as a suffix. Therefore, it is treated differently. - case FilterType.MAX_MESSAGES_VALUE: - pstmt.setLong(filters.size(), filter.getMaxMessages()); - break; - - } - } - - // Run query. - - messages = pstmt.executeQuery(); - - // Compile list of messages. - - sql = "SELECT Signature FROM SignatureTable WHERE EntryNum = ?"; - pstmt = connection.prepareStatement(sql); - - while (messages.next()){ - - // Get entry number and retrieve signatures. - - entryNum = messages.getLong(1); - pstmt.setLong(1, entryNum); - signatures = pstmt.executeQuery(); - - // Create message and append signatures. - - messageBuilder = BulletinBoardMessage.newBuilder() - .setEntryNum(entryNum) - .setMsg(UnsignedBulletinBoardMessage.parseFrom(messages.getBytes(2))); - - while (signatures.next()){ - messageBuilder.addSig(Signature.parseFrom(signatures.getBytes(1))); - } - - // Finalize message and add to message list. - - resultListBuilder.addMessage(messageBuilder.build()); - - } - - pstmt.close(); - - } catch (SQLException e){ - throw new CommunicationException("Error reading messages from DB: " + e.getMessage()); - } catch (InvalidProtocolBufferException e) { - throw new CommunicationException("Invalid data from DB: " + e.getMessage()); - } - - //Combine results and return. - - return resultListBuilder.build(); - } - -} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java new file mode 100644 index 0000000..945ae47 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java @@ -0,0 +1,123 @@ +package meerkat.bulletinboard.sqlserver; + +import meerkat.protobuf.BulletinBoardAPI.*; +import org.sqlite.SQLiteDataSource; + +import javax.sql.DataSource; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + */ + +public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvider { + + String dbName; + + public SQLiteQueryProvider(String dbName) { + this.dbName = dbName; + } + + @Override + public String getSQLString(QueryType queryType) throws IllegalArgumentException{ + + switch(queryType) { + case ADD_SIGNATURE: + return "INSERT OR IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum,:SignerId,:Signature)"; + case CONNECT_TAG: + return "INSERT OR IGNORE INTO MsgTagTable (TagId, EntryNum)" + + " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag"; + case FIND_MSG_ID: + return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; + case GET_MESSAGES: + return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; + case GET_SIGNATURES: + return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum"; + case INSERT_MSG: + return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId,:Msg)"; + case INSERT_NEW_TAG: + return "INSERT OR IGNORE INTO TagTable(Tag) VALUES (:Tag)"; + default: + throw new IllegalArgumentException("Cannot serve a query of type " + queryType); + } + + } + + @Override + public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { + + String serialString = Integer.toString(serialNum); + + switch(filterType) { + case EXACT_ENTRY: + return "MsgTable.EntryNum = :EntryNum" + serialString; + case MAX_ENTRY: + return "MsgTable.EntryNum <= :EntryNum" + serialString; + case MAX_MESSAGES: + return "LIMIT = :Limit" + serialString; + case MSG_ID: + return "MsgTable.MsgId = :MsgId" + serialString; + case SIGNER_ID: + return "EXISTS (SELECT 1 FROM SignatureTable" + + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; + case TAG: + return "EXISTS (SELECT 1 FROM TagTable" + + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" + + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { + return null; //TODO: write this. + } + + @Override + public DataSource getDataSource() { + // TODO: Fix this + SQLiteDataSource dataSource = new SQLiteDataSource(); + dataSource.setUrl("jdbc:sqlite:" + dbName); + dataSource.setDatabaseName("meerkat"); //TODO: Make generic + + return dataSource; + } + + + @Override + public List getSchemaCreationCommands() { + List list = new LinkedList(); + + list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INTEGER PRIMARY KEY, MsgId BLOB UNIQUE, Msg BLOB)"); + + list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INTEGER PRIMARY KEY, Tag varchar(50) UNIQUE)"); + list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum BLOB, TagId INTEGER, FOREIGN KEY (EntryNum)" + + " REFERENCES MsgTable(EntryNum), FOREIGN KEY (TagId) REFERENCES TagTable(TagId), UNIQUE (EntryNum, TagID))"); + + list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INTEGER, SignerId BLOB, Signature BLOB," + + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); + + list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); + list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId, EntryNum)"); + + return list; + } + + @Override + public List getSchemaDeletionCommands() { + List list = new LinkedList(); + + list.add("DROP TABLE IF EXISTS MsgTagTable"); + + list.add("DROP INDEX IF EXISTS SignerIndex"); + list.add("DROP TABLE IF EXISTS SignatureTable"); + + list.add("DROP TABLE IF EXISTS TagTable"); + list.add("DROP TABLE IF EXISTS MsgTable"); + + return list; + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/EntryNumMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/EntryNumMapper.java new file mode 100644 index 0000000..478c39e --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/EntryNumMapper.java @@ -0,0 +1,18 @@ +package meerkat.bulletinboard.sqlserver.mappers; + +import meerkat.protobuf.BulletinBoardAPI.MessageID; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Arbel Deutsch Peled on 11-Dec-15. + */ +public class EntryNumMapper implements RowMapper { + + @Override + public Long mapRow(ResultSet rs, int rowNum) throws SQLException { + return rs.getLong(1); + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageMapper.java new file mode 100644 index 0000000..fdc1fa8 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageMapper.java @@ -0,0 +1,32 @@ +package meerkat.bulletinboard.sqlserver.mappers; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; +import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Arbel Deutsch Peled on 11-Dec-15. + */ +public class MessageMapper implements RowMapper { + + @Override + public BulletinBoardMessage.Builder mapRow(ResultSet rs, int rowNum) throws SQLException { + + BulletinBoardMessage.Builder builder = BulletinBoardMessage.newBuilder(); + + try { + builder.setEntryNum(rs.getLong(1)) + .setMsg(UnsignedBulletinBoardMessage.parseFrom(rs.getBytes(2))); + + } catch (InvalidProtocolBufferException e) { + throw new SQLException(e.getMessage(), e); + } + + return builder; + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/SignatureMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/SignatureMapper.java new file mode 100644 index 0000000..60015c1 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/SignatureMapper.java @@ -0,0 +1,28 @@ +package meerkat.bulletinboard.sqlserver.mappers; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; +import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; +import meerkat.protobuf.Crypto.Signature; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Arbel Deutsch Peled on 11-Dec-15. + */ +public class SignatureMapper implements RowMapper { + + @Override + public Signature mapRow(ResultSet rs, int rowNum) throws SQLException { + + try { + return Signature.parseFrom(rs.getBytes(1)); + } catch (InvalidProtocolBufferException e) { + throw new SQLException(e.getMessage(), e); + } + + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java index 1b8883a..b3fc03c 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java @@ -1,16 +1,21 @@ package meerkat.bulletinboard.webapp; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import meerkat.bulletinboard.BulletinBoardServer; -import meerkat.bulletinboard.sqlserver.SQLiteBulletinBoardServer; +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; +import meerkat.bulletinboard.sqlserver.H2QueryProvider; +import meerkat.bulletinboard.sqlserver.MySQLQueryProvider; +import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider; import meerkat.comm.CommunicationException; import meerkat.protobuf.BulletinBoardAPI.BoolMsg; import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; @@ -18,42 +23,89 @@ import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessageList; import meerkat.protobuf.BulletinBoardAPI.MessageFilterList; import meerkat.rest.Constants; -import java.io.File; - -@Path("/sqlserver") -public class BulletinBoardWebApp implements BulletinBoardServer { +@Path(Constants.BULLETIN_BOARD_SERVER_PATH) +public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextListener{ + + private static final String BULLETIN_BOARD_ATTRIBUTE_NAME = "bulletinBoard"; + + @Context ServletContext servletContext; BulletinBoardServer bulletinBoard; - @PostConstruct + /** + * This is the servlet init method. + */ + public void init(){ + bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME); + } + + /** + * This is the BulletinBoard init method. + */ @Override - public void init(File meerkatDB) throws CommunicationException { - bulletinBoard = new SQLiteBulletinBoardServer(); + public void init(String meerkatDB) throws CommunicationException { bulletinBoard.init(meerkatDB); } - @Path("postmessage") + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) { + ServletContext servletContext = servletContextEvent.getServletContext(); + String dbType = servletContext.getInitParameter("dbType"); + String dbName = servletContext.getInitParameter("dbName"); + + if ("SQLite".equals(dbType)){ + + bulletinBoard = new BulletinBoardSQLServer(new SQLiteQueryProvider(dbName)); + + } else if ("H2".equals(dbType)) { + + bulletinBoard = new BulletinBoardSQLServer(new H2QueryProvider(dbName)); + + } else if ("MySQL".equals(dbType)) { + + String dbAddress = servletContext.getInitParameter("dbAddress"); + int dbPort = Integer.parseInt(servletContext.getInitParameter("dbPort")); + String username = servletContext.getInitParameter("username"); + String password = servletContext.getInitParameter("password"); + + bulletinBoard = new BulletinBoardSQLServer(new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password)); + } + + try { + init(dbName); + servletContext.setAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME, bulletinBoard); + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + } + } + + @Path(Constants.POST_MESSAGE_PATH) @POST @Consumes(Constants.MEDIATYPE_PROTOBUF) @Produces(Constants.MEDIATYPE_PROTOBUF) @Override public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException { + init(); return bulletinBoard.postMessage(msg); } - @Path("readmessages") + @Path(Constants.READ_MESSAGES_PATH) @POST @Consumes(Constants.MEDIATYPE_PROTOBUF) @Produces(Constants.MEDIATYPE_PROTOBUF) @Override public BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException { + init(); return bulletinBoard.readMessages(filterList); } @Override - @PreDestroy - public void close() throws CommunicationException { - bulletinBoard.close(); + public void close(){ + try { + bulletinBoard.close(); + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + } } @GET @@ -62,4 +114,11 @@ public class BulletinBoardWebApp implements BulletinBoardServer { return "This BulletinBoard is up and running!\n Please consult the API documents to perform queries."; } + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + ServletContext servletContext = servletContextEvent.getServletContext(); + bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME); + close(); + } + } diff --git a/bulletin-board-server/src/main/webapp/WEB-INF/web.xml b/bulletin-board-server/src/main/webapp/WEB-INF/web.xml index cc90843..2198c07 100644 --- a/bulletin-board-server/src/main/webapp/WEB-INF/web.xml +++ b/bulletin-board-server/src/main/webapp/WEB-INF/web.xml @@ -14,4 +14,25 @@ Jersey Hello World /* + + dbAddress + localhost + + dbPort + 3306 + + dbName + meerkat + + username + arbel + + password + mypass + + dbType + SQLite + + meerkat.bulletinboard.webapp.BulletinBoardWebApp + diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteServerIntegrationTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java similarity index 66% rename from bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteServerIntegrationTest.java rename to bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java index e090529..838adcc 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteServerIntegrationTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java @@ -19,19 +19,17 @@ import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Response; -public class SQLiteServerIntegrationTest { +public class BulletinBoardSQLServerIntegrationTest { private static String PROP_GETTY_URL = "gretty.httpBaseURI"; - private static String DEFAULT_BASE_URL = "localhost:8081"; + private static String DEFAULT_BASE_URL = "http://localhost:8081"; private static String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL); - private static String SQL_SERVER_POST = "sqlserver/postmessage"; - private static String SQL_SERVER_GET = "sqlserver/readmessages"; Client client; -// Connection connection; @Before public void setup() throws Exception { + System.err.println("Registering client"); client = ClientBuilder.newClient(); client.register(ProtobufMessageBodyReader.class); client.register(ProtobufMessageBodyWriter.class); @@ -54,17 +52,11 @@ public class SQLiteServerIntegrationTest { MessageFilterList filterList; BulletinBoardMessageList msgList; -// try{ -// connection = DriverManager.getConnection("jdbc:sqlite:d:/arbel/projects/meerkat-java/bulletin-board-server/local-instances/meerkat.db"); -// } catch (SQLException e) { -// System.err.println(e.getMessage()); -// assert false; -// } - // Test writing mechanism - System.err.println("******** Testing: " + SQL_SERVER_POST); - webTarget = client.target(BASE_URL).path(SQL_SERVER_POST); + System.err.println("******** Testing: " + Constants.POST_MESSAGE_PATH); + webTarget = client.target(BASE_URL).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.POST_MESSAGE_PATH); + System.err.println(webTarget.getUri()); msg = BulletinBoardMessage.newBuilder() .setMsg(UnsignedBulletinBoardMessage.newBuilder() @@ -109,9 +101,8 @@ public class SQLiteServerIntegrationTest { // Test reading mechanism - System.err.println("******** Testing: " + SQL_SERVER_GET); - webTarget = client.target(BASE_URL).path(SQL_SERVER_GET); - + System.err.println("******** Testing: " + Constants.READ_MESSAGES_PATH); + webTarget = client.target(BASE_URL).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH); filterList = MessageFilterList.newBuilder() .addFilter( MessageFilter.newBuilder() @@ -120,47 +111,7 @@ public class SQLiteServerIntegrationTest { .build() ) .build(); - -// String sql = "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable INNER JOIN SignatureTable ON SignatureTable.EntryNum = MsgTable.EntryNum WHERE SignatureTable.SignerId = ?"; -// PreparedStatement pstmt = connection.prepareStatement(sql); -// int i=1; -// for (MessageFilter filter : filterList.getFilterList()){ -// -// switch (filter.getType().getNumber()){ -// -// case FilterType.EXACT_ENTRY_VALUE: // Go through. -// case FilterType.MAX_ENTRY_VALUE: -// pstmt.setLong(i, filter.getEntry()); -// i++; -// break; -// -// case FilterType.MSG_ID_VALUE: // Go through. -// case FilterType.SIGNER_ID_VALUE: -// pstmt.setBytes(i, filter.getId().toByteArray()); -// i++; -// break; -// -// case FilterType.TAG_VALUE: -// pstmt.setString(i, filter.getTag()); -// break; -// -// // The max-messages condition is applied as a suffix. Therefore, it is treated differently. -// case FilterType.MAX_MESSAGES_VALUE: -// pstmt.setLong(filterList.getFilterList().size(), filter.getMaxMessages()); -// break; -// -// } -// } -// ResultSet rs = pstmt.executeQuery(); -// -// i = 0; -// while (rs.next()){ -// i++; -// assert rs.getBytes(2) -// } -// System.err.println("Local DB size = " + i); -// pstmt.close(); - + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); System.err.println(response); msgList = response.readEntity(BulletinBoardMessageList.class); diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardServerTest.java deleted file mode 100644 index eb43f15..0000000 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardServerTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package meerkat.bulletinboard; - -import org.junit.Test; - -import meerkat.bulletinboard.sqlserver.SQLiteBulletinBoardServer; - -public class BulletinBoardServerTest { - - @Test - public void testAllServers() throws Exception { - GenericBulletinBoardServerTest bbst = new GenericBulletinBoardServerTest(); - - bbst.init(SQLiteBulletinBoardServer.class); - bbst.bulkTest(); - bbst.close(); - } -} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java index 279ee7c..4799e0d 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java @@ -1,8 +1,9 @@ package meerkat.bulletinboard; -import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.KeyStore; @@ -28,130 +29,239 @@ import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; public class GenericBulletinBoardServerTest { - private BulletinBoardServer bulletinBoardServer; + + protected BulletinBoardServer bulletinBoardServer; private ECDSASignature signers[]; + private ByteString[] signerIDs; private Random random; - + private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; - private static String KEYFILE_PASSWORD = "secret"; + private static String KEYFILE_EXAMPLE3 = "/certs/enduser-certs/user3-key-with-password-shh.p12"; + + private static String KEYFILE_PASSWORD1 = "secret"; + private static String KEYFILE_PASSWORD3 = "shh"; public static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt"; - -// private static String KEYFILE_EXAMPLE2 = "/certs/enduser-certs/user2-key.pem"; - - public void init(Class cls) throws InstantiationException, IllegalAccessException, CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, CommunicationException{ - bulletinBoardServer = (BulletinBoardServer) cls.newInstance(); + public static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt"; - bulletinBoardServer.init(File.createTempFile("meerkat-test", "db")); + private final int TAG_NUM = 5; // Number of tags. + private final int MESSAGE_NUM = 32; // Number of messages (2^TAG_NUM). + + private String[] tags; + private byte[][] data; + + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests + + /** + * @param bulletinBoardServer is an initialized server. + * @throws InstantiationException + * @throws IllegalAccessException + * @throws CertificateException + * @throws KeyStoreException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws UnrecoverableKeyException + * @throws CommunicationException + */ + public void init(BulletinBoardServer bulletinBoardServer) { + + System.err.println("Starting to initialize GenericBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + this.bulletinBoardServer = bulletinBoardServer; signers = new ECDSASignature[2]; + signerIDs = new ByteString[signers.length]; signers[0] = new ECDSASignature(); signers[1] = new ECDSASignature(); InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); - char[] password = KEYFILE_PASSWORD.toCharArray(); - - KeyStore.Builder keyStore = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); - signers[0].loadSigningCertificate(keyStore); - - signers[0].loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE)); + char[] password = KEYFILE_PASSWORD1.toCharArray(); + + KeyStore.Builder keyStoreBuilder = null; + try { + keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); + + signers[0].loadSigningCertificate(keyStoreBuilder); + + signers[0].loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE)); + + keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE3); + password = KEYFILE_PASSWORD3.toCharArray(); + + keyStoreBuilder = signers[1].getPKCS12KeyStoreBuilder(keyStream, password); + signers[1].loadSigningCertificate(keyStoreBuilder); + + signers[1].loadVerificationCertificates(getClass().getResourceAsStream(CERT3_PEM_EXAMPLE)); + + for (int i = 0 ; i < signers.length ; i++) { + signerIDs[i] = signers[i].getSignerID(); + } + + } catch (IOException e) { + System.err.println("Failed reading from signature file " + e.getMessage()); + fail("Failed reading from signature file " + e.getMessage()); + } catch (CertificateException e) { + System.err.println("Failed reading certificate " + e.getMessage()); + fail("Failed reading certificate " + e.getMessage()); + } catch (KeyStoreException e) { + System.err.println("Failed reading keystore " + e.getMessage()); + fail("Failed reading keystore " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + System.err.println("Couldn't find signing algorithm " + e.getMessage()); + fail("Couldn't find signing algorithm " + e.getMessage()); + } catch (UnrecoverableKeyException e) { + System.err.println("Couldn't find signing key " + e.getMessage()); + fail("Couldn't find signing key " + e.getMessage()); + } random = new Random(0); // We use insecure randomness in tests for repeatability + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished initializing GenericBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); } private byte randomByte(){ return (byte) random.nextInt(); } - + private String randomString(){ return new BigInteger(130, random).toString(32); } - - public void bulkTest() throws CommunicationException, SignatureException, InvalidKeyException, CertificateException, IOException{ - - final int TAG_NUM = 5; // Number of tags. - final int MESSAGE_NUM = 32; // Number of messages (2^TAG_NUM). + + /** + * Tests writing of several messages with multiple tags and signatures. + * @throws CommunicationException + * @throws SignatureException + * @throws InvalidKeyException + * @throws CertificateException + * @throws IOException + */ + public void testInsert() { + + System.err.println("Starting to insert messages to DB"); + long start = threadBean.getCurrentThreadCpuTime(); + final int BYTES_PER_MESSAGE_DATA = 50; // Message size. - - String[] tags = new String[TAG_NUM]; - byte[][] data = new byte[MESSAGE_NUM][BYTES_PER_MESSAGE_DATA]; - + + tags = new String[TAG_NUM]; + data = new byte[MESSAGE_NUM][BYTES_PER_MESSAGE_DATA]; + UnsignedBulletinBoardMessage.Builder unsignedMsgBuilder; BulletinBoardMessage.Builder msgBuilder; - - int i,j; - + + int i, j; + // Generate random data. - - for (i = 1 ; i <= MESSAGE_NUM ; i++){ - for (j = 0 ; j < BYTES_PER_MESSAGE_DATA ; j++){ - data[i-1][j] = randomByte(); + + for (i = 1; i <= MESSAGE_NUM; i++) { + for (j = 0; j < BYTES_PER_MESSAGE_DATA; j++) { + data[i - 1][j] = randomByte(); } } - - for (i = 0 ; i < TAG_NUM ; i++){ + + for (i = 0; i < TAG_NUM; i++) { tags[i] = randomString(); } - + // Build messages. - - for (i = 1 ; i <= MESSAGE_NUM ; i++){ + + for (i = 1; i <= MESSAGE_NUM; i++) { unsignedMsgBuilder = UnsignedBulletinBoardMessage.newBuilder() - .setData(ByteString.copyFrom(data[i-1])); - + .setData(ByteString.copyFrom(data[i - 1])); + // Add tags based on bit-representation of message number. - + int copyI = i; - for (j = 0 ; j < TAG_NUM ; j++){ - if (copyI % 2 == 1){ + for (j = 0; j < TAG_NUM; j++) { + if (copyI % 2 == 1) { unsignedMsgBuilder.addTag(tags[j]); } - + copyI >>>= 1; } - + // Build message. - + msgBuilder = BulletinBoardMessage.newBuilder() .setMsg(unsignedMsgBuilder.build()); - + // Add signatures. - - if (i % 2 == 1){ - signers[0].updateContent(msgBuilder.getMsg()); - msgBuilder.addSig(signers[0].sign()); - } - - // Post message. - - bulletinBoardServer.postMessage(msgBuilder.build()); + + try { + + if (i % 2 == 1) { + signers[0].updateContent(msgBuilder.getMsg()); + msgBuilder.addSig(signers[0].sign()); + + if (i % 4 == 1) { + signers[1].updateContent(msgBuilder.getMsg()); + msgBuilder.addSig(signers[1].sign()); + } + } + + } catch (SignatureException e) { + fail(e.getMessage()); + } + + // Post message. + + try { + bulletinBoardServer.postMessage(msgBuilder.build()); + } catch (CommunicationException e) { + fail(e.getMessage()); + } } - + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished inserting messages to DB"); + System.err.println("Time of operation: " + (end - start)); + + } + + /** + * Tests retrieval of messages written in {@Link #testInsert()} + * Only queries using one tag filter + */ + public void testSimpleTagAndSignature(){ + + System.err.println("Starting to test tag and signature mechanism"); + long start = threadBean.getCurrentThreadCpuTime(); + + List messages; + // Check tag mechanism - for (i = 0 ; i < TAG_NUM ; i++){ + for (int i = 0 ; i < TAG_NUM ; i++){ // Retrieve messages having tag i - - List messages = - bulletinBoardServer.readMessages( - MessageFilterList.newBuilder() - .addFilter(MessageFilter.newBuilder() - .setType(FilterType.TAG) - .setTag(tags[i]) + + try { + + messages = bulletinBoardServer.readMessages( + MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag(tags[i]) + .build() + ) .build() - ) - .build() ) .getMessageList(); - + + } catch (CommunicationException e) { + fail(e.getMessage()); + return; + } + // Assert that the number of retrieved messages is correct. assertThat(messages.size(), is(MESSAGE_NUM / 2)); // Assert the identity of the messages. - + for (BulletinBoardMessage msg : messages){ // Assert serial number and raw data. @@ -160,20 +270,122 @@ public class GenericBulletinBoardServerTest { assertThat(msg.getMsg().getData().toByteArray(), is(data[(int) msg.getEntryNum() - 1])); // Assert signatures. - - if (msg.getEntryNum() % 2 == 1){ - signers[0].initVerify(msg.getSig(0)); - signers[0].updateContent(msg.getMsg()); - assertTrue("Signature did not verify!", signers[0].verify()); + + try { + + if (msg.getEntryNum() % 2 == 1) { + signers[0].initVerify(msg.getSig(0)); + signers[0].updateContent(msg.getMsg()); + assertTrue("Signature did not verify!", signers[0].verify()); + + if (msg.getEntryNum() % 4 == 1) { + signers[1].initVerify(msg.getSig(1)); + signers[1].updateContent(msg.getMsg()); + assertTrue("Signature did not verify!", signers[1].verify()); + + assertThat(msg.getSigCount(), is(2)); + } else { + assertThat(msg.getSigCount(), is(1)); + } + } else { + assertThat(msg.getSigCount(), is(0)); + } + } catch (Exception e) { + fail(e.getMessage()); } } } - + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished testing tag and signature mechanism"); + System.err.println("Time of operation: " + (end - start)); + + } + + /** + * Tests retrieval of messages written in {@Link #testInsert()} using multiple tags/signature filters. + */ + public void testEnhancedTagsAndSignatures(){ + + System.err.println("Starting to test multiple tags and signatures"); + long start = threadBean.getCurrentThreadCpuTime(); + + List messages; + MessageFilterList.Builder filterListBuilder = MessageFilterList.newBuilder(); + + int expectedMsgCount = MESSAGE_NUM; + + // Check multiple tag filters. + + for (int i = 0 ; i < TAG_NUM ; i++) { + + filterListBuilder.addFilter( + MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag(tags[i]) + .build() + ); + + try { + messages = bulletinBoardServer.readMessages(filterListBuilder.build()).getMessageList(); + } catch (CommunicationException e) { + System.err.println("Failed retrieving multi-tag messages from DB: " + e.getMessage()); + fail("Failed retrieving multi-tag messages from DB: " + e.getMessage()); + return; + } + + expectedMsgCount /= 2; + + assertThat(messages.size(), is(expectedMsgCount)); + + for (BulletinBoardMessage msg : messages) { + for (int j = 0 ; j <= i ; j++) { + assertThat((msg.getEntryNum() >>> j) % 2, is((long) 1)); + } + } + } + + // Check multiple signature filters. + + filterListBuilder = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.SIGNER_ID) + .setId(signerIDs[0]) + .build()) + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.SIGNER_ID) + .setId(signerIDs[1]) + .build()); + + try { + messages = bulletinBoardServer.readMessages(filterListBuilder.build()).getMessageList(); + } catch (CommunicationException e) { + System.err.println("Failed retrieving multi-signature message from DB: " + e.getMessage()); + fail("Failed retrieving multi-signature message from DB: " + e.getMessage()); + return; + } + + assertThat(messages.size(), is(MESSAGE_NUM / 4)); + + for (BulletinBoardMessage message : messages) { + assertThat(message.getEntryNum() % 4, is((long) 1)); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished testing multiple tags and signatures"); + System.err.println("Time of operation: " + (end - start)); + } public void close(){ signers[0].clearSigningKey(); signers[1].clearSigningKey(); + try { + bulletinBoardServer.close(); + } catch (CommunicationException e) { + System.err.println("Error closing server " + e.getMessage()); + fail("Error closing server " + e.getMessage()); + } } } diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java new file mode 100644 index 0000000..ef19310 --- /dev/null +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java @@ -0,0 +1,122 @@ +package meerkat.bulletinboard; + +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; +import meerkat.bulletinboard.sqlserver.H2QueryProvider; +import meerkat.comm.CommunicationException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Result; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.sql.*; +import java.util.List; + +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 07-Dec-15. + */ +public class H2BulletinBoardServerTest { + + private final String dbName = "meerkatTest"; + + private GenericBulletinBoardServerTest serverTest; + + private SQLQueryProvider queryProvider; + + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests + + @Before + public void init(){ + + System.err.println("Starting to initialize H2BulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + queryProvider = new H2QueryProvider(dbName); + + try { + + Connection conn = queryProvider.getDataSource().getConnection(); + Statement stmt = conn.createStatement(); + + List deletionQueries = queryProvider.getSchemaDeletionCommands(); + + for (String deletionQuery : deletionQueries) { + stmt.execute(deletionQuery); + } + + } catch (SQLException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider); + try { + bulletinBoardServer.init(""); + + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + return; + } + + serverTest = new GenericBulletinBoardServerTest(); + try { + serverTest.init(bulletinBoardServer); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished initializing H2BulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @Test + public void bulkTest() { + System.err.println("Starting bulkTest of H2BulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + try { + serverTest.testInsert(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testSimpleTagAndSignature(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testEnhancedTagsAndSignatures(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished bulkTest of H2BulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @After + public void close() { + System.err.println("Starting to close H2BulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + serverTest.close(); + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished closing H2BulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + +} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java new file mode 100644 index 0000000..e473931 --- /dev/null +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java @@ -0,0 +1,126 @@ +package meerkat.bulletinboard; + +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; +import meerkat.bulletinboard.sqlserver.MySQLQueryProvider; +import meerkat.comm.CommunicationException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 07-Dec-15. + */ +public class MySQLBulletinBoardServerTest { + + private final String dbAddress = "localhost"; + private final int dbPort = 3306; + private final String dbName = "meerkat"; + private final String username = "arbel"; + private final String password = "mypass"; + + private GenericBulletinBoardServerTest serverTest; + + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests + + @Before + public void init(){ + + System.err.println("Starting to initialize MySQLBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + SQLQueryProvider queryProvider = new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password); + + try { + + Connection conn = queryProvider.getDataSource().getConnection(); + Statement stmt = conn.createStatement(); + + List deletionQueries = queryProvider.getSchemaDeletionCommands(); + + for (String deletionQuery : deletionQueries) { + stmt.execute(deletionQuery); + } + + } catch (SQLException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider); + try { + bulletinBoardServer.init(""); + + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + return; + } + + serverTest = new GenericBulletinBoardServerTest(); + try { + serverTest.init(bulletinBoardServer); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished initializing MySQLBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @Test + public void bulkTest() { + System.err.println("Starting bulkTest of MySQLBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + try { + serverTest.testInsert(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testSimpleTagAndSignature(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testEnhancedTagsAndSignatures(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished bulkTest of MySQLBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @After + public void close() { + System.err.println("Starting to close MySQLBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + serverTest.close(); + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished closing MySQLBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + +} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java new file mode 100644 index 0000000..1d7aae0 --- /dev/null +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java @@ -0,0 +1,106 @@ +package meerkat.bulletinboard; + +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; +import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider; +import meerkat.comm.CommunicationException; +import meerkat.protobuf.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.security.*; +import java.security.cert.CertificateException; + +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 07-Dec-15. + */ +public class SQLiteBulletinBoardServerTest{ + + private String testFilename = "SQLiteDBTest.db"; + + private GenericBulletinBoardServerTest serverTest; + + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests + + @Before + public void init(){ + + System.err.println("Starting to initialize SQLiteBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + File old = new File(testFilename); + old.delete(); + + BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(new SQLiteQueryProvider(testFilename)); + try { + bulletinBoardServer.init(""); + + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + return; + } + + serverTest = new GenericBulletinBoardServerTest(); + try { + serverTest.init(bulletinBoardServer); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished initializing SQLiteBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @Test + public void bulkTest() { + System.err.println("Starting bulkTest of SQLiteBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + try { + serverTest.testInsert(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testSimpleTagAndSignature(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testEnhancedTagsAndSignatures(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished bulkTest of SQLiteBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @After + public void close() { + System.err.println("Starting to close SQLiteBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + serverTest.close(); + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished closing SQLiteBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 05ef575..30d399d 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2237c68..864f396 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Mon Oct 26 15:30:44 IST 2015 +#Tue Aug 05 03:26:05 IDT 2014 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip +distributionSha256Sum=4647967f8de78d6d6d8093cdac50f368f8c2b8038f41a5afe1c3bce4c69219a9 diff --git a/gradlew b/gradlew index 9d82f78..91a7e26 100755 --- a/gradlew +++ b/gradlew @@ -42,6 +42,11 @@ case "`uname`" in ;; esac +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" @@ -56,9 +61,9 @@ while [ -h "$PRG" ] ; do fi done SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null +cd "`dirname \"$PRG\"`/" >&- APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null +cd "$SAVED" >&- CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -109,7 +114,6 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` diff --git a/meerkat-common/build.gradle b/meerkat-common/build.gradle index d2fe0fd..c510e0a 100644 --- a/meerkat-common/build.gradle +++ b/meerkat-common/build.gradle @@ -45,10 +45,12 @@ dependencies { // Google protobufs compile 'com.google.protobuf:protobuf-java:3.+' + // ListeningExecutor + compile 'com.google.guava:guava:11.0.+' + // Crypto - compile 'org.factcenter.qilin:qilin:1.1+' + compile 'org.factcenter.qilin:qilin:1.2+' compile 'org.bouncycastle:bcprov-jdk15on:1.53' - compile 'org.bouncycastle:bcpkix-jdk15on:1.53' testCompile 'junit:junit:4.+' diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoard.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoard.java deleted file mode 100644 index 0efd6a7..0000000 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoard.java +++ /dev/null @@ -1,48 +0,0 @@ -package meerkat.bulletinboard; - -import meerkat.comm.*; -import static meerkat.protobuf.BulletinBoardAPI.*; - -import java.util.List; - -/** - * Created by talm on 24/10/15. - */ -public interface BulletinBoard { - /** - * Post a message to the bulletin board - * @param msg - */ - MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException; - - /** - * Check how "safe" a given message is - * @param id - * @return a normalized "redundancy score" from 0 (local only) to 1 (fully published) - */ - float getRedundancy(MessageID id); - - /** - * Read all messages posted matching the given filter - * Note that if messages haven't been "fully posted", this might return a different - * set of messages in different calls. However, messages that are fully posted - * are guaranteed to be included. - * @param filter return only messages that match the filter (null means no filtering). - * @param max maximum number of messages to return (0=no limit) - * @return - */ - List readMessages(MessageFilter filter, int max); - - interface MessageCallback { - void handleNewMessage(BulletinBoardMessage msg); - } - - /** - * Register a callback that will be called with each new message that is posted. - * The callback will be called only once for each message. - * @param callback - * @param filter only call back for messages that match the filter. - */ - void registerNewMessageCallback(MessageCallback callback, MessageFilter filter); - -} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java new file mode 100644 index 0000000..c51e561 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java @@ -0,0 +1,54 @@ +package meerkat.bulletinboard; + +import meerkat.comm.*; +import meerkat.protobuf.Voting.*; + +import static meerkat.protobuf.BulletinBoardAPI.*; + +import java.util.List; + +/** + * Created by talm on 24/10/15. + */ +public interface BulletinBoardClient { + + interface ClientCallback { + void handleCallback(T msg); + void handleFailure(Throwable t); + } + + /** + * Initialize the client to use some specified servers + * @param clientParams contains the parameters required for the client setup + */ + void init(BulletinBoardClientParams clientParams); + + /** + * Post a message to the bulletin board + * @param msg + */ + MessageID postMessage(BulletinBoardMessage msg, ClientCallback callback); + + /** + * Check how "safe" a given message is + * @param id + * @return a normalized "redundancy score" from 0 (local only) to 1 (fully published) + */ + void getRedundancy(MessageID id, ClientCallback callback); + + /** + * Read all messages posted matching the given filter + * Note that if messages haven't been "fully posted", this might return a different + * set of messages in different calls. However, messages that are fully posted + * are guaranteed to be included. + * @param filterList return only messages that match the filters (null means no filtering). + */ + void readMessages(MessageFilterList filterList, ClientCallback> callback); + + /** + * Closes all connections, if any. + * This is done in a synchronous (blocking) way. + */ + void close(); + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java index 298c290..da53c1f 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java @@ -3,8 +3,6 @@ package meerkat.bulletinboard; import meerkat.comm.CommunicationException; import meerkat.protobuf.BulletinBoardAPI.*; -import java.io.File; - /** * Created by Arbel on 07/11/15. * @@ -19,7 +17,7 @@ public interface BulletinBoardServer{ * It also establishes the connection to the DB. * @throws CommunicationException on DB connection error. */ - public void init(File meerkatDB) throws CommunicationException; + public void init(String meerkatDB) throws CommunicationException; /** * Post a message to bulletin board. diff --git a/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java b/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java index 5386c5f..e7b64e5 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java +++ b/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java @@ -1,5 +1,6 @@ package meerkat.crypto; +import com.google.protobuf.ByteString; import com.google.protobuf.Message; import java.io.IOException; @@ -82,6 +83,10 @@ public interface DigitalSignature { throws IOException, CertificateException, UnrecoverableKeyException; + /** + * @return the signer ID if it exists; null otherwise. + */ + public ByteString getSignerID(); /** * Clear the signing key (will require authentication to use again). diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java index 615b06e..887b8e8 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java @@ -300,6 +300,11 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur throw new UnrecoverableKeyException("Didn't find valid private key entry in keystore!"); } + @Override + public ByteString getSignerID() { + return loadedSigningKeyId; + } + public void clearSigningKey() { try { // TODO: Check if this really clears the key from memory diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java index 1e075bf..f9936c7 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java @@ -17,10 +17,10 @@ import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.BigIntegers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import qilin.primitives.concrete.ECElGamal; -import qilin.primitives.concrete.ECGroup; -import qilin.util.PRGRandom; -import qilin.util.Pair; +import org.factcenter.qilin.primitives.concrete.ECElGamal; +import org.factcenter.qilin.primitives.concrete.ECGroup; +import org.factcenter.qilin.util.PRGRandom; +import org.factcenter.qilin.util.Pair; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java new file mode 100644 index 0000000..77a6663 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java @@ -0,0 +1,49 @@ +package meerkat.util; + +import meerkat.protobuf.BulletinBoardAPI; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Crypto.*; + +import java.util.Comparator; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + * This class implements a comparison between BulletinBoardMessage instances that disregards: + * 1. The entry number (since this can be different between database instances) + * 2. The order of the signatures + */ +public class BulletinBoardMessageComparator implements Comparator { + + /** + * Compare the messages + * @param msg1 + * @param msg2 + * @return 0 if the messages are equivalent (see above) and -1 otherwise. + */ + @Override + public int compare(BulletinBoardMessage msg1, BulletinBoardMessage msg2) { + + List msg1Sigs = msg1.getSigList(); + List msg2Sigs = msg2.getSigList(); + + // Compare unsigned message + if (!msg1.getMsg().equals(msg2.getMsg())){ + return -1; + } + + // Compare signatures + + if (msg1Sigs.size() != msg2Sigs.size()){ + return -1; + } + + for (Signature sig : msg1Sigs){ + if (!msg2Sigs.contains(sig)) { + return -1; + } + } + + return 0; + } +} diff --git a/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto b/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto index 28fc948..0fe35f8 100644 --- a/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto +++ b/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto @@ -10,6 +10,9 @@ message BoolMsg { bool value = 1; } +message IntMsg { + int32 value = 1; +} message MessageID { // The ID of a message for unique retrieval. @@ -49,6 +52,10 @@ enum FilterType { MAX_ENTRY = 2; // Find all entries in database up to specified entry number (chronological) SIGNER_ID = 3; // Find all entries in database that correspond to specific signature (signer) TAG = 4; // Find all entries in database that have a specific tag + + // NOTE: The MAX_MESSAGES filter must remain the last filter type + // This is because the condition it specifies in an SQL statement must come last in the statement + // Keeping it last here allows for easily sorting the filters and keeping the code general MAX_MESSAGES = 5; // Return at most some specified number of messages } diff --git a/meerkat-common/src/main/proto/meerkat/voting.proto b/meerkat-common/src/main/proto/meerkat/voting.proto index 77d8051..7766f1e 100644 --- a/meerkat-common/src/main/proto/meerkat/voting.proto +++ b/meerkat-common/src/main/proto/meerkat/voting.proto @@ -53,25 +53,28 @@ message BallotAnswerTranslationTable { } - -enum UIDataType { +// Type of the element data to be presented by UI +enum UIElementDataType { TEXT = 0; IMAGE = 1; VOICE = 2; } +// Type of question enum QuestionType { MULTIPLE_CHOICE = 0; MULTIPLE_SELECTION = 1; ORDER = 2; } +// An element to be presented by UI message UIElement { - UIDataType type = 1; + UIElementDataType type = 1; bytes data = 2; } -message BllotQuestionNew { +// a new data structure for BallotQuestion. Need to delete the old one +message BallotQuestionNew { bool is_mandatory = 1; UIElement question = 2; UIElement description = 3; @@ -79,6 +82,16 @@ message BllotQuestionNew { } +// Data required in order to access the Bulletin Board Servers +message BulletinBoardClientParams { + + // Addresses of all Bulletin Board Servers + repeated string bulletinBoardAddress = 1; + + // Threshold fraction of successful servers posts before a post task is considered complete + float minRedundancy = 2; +} + message ElectionParams { // TODO: different sets of keys for different roles? repeated SignatureVerificationKey trusteeVerificationKeys = 1; @@ -97,9 +110,11 @@ message ElectionParams { uint32 mixerThreshold = 5; // Candidate list (or other question format) - repeated BallotQuestion questions = 6; + repeated BallotQuestionNew questions = 6; // Translation table between answers and plaintext encoding - BallotAnswerTranslationTable answerTranslationTable = 7; + //BallotAnswerTranslationTable answerTranslationTable = 7; + // Data required in order to access the Bulletin Board Servers + BulletinBoardClientParams bulletinBoardClientParams = 8; } diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalEncryptionTest.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalEncryptionTest.java index bb52df9..81375e0 100644 --- a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalEncryptionTest.java +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalEncryptionTest.java @@ -8,9 +8,9 @@ import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import qilin.primitives.concrete.ECElGamal; -import qilin.primitives.concrete.ECGroup; -import qilin.util.Pair; +import org.factcenter.qilin.primitives.concrete.ECElGamal; +import org.factcenter.qilin.primitives.concrete.ECGroup; +import org.factcenter.qilin.util.Pair; import java.math.BigInteger; import java.util.Random; diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java index 44efe69..66e1647 100644 --- a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java @@ -11,10 +11,10 @@ import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import qilin.primitives.concrete.ECElGamal; -import qilin.primitives.concrete.ECGroup; -import qilin.primitives.generic.ElGamal; -import qilin.util.Pair; +import org.factcenter.qilin.primitives.concrete.ECElGamal; +import org.factcenter.qilin.primitives.concrete.ECGroup; +import org.factcenter.qilin.primitives.generic.ElGamal; +import org.factcenter.qilin.util.Pair; import java.io.ByteArrayInputStream; import java.security.KeyFactory; diff --git a/restful-api-common/src/main/java/meerkat/rest/Constants.java b/restful-api-common/src/main/java/meerkat/rest/Constants.java index 2c04248..73ed7d1 100644 --- a/restful-api-common/src/main/java/meerkat/rest/Constants.java +++ b/restful-api-common/src/main/java/meerkat/rest/Constants.java @@ -5,4 +5,8 @@ package meerkat.rest; */ public interface Constants { public static final String MEDIATYPE_PROTOBUF = "application/x-protobuf"; + + public static final String BULLETIN_BOARD_SERVER_PATH = "/bbserver"; + public static final String READ_MESSAGES_PATH = "/readmessages"; + public static final String POST_MESSAGE_PATH = "/postmessage"; } diff --git a/settings.gradle b/settings.gradle index e4ef054..99f4c5e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,3 +3,5 @@ include 'voting-booth' include 'bulletin-board-server' include 'polling-station' include 'restful-api-common' +include 'bulletin-board-client' + diff --git a/voting-booth/src/main/java/meerkat/voting/VotingBoothToy.java b/voting-booth/src/main/java/meerkat/voting/VotingBoothToy.java index 396ebc0..bfd43a6 100644 --- a/voting-booth/src/main/java/meerkat/voting/VotingBoothToy.java +++ b/voting-booth/src/main/java/meerkat/voting/VotingBoothToy.java @@ -19,10 +19,9 @@ public class VotingBoothToy implements VotingBooth, Runnable { //private ElectionParams m_electionParams; private EncryptionPublicKey m_ballotEncryptionKey; - private List l_questions; - private BallotQuestion a_questions[]; - private BallotAnswer a_answers[]; - private BallotAnswerTranslationTable m_answerTranslationTable; + //private List l_questions; + //private BallotQuestionNew a_questions[]; + //private BallotAnswer a_answers[]; private ArrayBlockingQueue a_queue; static private int m_queueSize = 5; @@ -116,10 +115,8 @@ public class VotingBoothToy implements VotingBooth, Runnable { public void init(ElectionParams globalParams, BoothParams boothParams) { System.err.println ("debug VB: init."); this.m_ballotEncryptionKey = globalParams.getBallotEncryptionKey(); - this.l_questions = globalParams.getQuestionsList(); + //this.l_questions = globalParams.getQuestionsList(); - this.m_answerTranslationTable = globalParams.getAnswerTranslationTable(); - List l_signatureKeys = boothParams.getPscVerificationKeysList(); a_signatureKeys = new SignatureVerificationKey[l_signatureKeys.size()]; int i = 0; diff --git a/voting-booth/src/main/java/meerkat/voting/VotingBoothToyConsoleUI.java b/voting-booth/src/main/java/meerkat/voting/VotingBoothToyConsoleUI.java index 4c87bfa..c3e0a1c 100644 --- a/voting-booth/src/main/java/meerkat/voting/VotingBoothToyConsoleUI.java +++ b/voting-booth/src/main/java/meerkat/voting/VotingBoothToyConsoleUI.java @@ -30,14 +30,12 @@ public class VotingBoothToyConsoleUI implements UI, Runnable { //private SharedEncryptedBallotMessage m_sharedEncrypted; private ArrayBlockingQueue a_queue; static private int m_queueSize = 5; - private BallotQuestion a_questions[]; - private BallotQuestionNew a_questionsNew[]; + private BallotQuestionNew a_questions[]; private int m_serialNumber; private PlaintextBallot m_plaintextBallot; private EncryptedBallot m_encryptedBallot; private BallotSecrets m_ballotSecrets; private int m_waitForControllerMillisecTimeout = 10; - private BallotAnswerTranslationTable m_answerTranslationTable; public VotingBoothToyConsoleUI () { @@ -47,14 +45,12 @@ public class VotingBoothToyConsoleUI implements UI, Runnable { public VotingBoothToyConsoleUI(ElectionParams globalParams) { m_serialNumber = 0; - this.m_answerTranslationTable = globalParams.getAnswerTranslationTable(); - - List l_questions = globalParams.getQuestionsList(); - a_questions = new BallotQuestion[l_questions.size()]; + List l_questions = globalParams.getQuestionsList(); + a_questions = new BallotQuestionNew[l_questions.size()]; m_in = new BufferedReader(new InputStreamReader(System.in)); int i = 0; - for (BallotQuestion q: l_questions) { + for (BallotQuestionNew q: l_questions) { a_questions[i] = q; ++i; } @@ -142,7 +138,7 @@ public class VotingBoothToyConsoleUI implements UI, Runnable { private void eraseEncryption () { - //TODO: should we clean memory stronger? + //TODO: should we clean memory 'stronger'? if (m_encryptedBallot != null) { m_encryptedBallot = null; } @@ -152,7 +148,7 @@ public class VotingBoothToyConsoleUI implements UI, Runnable { } private void erasePlaintext () { - //TODO: should we clean memory stronger? + //TODO: should we clean memory 'stronger'? if (m_plaintextBallot != null) { m_plaintextBallot = null; } @@ -218,7 +214,11 @@ public class VotingBoothToyConsoleUI implements UI, Runnable { int index = 0; while (index < a_questions.length) { - BallotQuestion q = a_questions[index]; + BallotQuestionNew q = a_questions[index]; + if (q.getIsMandatory()) { + throw new UnsupportedOperationException("question " + index + " is marked as mandatory"); + } + printQuestion(index, q); System.out.println("UI screen: Enter your answer. You can also type 'back' or 'cancel'"); String s = readInputLine(); @@ -233,6 +233,11 @@ public class VotingBoothToyConsoleUI implements UI, Runnable { continue; } + if (s.equals("skip")) { + ++index; + continue; + } + BallotAnswer answer = translateStringAnswerToProtoBufMessageAnswer (s); ptbb.setAnswers(index, answer); } @@ -255,24 +260,42 @@ public class VotingBoothToyConsoleUI implements UI, Runnable { return s; } - private String getHexData (ByteString data) { - String s = ""; - for (Byte b : data) { - s += "0123456789ABCDEF".charAt((int)b / 16); - s += "0123456789ABCDEF".charAt((int)b % 16); + + private void printQuestion (int i, BallotQuestionNew q) { + + boolean isText = true; + + if (q.getQuestion().getType() != UIElementDataType.TEXT + || q.getDescription().getType() != UIElementDataType.TEXT) { + isText = false; } - return s; - } - - private void printHexData (ByteString data) { - String s = getHexData(data); - System.out.println(s); - } - - private void printQuestion (int i, BallotQuestion q) { + + for (UIElement answer : q.getAnswersList()) { + if (answer.getType() != UIElementDataType.TEXT) { + isText = false; + } + } + + if (!isText) { + System.err.println("debug: an element in question " + i + " is not of TEXT type"); + throw new UnsupportedOperationException(); + } + + System.out.println("UI screen: question number " + i); - System.out.println(q.getData()); - printHexData (q.getData()); + + System.out.println("Question text: " + bytesToString(q.getQuestion().getData())); + System.out.println("Description: " + bytesToString(q.getDescription().getData())); + int answerIndex = 0; + for (UIElement answer : q.getAnswersList()) { + ++answerIndex; + System.out.println("Answer " + answerIndex + ": " + bytesToString(answer.getData())); + } + + } + + private static String bytesToString (ByteString data) { + return data.toStringUtf8(); } private BallotAnswer translateStringAnswerToProtoBufMessageAnswer (String s) { @@ -293,6 +316,7 @@ public class VotingBoothToyConsoleUI implements UI, Runnable { a_queue.add (VBMessage.newTick()); } + private void sendBallotToControllerForEncryptionAndWaitForResponse () { class TickerTask extends TimerTask { diff --git a/voting-booth/src/main/java/meerkat/voting/VotingBoothToyDemoRun.java b/voting-booth/src/main/java/meerkat/voting/VotingBoothToyDemoRun.java index 6e023bd..d92dc06 100644 --- a/voting-booth/src/main/java/meerkat/voting/VotingBoothToyDemoRun.java +++ b/voting-booth/src/main/java/meerkat/voting/VotingBoothToyDemoRun.java @@ -4,8 +4,7 @@ import com.google.protobuf.ByteString; import meerkat.protobuf.Crypto.SignatureType; import meerkat.protobuf.Crypto.SignatureVerificationKey; -import meerkat.protobuf.Voting.BoothParams; -import meerkat.protobuf.Voting.ElectionParams; +import meerkat.protobuf.Voting.*; public class VotingBoothToyDemoRun { @@ -19,6 +18,21 @@ public class VotingBoothToyDemoRun { vbController.registerUI (ui); ui.registerVBController(vbController); + + BoothParams boothParams = generateDemoBoothParams(); + ElectionParams electionParams = generateDemoElectionParams(); + + vbController.init(electionParams, boothParams); + + Thread controllerThread = new Thread(new VotingBoothToy ()); + Thread uiThread = new Thread(ui); + + controllerThread.start(); + uiThread.start(); + } + + + public static BoothParams generateDemoBoothParams () { BoothParams.Builder bpb = BoothParams.newBuilder(); SignatureType signatureType = SignatureType.ECDSA; for (int i = 0; i < N_SIGNATURE_VERIFICATION_KEYS; ++i) { @@ -28,18 +42,59 @@ public class VotingBoothToyDemoRun { .build(); bpb.addPscVerificationKeys(i, verifiationKey); } - BoothParams boothParams = bpb.build(); - - ElectionParams electionParams = new ElectionParams(); - - vbController.init(electionParams, boothParams); - - Thread controllerThread = new Thread(new VotingBoothToy ()); - Thread uiThread = new Thread(ui); + return bpb.build(); - controllerThread.start(); - uiThread.start(); + } + + public static BallotQuestionNew generateBallotQuestion(String questionStr, String descriptionStr, String[] answers) { + UIElement question = UIElement.newBuilder() + .setType(UIElementDataType.TEXT) + .setData(stringToBytes(questionStr)) + .build(); + UIElement description = UIElement.newBuilder() + .setType(UIElementDataType.TEXT) + .setData(stringToBytes(descriptionStr)) + .build(); + + BallotQuestionNew.Builder bqb = BallotQuestionNew.newBuilder(); + bqb.setIsMandatory(false); + bqb.setQuestion(question); + bqb.setDescription(description); + for (String answerStr : answers) { + UIElement answer = UIElement.newBuilder() + .setType(UIElementDataType.TEXT) + .setData(stringToBytes(answerStr)) + .build(); + bqb.addAnswers(answer); + } + + return bqb.build(); + } + + + + public static ByteString stringToBytes (String s) { + return ByteString.copyFromUtf8(s); + } + + public static ElectionParams generateDemoElectionParams () { + String[] answers1 = {"Blue", "Red", "Green", "Purple"}; + BallotQuestionNew question1 = generateBallotQuestion("What is your favorite color?", "Pick one answer", answers1); + + String[] answers2 = {"Miranda Kerr", "Doutzen Kroes", "Moran Atias", "Roslana Rodina", "Adriana Lima"}; + BallotQuestionNew question2 = generateBallotQuestion("Which model do you like", "Mark as many as you want", answers2); + + String[] answers3 = {"Clint Eastwood", "Ninja", "Sonic", "Tai-chi", "Diablo"}; + BallotQuestionNew question3 = generateBallotQuestion("Good name for a cat", "Pick the best one", answers3); + + ElectionParams.Builder epb = ElectionParams.newBuilder(); + epb.setTrusteeSignatureThreshold(5); + epb.setQuestions(0, question1); + epb.setQuestions(1, question2); + epb.setQuestions(2, question3); + + return epb.build(); } }