Trying to run demo.
But couldn't make Gradle compile it. Committing so Arbel can also have a look at it.vbdev
commit
9d19d82477
|
@ -1,5 +1,4 @@
|
|||
|
||||
|
||||
subprojects { proj ->
|
||||
proj.afterEvaluate {
|
||||
// Used to generate initial maven-dir layout
|
||||
|
|
|
@ -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<String> 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<String> 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<String> newServerAdresses) {
|
||||
this.serverAddresses = newServerAdresses;
|
||||
}
|
||||
|
||||
public List<String> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<BulletinClientJobResult> {
|
||||
|
||||
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<Client> clientLocal =
|
||||
new ThreadLocal<Client> () {
|
||||
@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<Digest> digestLocal =
|
||||
new ThreadLocal<Digest> () {
|
||||
@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<String> serverAddresses = new LinkedList<String>(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<String> 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");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String> 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<BulletinBoardMessage> 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));
|
||||
// }
|
||||
}
|
|
@ -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<String> 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<Float> 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<List<BulletinBoardMessage>> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<BulletinClientJobResult> {
|
||||
|
||||
protected ListeningExecutorService listeningExecutor;
|
||||
|
||||
ClientFutureCallback(ListeningExecutorService listeningExecutor) {
|
||||
this.listeningExecutor = listeningExecutor;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Float> callback;
|
||||
|
||||
public GetRedundancyFutureCallback(ListeningExecutorService listeningExecutor,
|
||||
BulletinBoardClient.ClientCallback<Float> 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<List<BulletinBoardAPI.BulletinBoardMessage>> callback;
|
||||
|
||||
public ReadMessagesFutureCallback(ListeningExecutorService listeningExecutor,
|
||||
BulletinBoardClient.ClientCallback<List<BulletinBoardAPI.BulletinBoardMessage>> 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);
|
||||
}
|
||||
}
|
|
@ -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<Throwable> thrown;
|
||||
|
||||
private class PostCallback implements ClientCallback<Object>{
|
||||
|
||||
@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<Float>{
|
||||
|
||||
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<List<BulletinBoardMessage>>{
|
||||
|
||||
private List<BulletinBoardMessage> expectedMsgList;
|
||||
|
||||
public ReadCallback(List<BulletinBoardMessage> expectedMsgList) {
|
||||
this.expectedMsgList = expectedMsgList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallback(List<BulletinBoardMessage> messages) {
|
||||
|
||||
System.err.println(messages);
|
||||
jobSemaphore.release();
|
||||
|
||||
BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator();
|
||||
|
||||
assertThat(messages.size(), is(expectedMsgList.size()));
|
||||
|
||||
Iterator<BulletinBoardMessage> expectedMessageIterator = expectedMsgList.iterator();
|
||||
Iterator<BulletinBoardMessage> 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<String> testDB = new LinkedList<String>();
|
||||
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<BulletinBoardMessage> msgList;
|
||||
|
||||
MessageID messageID;
|
||||
|
||||
Comparator<BulletinBoardMessage> 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<BulletinBoardMessage>();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String> 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<String> 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<MessageFilter> {
|
||||
|
||||
@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<List<SignatureVerificationKey>> 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<Long> 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<BR>";
|
||||
|
||||
@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<MessageFilter> filters = new ArrayList<MessageFilter>(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<BR>";
|
||||
}
|
||||
|
||||
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<BR>";
|
||||
}
|
||||
} catch(SQLException e){
|
||||
s += "Error reading from DB";
|
||||
|
||||
}
|
||||
|
||||
return s;
|
||||
|
||||
// Run query
|
||||
|
||||
List<BulletinBoardMessage.Builder> 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<Signature> 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() {}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<String> getSchemaCreationCommands() {
|
||||
List<String> list = new LinkedList<String>();
|
||||
|
||||
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<String> getSchemaDeletionCommands() {
|
||||
List<String> list = new LinkedList<String>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String> getSchemaCreationCommands() {
|
||||
List<String> list = new LinkedList<String>();
|
||||
|
||||
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<String> getSchemaDeletionCommands() {
|
||||
List<String> list = new LinkedList<String>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<MessageFilter> 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String> getSchemaCreationCommands() {
|
||||
List<String> list = new LinkedList<String>();
|
||||
|
||||
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<String> getSchemaDeletionCommands() {
|
||||
List<String> list = new LinkedList<String>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<Long> {
|
||||
|
||||
@Override
|
||||
public Long mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
return rs.getLong(1);
|
||||
}
|
||||
}
|
|
@ -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<BulletinBoardMessage.Builder> {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Signature> {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,4 +14,25 @@
|
|||
<servlet-name>Jersey Hello World</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
<context-param>
|
||||
<param-name>dbAddress</param-name>
|
||||
<param-value>localhost</param-value></context-param>
|
||||
<context-param>
|
||||
<param-name>dbPort</param-name>
|
||||
<param-value>3306</param-value></context-param>
|
||||
<context-param>
|
||||
<param-name>dbName</param-name>
|
||||
<param-value>meerkat</param-value></context-param>
|
||||
<context-param>
|
||||
<param-name>username</param-name>
|
||||
<param-value>arbel</param-value></context-param>
|
||||
<context-param>
|
||||
<param-name>password</param-name>
|
||||
<param-value>mypass</param-value></context-param>
|
||||
<context-param>
|
||||
<param-name>dbType</param-name>
|
||||
<param-value>SQLite</param-value></context-param>
|
||||
<listener>
|
||||
<listener-class>meerkat.bulletinboard.webapp.BulletinBoardWebApp</listener-class>
|
||||
</listener>
|
||||
</web-app>
|
||||
|
|
|
@ -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);
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<BulletinBoardMessage> 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<BulletinBoardMessage> 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<BulletinBoardMessage> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String> 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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.+'
|
||||
|
||||
|
|
|
@ -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<BulletinBoardMessage> 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);
|
||||
|
||||
}
|
|
@ -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<T> {
|
||||
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<Float> 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<List<BulletinBoardMessage>> callback);
|
||||
|
||||
/**
|
||||
* Closes all connections, if any.
|
||||
* This is done in a synchronous (blocking) way.
|
||||
*/
|
||||
void close();
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<BulletinBoardMessage> {
|
||||
|
||||
/**
|
||||
* 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<Signature> msg1Sigs = msg1.getSigList();
|
||||
List<Signature> 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -3,3 +3,5 @@ include 'voting-booth'
|
|||
include 'bulletin-board-server'
|
||||
include 'polling-station'
|
||||
include 'restful-api-common'
|
||||
include 'bulletin-board-client'
|
||||
|
||||
|
|
|
@ -19,10 +19,9 @@ public class VotingBoothToy implements VotingBooth, Runnable {
|
|||
|
||||
//private ElectionParams m_electionParams;
|
||||
private EncryptionPublicKey m_ballotEncryptionKey;
|
||||
private List<BallotQuestion> l_questions;
|
||||
private BallotQuestion a_questions[];
|
||||
private BallotAnswer a_answers[];
|
||||
private BallotAnswerTranslationTable m_answerTranslationTable;
|
||||
//private List<BallotQuestionNew> l_questions;
|
||||
//private BallotQuestionNew a_questions[];
|
||||
//private BallotAnswer a_answers[];
|
||||
|
||||
private ArrayBlockingQueue<VBMessage> 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<SignatureVerificationKey> l_signatureKeys = boothParams.getPscVerificationKeysList();
|
||||
a_signatureKeys = new SignatureVerificationKey[l_signatureKeys.size()];
|
||||
int i = 0;
|
||||
|
|
|
@ -30,14 +30,12 @@ public class VotingBoothToyConsoleUI implements UI, Runnable {
|
|||
//private SharedEncryptedBallotMessage m_sharedEncrypted;
|
||||
private ArrayBlockingQueue<VBMessage> 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<BallotQuestion> l_questions = globalParams.getQuestionsList();
|
||||
a_questions = new BallotQuestion[l_questions.size()];
|
||||
List<BallotQuestionNew> 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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue