mereging with master for getting the new timestamp uasge
commit
5c4b5a2150
|
@ -0,0 +1,25 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BatchData;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 17-Jan-16.
|
||||||
|
* Used to store the complete data required for sending a batch data list inside a single object
|
||||||
|
*/
|
||||||
|
public class BatchDataContainer {
|
||||||
|
|
||||||
|
public final byte[] signerId;
|
||||||
|
public final int batchId;
|
||||||
|
public final List<BatchData> batchDataList;
|
||||||
|
public final int startPosition;
|
||||||
|
|
||||||
|
public BatchDataContainer(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition) {
|
||||||
|
this.signerId = signerId;
|
||||||
|
this.batchId = batchId;
|
||||||
|
this.batchDataList = batchDataList;
|
||||||
|
this.startPosition = startPosition;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,82 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,217 +1,38 @@
|
||||||
package meerkat.bulletinboard;
|
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.
|
* Created by Arbel Deutsch Peled on 09-Dec-15.
|
||||||
*
|
*
|
||||||
* This class implements the actual communication with the Bulletin Board Servers.
|
* This class handles bulletin client work.
|
||||||
* It is meant to be used in a multi-threaded environment.
|
* It is meant to be used in a multi-threaded environment.
|
||||||
*/
|
*/
|
||||||
//TODO: Maybe make this abstract and inherit from it.
|
public abstract class BulletinClientWorker<IN> {
|
||||||
public class BulletinClientWorker implements Callable<BulletinClientJobResult> {
|
|
||||||
|
|
||||||
private final BulletinClientJob job; // The requested job to be handled
|
protected final IN payload; // Payload of the job
|
||||||
|
|
||||||
public BulletinClientWorker(BulletinClientJob job){
|
private int maxRetry; // Number of retries for this job; set to -1 for infinite retries
|
||||||
this.job = job;
|
|
||||||
|
public BulletinClientWorker(IN payload, int maxRetry) {
|
||||||
|
this.payload = payload;
|
||||||
|
this.maxRetry = maxRetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This resource enabled creation of a single Client per thread.
|
public IN getPayload() {
|
||||||
private static final ThreadLocal<Client> clientLocal =
|
return payload;
|
||||||
new ThreadLocal<Client> () {
|
}
|
||||||
@Override protected Client initialValue() {
|
|
||||||
Client client;
|
|
||||||
client = ClientBuilder.newClient();
|
|
||||||
client.register(ProtobufMessageBodyReader.class);
|
|
||||||
client.register(ProtobufMessageBodyWriter.class);
|
|
||||||
|
|
||||||
return client;
|
public int getMaxRetry() {
|
||||||
}
|
return maxRetry;
|
||||||
};
|
}
|
||||||
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
|
public void decMaxRetry(){
|
||||||
|
if (maxRetry > 0) {
|
||||||
|
maxRetry--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRetry(){
|
||||||
|
return (maxRetry != 0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 09-Dec-15.
|
||||||
|
*
|
||||||
|
* This is a general class for handling multi-server work
|
||||||
|
* It utilizes Single Server Clients to perform the actual per-server work
|
||||||
|
*/
|
||||||
|
public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN> implements Runnable, FutureCallback<OUT>{
|
||||||
|
|
||||||
|
private final List<SingleServerBulletinBoardClient> clients;
|
||||||
|
|
||||||
|
protected AtomicInteger minServers; // The minimal number of servers the job must be successful on for the job to be completed
|
||||||
|
|
||||||
|
protected AtomicInteger maxFailedServers; // The maximal number of allowed server failures
|
||||||
|
|
||||||
|
private AtomicBoolean returnedResult;
|
||||||
|
|
||||||
|
private final FutureCallback<OUT> futureCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param clients contains a list of Single Server clients to handle requests
|
||||||
|
* @param shuffleClients is a boolean stating whether or not it is needed to shuffle the clients
|
||||||
|
* @param minServers is the minimal amount of servers needed in order to successfully complete the job
|
||||||
|
* @param payload is the payload for the job
|
||||||
|
* @param maxRetry is the maximal per-server retry count
|
||||||
|
* @param futureCallback contains the callback methods used to report the result back to the client
|
||||||
|
*/
|
||||||
|
public MultiServerWorker(List<SingleServerBulletinBoardClient> clients, boolean shuffleClients,
|
||||||
|
int minServers, IN payload, int maxRetry,
|
||||||
|
FutureCallback<OUT> futureCallback) {
|
||||||
|
|
||||||
|
super(payload,maxRetry);
|
||||||
|
|
||||||
|
this.clients = clients;
|
||||||
|
if (shuffleClients){
|
||||||
|
Collections.shuffle(clients);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minServers = new AtomicInteger(minServers);
|
||||||
|
maxFailedServers = new AtomicInteger(clients.size() - minServers);
|
||||||
|
this.futureCallback = futureCallback;
|
||||||
|
|
||||||
|
returnedResult = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor overload without client shuffling
|
||||||
|
*/
|
||||||
|
public MultiServerWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, IN payload, int maxRetry,
|
||||||
|
FutureCallback<OUT> futureCallback) {
|
||||||
|
|
||||||
|
this(clients, false, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to report a successful operation to the client
|
||||||
|
* Only reports once to the client
|
||||||
|
* @param result is the result
|
||||||
|
*/
|
||||||
|
protected void succeed(OUT result){
|
||||||
|
if (returnedResult.compareAndSet(false, true)) {
|
||||||
|
futureCallback.onSuccess(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to report a failed operation to the client
|
||||||
|
* Only reports once to the client
|
||||||
|
* @param t contains the error/exception that occurred
|
||||||
|
*/
|
||||||
|
protected void fail(Throwable t){
|
||||||
|
if (returnedResult.compareAndSet(false, true)) {
|
||||||
|
futureCallback.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by implementations to get a Single Server Client iterator
|
||||||
|
* @return the requested iterator
|
||||||
|
*/
|
||||||
|
protected Iterator<SingleServerBulletinBoardClient> getClientIterator() {
|
||||||
|
return clients.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getClientNumber() {
|
||||||
|
return clients.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,8 +5,7 @@ import meerkat.comm.CommunicationException;
|
||||||
import meerkat.crypto.Digest;
|
import meerkat.crypto.Digest;
|
||||||
import meerkat.crypto.concrete.SHA256Digest;
|
import meerkat.crypto.concrete.SHA256Digest;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.*;
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
import meerkat.protobuf.Voting;
|
import meerkat.protobuf.Voting.*;
|
||||||
import meerkat.protobuf.Voting.BulletinBoardClientParams;
|
|
||||||
import meerkat.rest.*;
|
import meerkat.rest.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -17,25 +16,28 @@ import javax.ws.rs.client.Entity;
|
||||||
import javax.ws.rs.client.WebTarget;
|
import javax.ws.rs.client.WebTarget;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
||||||
|
* Implements BulletinBoardClient interface in a simple, straightforward manner
|
||||||
*/
|
*/
|
||||||
public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
public class SimpleBulletinBoardClient implements BulletinBoardClient{
|
||||||
|
|
||||||
private List<String> meerkatDBs;
|
protected List<String> meerkatDBs;
|
||||||
|
|
||||||
private Client client;
|
protected Client client;
|
||||||
|
|
||||||
private Digest digest;
|
protected Digest digest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores database locations and initializes the web Client
|
* Stores database locations and initializes the web Client
|
||||||
* @param clientParams contains the data needed to access the DBs
|
* @param clientParams contains the data needed to access the DBs
|
||||||
*/
|
*/
|
||||||
// @Override
|
@Override
|
||||||
public void init(Voting.BulletinBoardClientParams clientParams) {
|
public void init(BulletinBoardClientParams clientParams) {
|
||||||
|
|
||||||
meerkatDBs = clientParams.getBulletinBoardAddressList();
|
this.meerkatDBs = clientParams.getBulletinBoardAddressList();
|
||||||
|
|
||||||
client = ClientBuilder.newClient();
|
client = ClientBuilder.newClient();
|
||||||
client.register(ProtobufMessageBodyReader.class);
|
client.register(ProtobufMessageBodyReader.class);
|
||||||
|
@ -52,7 +54,7 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
* @return the message ID for later retrieval
|
* @return the message ID for later retrieval
|
||||||
* @throws CommunicationException
|
* @throws CommunicationException
|
||||||
*/
|
*/
|
||||||
// @Override
|
@Override
|
||||||
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
||||||
|
|
||||||
WebTarget webTarget;
|
WebTarget webTarget;
|
||||||
|
@ -61,7 +63,7 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
// Post message to all databases
|
// Post message to all databases
|
||||||
try {
|
try {
|
||||||
for (String db : meerkatDBs) {
|
for (String db : meerkatDBs) {
|
||||||
webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.POST_MESSAGE_PATH);
|
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH);
|
||||||
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF));
|
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
// Only consider valid responses
|
// Only consider valid responses
|
||||||
|
@ -88,7 +90,7 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
* @param id is the requested message ID
|
* @param id is the requested message ID
|
||||||
* @return the number of DBs in which retrieval was successful
|
* @return the number of DBs in which retrieval was successful
|
||||||
*/
|
*/
|
||||||
// @Override
|
@Override
|
||||||
public float getRedundancy(MessageID id) {
|
public float getRedundancy(MessageID id) {
|
||||||
WebTarget webTarget;
|
WebTarget webTarget;
|
||||||
Response response;
|
Response response;
|
||||||
|
@ -104,7 +106,7 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
|
|
||||||
for (String db : meerkatDBs) {
|
for (String db : meerkatDBs) {
|
||||||
try {
|
try {
|
||||||
webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH);
|
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
|
||||||
|
|
||||||
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
|
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
|
@ -123,9 +125,9 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
* If at the operation is successful for some DB: return the results and stop iterating
|
* 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)
|
* If no operation is successful: return null (NOT blank list)
|
||||||
* @param filterList return only messages that match the filters (null means no filtering).
|
* @param filterList return only messages that match the filters (null means no filtering).
|
||||||
* @return
|
* @return the list of Bulletin Board messages that are returned from a server
|
||||||
*/
|
*/
|
||||||
// @Override
|
@Override
|
||||||
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) {
|
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) {
|
||||||
WebTarget webTarget;
|
WebTarget webTarget;
|
||||||
Response response;
|
Response response;
|
||||||
|
@ -138,7 +140,7 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
|
|
||||||
for (String db : meerkatDBs) {
|
for (String db : meerkatDBs) {
|
||||||
try {
|
try {
|
||||||
webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH);
|
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
|
||||||
|
|
||||||
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
|
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
|
@ -154,8 +156,8 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
public void close() {
|
||||||
// public void registerNewMessageCallback(MessageCallback callback, MessageFilterList filterList) {
|
client.close();
|
||||||
// callback.handleNewMessage(readMessages(filterList));
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,605 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import meerkat.bulletinboard.workers.singleserver.*;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import meerkat.protobuf.Voting.BulletinBoardClientParams;
|
||||||
|
import meerkat.util.BulletinBoardUtils;
|
||||||
|
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 28-Dec-15.
|
||||||
|
*
|
||||||
|
* This class implements the asynchronous Bulletin Board Client interface
|
||||||
|
* It only handles a single Bulletin Board Server
|
||||||
|
* If the list of servers contains more than one server: the server actually used is the first one
|
||||||
|
* The class further implements a delayed access to the server after a communication error occurs
|
||||||
|
*/
|
||||||
|
public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient implements AsyncBulletinBoardClient {
|
||||||
|
|
||||||
|
private final int MAX_RETRIES = 11;
|
||||||
|
|
||||||
|
private ListeningScheduledExecutorService executorService;
|
||||||
|
|
||||||
|
protected BatchDigest batchDigest;
|
||||||
|
|
||||||
|
private long lastServerErrorTime;
|
||||||
|
|
||||||
|
private final long failDelayInMilliseconds;
|
||||||
|
|
||||||
|
private final long subscriptionIntervalInMilliseconds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the client that a job has failed
|
||||||
|
* This makes new scheduled jobs be scheduled for a later time (after the given delay)
|
||||||
|
*/
|
||||||
|
protected void fail() {
|
||||||
|
|
||||||
|
// Update last fail time
|
||||||
|
lastServerErrorTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method adds a worker to the scheduled queue of the threadpool
|
||||||
|
* If the server is in an accessible state: the job is submitted for immediate handling
|
||||||
|
* If the server is not accessible: the job is scheduled for a later time
|
||||||
|
* @param worker is the worker that should be scheduled for work
|
||||||
|
* @param callback is the class containing callbacks for handling job completion/failure
|
||||||
|
*/
|
||||||
|
protected void scheduleWorker(SingleServerWorker worker, FutureCallback callback){
|
||||||
|
|
||||||
|
long timeSinceLastServerError = System.currentTimeMillis() - lastServerErrorTime;
|
||||||
|
|
||||||
|
if (timeSinceLastServerError >= failDelayInMilliseconds) {
|
||||||
|
|
||||||
|
// Schedule for immediate processing
|
||||||
|
Futures.addCallback(executorService.submit(worker), callback);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Schedule for processing immediately following delay expiry
|
||||||
|
Futures.addCallback(executorService.schedule(
|
||||||
|
worker,
|
||||||
|
failDelayInMilliseconds - timeSinceLastServerError,
|
||||||
|
TimeUnit.MILLISECONDS),
|
||||||
|
callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class for handling simple operation results and retrying if needed
|
||||||
|
*/
|
||||||
|
class RetryCallback<T> implements FutureCallback<T> {
|
||||||
|
|
||||||
|
private final SingleServerWorker worker;
|
||||||
|
private final FutureCallback<T> futureCallback;
|
||||||
|
|
||||||
|
public RetryCallback(SingleServerWorker worker, FutureCallback<T> futureCallback) {
|
||||||
|
this.worker = worker;
|
||||||
|
this.futureCallback = futureCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T result) {
|
||||||
|
futureCallback.onSuccess(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Notify client about failure
|
||||||
|
fail();
|
||||||
|
|
||||||
|
// Check if another attempt should be made
|
||||||
|
|
||||||
|
worker.decMaxRetry();
|
||||||
|
|
||||||
|
if (worker.isRetry()) {
|
||||||
|
// Perform another attempt
|
||||||
|
scheduleWorker(worker, this);
|
||||||
|
} else {
|
||||||
|
// No more retries: notify caller about failure
|
||||||
|
futureCallback.onFailure(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback ties together all the per-batch-data callbacks into a single callback
|
||||||
|
* It reports success back to the user only if all of the batch-data were successfully posted
|
||||||
|
* If any batch-data fails to post: this callback reports failure
|
||||||
|
*/
|
||||||
|
class PostBatchDataListCallback implements FutureCallback<Boolean> {
|
||||||
|
|
||||||
|
private final FutureCallback<Boolean> callback;
|
||||||
|
|
||||||
|
private AtomicInteger batchDataRemaining;
|
||||||
|
private AtomicBoolean aggregatedResult;
|
||||||
|
|
||||||
|
public PostBatchDataListCallback(int batchDataLength, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
this.callback = callback;
|
||||||
|
this.batchDataRemaining = new AtomicInteger(batchDataLength);
|
||||||
|
this.aggregatedResult = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
|
||||||
|
if (result){
|
||||||
|
this.aggregatedResult.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batchDataRemaining.decrementAndGet() == 0){
|
||||||
|
callback.onSuccess(this.aggregatedResult.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Notify caller about failure
|
||||||
|
callback.onFailure(t);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback ties together the different parts of a CompleteBatch as they arrive from the server
|
||||||
|
* It assembles a CompleteBatch from the parts and sends it to the user if all parts arrived
|
||||||
|
* If any part fails to arrive: it invokes the onFailure method
|
||||||
|
*/
|
||||||
|
class CompleteBatchReadCallback {
|
||||||
|
|
||||||
|
private final FutureCallback<CompleteBatch> callback;
|
||||||
|
|
||||||
|
private List<BatchData> batchDataList;
|
||||||
|
private BulletinBoardMessage batchMessage;
|
||||||
|
|
||||||
|
private AtomicInteger remainingQueries;
|
||||||
|
private AtomicBoolean failed;
|
||||||
|
|
||||||
|
public CompleteBatchReadCallback(FutureCallback<CompleteBatch> callback) {
|
||||||
|
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
|
remainingQueries = new AtomicInteger(2);
|
||||||
|
failed = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void combineAndReturn() {
|
||||||
|
|
||||||
|
final String[] prefixes = {
|
||||||
|
BulletinBoardConstants.BATCH_ID_TAG_PREFIX,
|
||||||
|
BulletinBoardConstants.BATCH_TAG};
|
||||||
|
|
||||||
|
if (remainingQueries.decrementAndGet() == 0){
|
||||||
|
|
||||||
|
String batchIdStr = BulletinBoardUtils.findTagWithPrefix(batchMessage, BulletinBoardConstants.BATCH_ID_TAG_PREFIX);
|
||||||
|
|
||||||
|
if (batchIdStr == null){
|
||||||
|
callback.onFailure(new CommunicationException("Server returned invalid message with no Batch ID tag"));
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginBatchMessage beginBatchMessage =
|
||||||
|
BeginBatchMessage.newBuilder()
|
||||||
|
.setSignerId(batchMessage.getSig(0).getSignerId())
|
||||||
|
.setBatchId(Integer.parseInt(batchIdStr))
|
||||||
|
.addAllTag(BulletinBoardUtils.removePrefixTags(batchMessage, Arrays.asList(prefixes)))
|
||||||
|
.build();
|
||||||
|
callback.onSuccess(new CompleteBatch(beginBatchMessage, batchDataList, batchMessage.getSig(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fail(Throwable t) {
|
||||||
|
if (failed.compareAndSet(false, true)) {
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a FutureCallback for the Batch Data List that ties to this object
|
||||||
|
*/
|
||||||
|
public FutureCallback<List<BatchData>> asBatchDataListFutureCallback() {
|
||||||
|
return new FutureCallback<List<BatchData>>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BatchData> result) {
|
||||||
|
batchDataList = result;
|
||||||
|
|
||||||
|
combineAndReturn();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
fail(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a FutureCallback for the Bulletin Board Message that ties to this object
|
||||||
|
*/
|
||||||
|
public FutureCallback<List<BulletinBoardMessage>> asBulletinBoardMessageListFutureCallback() {
|
||||||
|
return new FutureCallback<List<BulletinBoardMessage>>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> result) {
|
||||||
|
if (result.size() < 1){
|
||||||
|
onFailure(new IllegalArgumentException("Server returned empty message list"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
batchMessage = result.get(0);
|
||||||
|
|
||||||
|
combineAndReturn();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
fail(t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class for handling returned values of subscription operations
|
||||||
|
* This class's methods also ensure continued operation of the subscription
|
||||||
|
*/
|
||||||
|
class SubscriptionCallback implements FutureCallback<List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
private SingleServerReadMessagesWorker worker;
|
||||||
|
private final MessageHandler messageHandler;
|
||||||
|
|
||||||
|
private MessageFilterList.Builder filterBuilder;
|
||||||
|
|
||||||
|
public SubscriptionCallback(SingleServerReadMessagesWorker worker, MessageHandler messageHandler) {
|
||||||
|
this.worker = worker;
|
||||||
|
this.messageHandler = messageHandler;
|
||||||
|
filterBuilder = worker.getPayload().toBuilder();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> result) {
|
||||||
|
|
||||||
|
// Report new messages to user
|
||||||
|
messageHandler.handleNewMessages(result);
|
||||||
|
|
||||||
|
// Remove last filter from list (MIN_ENTRY one)
|
||||||
|
filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1);
|
||||||
|
|
||||||
|
// Add updated MIN_ENTRY filter (entry number is successor of last received entry's number)
|
||||||
|
filterBuilder.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MIN_ENTRY)
|
||||||
|
.setEntry(result.get(result.size() - 1).getEntryNum() + 1)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// Create new worker with updated task
|
||||||
|
worker = new SingleServerReadMessagesWorker(worker.serverAddress, filterBuilder.build(), 1);
|
||||||
|
|
||||||
|
// Schedule the worker
|
||||||
|
scheduleWorker(worker, this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Notify client about failure
|
||||||
|
fail();
|
||||||
|
|
||||||
|
// Reschedule exact same task
|
||||||
|
scheduleWorker(worker, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleServerBulletinBoardClient(int threadPoolSize, long failDelayInMilliseconds, long subscriptionIntervalInMilliseconds) {
|
||||||
|
|
||||||
|
executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadPoolSize));
|
||||||
|
|
||||||
|
this.failDelayInMilliseconds = failDelayInMilliseconds;
|
||||||
|
this.subscriptionIntervalInMilliseconds = subscriptionIntervalInMilliseconds;
|
||||||
|
|
||||||
|
// Set server error time to a time sufficiently in the past to make new jobs go through
|
||||||
|
lastServerErrorTime = System.currentTimeMillis() - failDelayInMilliseconds;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores database location, initializes the web Client and
|
||||||
|
* @param clientParams contains the data needed to access the DBs
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void init(BulletinBoardClientParams clientParams) {
|
||||||
|
|
||||||
|
// Perform usual setup
|
||||||
|
super.init(clientParams);
|
||||||
|
|
||||||
|
// Wrap the Digest into a BatchDigest
|
||||||
|
batchDigest = new GenericBatchDigest(digest);
|
||||||
|
|
||||||
|
// Remove all but first DB address
|
||||||
|
String dbAddress = meerkatDBs.get(0);
|
||||||
|
meerkatDBs = new LinkedList<>();
|
||||||
|
meerkatDBs.add(dbAddress);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(meerkatDBs.get(0), msg, MAX_RETRIES);
|
||||||
|
|
||||||
|
// Submit worker and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
// Calculate the correct message ID and return it
|
||||||
|
batchDigest.reset();
|
||||||
|
batchDigest.update(msg.getMsg());
|
||||||
|
return batchDigest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PostBatchDataCallback implements FutureCallback<Boolean> {
|
||||||
|
|
||||||
|
private final CompleteBatch completeBatch;
|
||||||
|
private final FutureCallback<Boolean> callback;
|
||||||
|
|
||||||
|
public PostBatchDataCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
|
||||||
|
this.completeBatch = completeBatch;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean msg) {
|
||||||
|
closeBatch(
|
||||||
|
completeBatch.getCloseBatchMessage(),
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BeginBatchCallback implements FutureCallback<Boolean> {
|
||||||
|
|
||||||
|
private final CompleteBatch completeBatch;
|
||||||
|
private final FutureCallback<Boolean> callback;
|
||||||
|
|
||||||
|
public BeginBatchCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
|
||||||
|
this.completeBatch = completeBatch;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean msg) {
|
||||||
|
|
||||||
|
postBatchData(
|
||||||
|
completeBatch.getBeginBatchMessage().getSignerId(),
|
||||||
|
completeBatch.getBeginBatchMessage().getBatchId(),
|
||||||
|
completeBatch.getBatchDataList(),
|
||||||
|
0,
|
||||||
|
new PostBatchDataCallback(completeBatch,callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
beginBatch(
|
||||||
|
completeBatch.getBeginBatchMessage(),
|
||||||
|
new BeginBatchCallback(completeBatch, callback)
|
||||||
|
);
|
||||||
|
|
||||||
|
batchDigest.update(completeBatch);
|
||||||
|
|
||||||
|
return batchDigest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
SingleServerBeginBatchWorker worker =
|
||||||
|
new SingleServerBeginBatchWorker(meerkatDBs.get(0), beginBatchMessage, MAX_RETRIES);
|
||||||
|
|
||||||
|
// Submit worker and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList,
|
||||||
|
int startPosition, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
BatchMessage.Builder builder = BatchMessage.newBuilder()
|
||||||
|
.setSignerId(signerId)
|
||||||
|
.setBatchId(batchId);
|
||||||
|
|
||||||
|
// Create a unified callback to aggregate successful posts
|
||||||
|
|
||||||
|
PostBatchDataListCallback listCallback = new PostBatchDataListCallback(batchDataList.size(), callback);
|
||||||
|
|
||||||
|
// Iterate through data list
|
||||||
|
|
||||||
|
for (BatchData data : batchDataList) {
|
||||||
|
builder.setSerialNum(startPosition).setData(data);
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
SingleServerPostBatchWorker worker =
|
||||||
|
new SingleServerPostBatchWorker(meerkatDBs.get(0), builder.build(), MAX_RETRIES);
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, listCallback));
|
||||||
|
|
||||||
|
// Increment position in batch
|
||||||
|
startPosition++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
postBatchData(signerId, batchId, batchDataList, 0, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList,
|
||||||
|
int startPosition, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
postBatchData(signerId, batchId, batchDataList, 0, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
SingleServerCloseBatchWorker worker =
|
||||||
|
new SingleServerCloseBatchWorker(meerkatDBs.get(0), closeBatchMessage, MAX_RETRIES);
|
||||||
|
|
||||||
|
// Submit worker and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
|
||||||
|
|
||||||
|
// Create worker with no retries
|
||||||
|
SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(meerkatDBs.get(0), id, 1);
|
||||||
|
|
||||||
|
// Submit job and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
|
||||||
|
// Create job with no retries
|
||||||
|
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1);
|
||||||
|
|
||||||
|
// Submit job and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) {
|
||||||
|
|
||||||
|
// Create job with no retries for retrieval of the Bulletin Board Message that defines the batch
|
||||||
|
|
||||||
|
MessageFilterList filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.TAG)
|
||||||
|
.setTag(BulletinBoardConstants.BATCH_TAG)
|
||||||
|
.build())
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.TAG)
|
||||||
|
.setTag(BulletinBoardConstants.BATCH_ID_TAG_PREFIX + batchSpecificationMessage.getBatchId())
|
||||||
|
.build())
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.SIGNER_ID)
|
||||||
|
.setId(batchSpecificationMessage.getSignerId())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1);
|
||||||
|
|
||||||
|
// Create job with no retries for retrieval of the Batch Data List
|
||||||
|
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(meerkatDBs.get(0), batchSpecificationMessage, 1);
|
||||||
|
|
||||||
|
// Create callback that will combine the two worker products
|
||||||
|
CompleteBatchReadCallback completeBatchReadCallback = new CompleteBatchReadCallback(callback);
|
||||||
|
|
||||||
|
// Submit jobs with wrapped callbacks
|
||||||
|
scheduleWorker(messageWorker, new RetryCallback<>(messageWorker, completeBatchReadCallback.asBulletinBoardMessageListFutureCallback()));
|
||||||
|
scheduleWorker(batchWorker, new RetryCallback<>(batchWorker, completeBatchReadCallback.asBatchDataListFutureCallback()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, MessageHandler messageHandler) {
|
||||||
|
|
||||||
|
// Remove all existing MIN_ENTRY filters and create new one that starts at 0
|
||||||
|
|
||||||
|
MessageFilterList.Builder filterListBuilder = filterList.toBuilder();
|
||||||
|
|
||||||
|
Iterator<MessageFilter> iterator = filterListBuilder.getFilterList().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
MessageFilter filter = iterator.next();
|
||||||
|
|
||||||
|
if (filter.getType() == FilterType.MIN_ENTRY){
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filterListBuilder.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MIN_ENTRY)
|
||||||
|
.setEntry(0)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// Create job with no retries
|
||||||
|
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterListBuilder.build(), 1);
|
||||||
|
|
||||||
|
// Submit job and create callback
|
||||||
|
scheduleWorker(worker, new SubscriptionCallback(worker, messageHandler));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
executorService.shutdown();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.rest.ProtobufMessageBodyReader;
|
||||||
|
import meerkat.rest.ProtobufMessageBodyWriter;
|
||||||
|
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.ClientBuilder;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 02-Jan-16.
|
||||||
|
*/
|
||||||
|
public abstract class SingleServerWorker<IN, OUT> extends BulletinClientWorker<IN> implements Callable<OUT>{
|
||||||
|
|
||||||
|
// This resource enabled creation of a single Client per thread.
|
||||||
|
protected 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected final String serverAddress;
|
||||||
|
|
||||||
|
public SingleServerWorker(String serverAddress, IN payload, int maxRetry) {
|
||||||
|
super(payload, maxRetry);
|
||||||
|
this.serverAddress = serverAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerAddress() {
|
||||||
|
return serverAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,39 +1,44 @@
|
||||||
package meerkat.bulletinboard;
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.protobuf.ByteString;
|
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 meerkat.bulletinboard.workers.multiserver.*;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import meerkat.protobuf.Voting.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
||||||
* Thread-based implementation of a Bulletin Board Client.
|
* Thread-based implementation of a Async Bulletin Board Client.
|
||||||
* Features:
|
* Features:
|
||||||
* 1. Handles tasks concurrently.
|
* 1. Handles tasks concurrently.
|
||||||
* 2. Retries submitting
|
* 2. Retries submitting
|
||||||
*/
|
*/
|
||||||
public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient implements AsyncBulletinBoardClient {
|
||||||
|
|
||||||
private final static int THREAD_NUM = 10;
|
// Executor service for handling jobs
|
||||||
ListeningExecutorService listeningExecutor;
|
private final static int JOBS_THREAD_NUM = 5;
|
||||||
|
private ExecutorService executorService;
|
||||||
|
|
||||||
private Digest digest;
|
// Per-server clients
|
||||||
|
private List<SingleServerBulletinBoardClient> clients;
|
||||||
|
|
||||||
private List<String> meerkatDBs;
|
private BatchDigest batchDigest;
|
||||||
private String postSubAddress;
|
|
||||||
private String readSubAddress;
|
|
||||||
|
|
||||||
|
private final static int POST_MESSAGE_RETRY_NUM = 3;
|
||||||
private final static int READ_MESSAGES_RETRY_NUM = 1;
|
private final static int READ_MESSAGES_RETRY_NUM = 1;
|
||||||
|
private final static int GET_REDUNDANCY_RETRY_NUM = 1;
|
||||||
|
|
||||||
|
private static final int SERVER_THREADPOOL_SIZE = 5;
|
||||||
|
private static final long FAIL_DELAY = 5000;
|
||||||
|
private static final long SUBSCRIPTION_INTERVAL = 10000;
|
||||||
|
|
||||||
private int minAbsoluteRedundancy;
|
private int minAbsoluteRedundancy;
|
||||||
|
|
||||||
|
@ -44,15 +49,29 @@ public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
||||||
* @param clientParams contains the required information
|
* @param clientParams contains the required information
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void init(Voting.BulletinBoardClientParams clientParams) {
|
public void init(BulletinBoardClientParams clientParams) {
|
||||||
|
|
||||||
meerkatDBs = clientParams.getBulletinBoardAddressList();
|
super.init(clientParams);
|
||||||
|
|
||||||
minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * meerkatDBs.size());
|
batchDigest = new GenericBatchDigest(digest);
|
||||||
|
|
||||||
listeningExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(THREAD_NUM));
|
minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * clientParams.getBulletinBoardAddressCount());
|
||||||
|
|
||||||
digest = new SHA256Digest();
|
executorService = Executors.newFixedThreadPool(JOBS_THREAD_NUM);
|
||||||
|
|
||||||
|
clients = new ArrayList<>(clientParams.getBulletinBoardAddressCount());
|
||||||
|
for (String address : clientParams.getBulletinBoardAddressList()){
|
||||||
|
|
||||||
|
SingleServerBulletinBoardClient client =
|
||||||
|
new SingleServerBulletinBoardClient(SERVER_THREADPOOL_SIZE, FAIL_DELAY, SUBSCRIPTION_INTERVAL);
|
||||||
|
|
||||||
|
client.init(BulletinBoardClientParams.newBuilder()
|
||||||
|
.addBulletinBoardAddress(address)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
clients.add(client);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,21 +80,100 @@ public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
||||||
* Retry failed DBs
|
* Retry failed DBs
|
||||||
* @param msg is the message,
|
* @param msg is the message,
|
||||||
* @return the message ID for later retrieval
|
* @return the message ID for later retrieval
|
||||||
* @throws CommunicationException
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public MessageID postMessage(BulletinBoardMessage msg, ClientCallback<?> callback){
|
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback){
|
||||||
|
|
||||||
// Create job
|
// Create job
|
||||||
BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.POST_MESSAGE, msg, -1);
|
MultiServerPostMessageWorker worker =
|
||||||
|
new MultiServerPostMessageWorker(clients, minAbsoluteRedundancy, msg, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
// Submit job and create callback
|
// Submit job
|
||||||
Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new PostMessageFutureCallback(listeningExecutor, callback));
|
executorService.submit(worker);
|
||||||
|
|
||||||
// Calculate the correct message ID and return it
|
// Calculate the correct message ID and return it
|
||||||
digest.reset();
|
batchDigest.reset();
|
||||||
digest.update(msg.getMsg());
|
batchDigest.update(msg.getMsg());
|
||||||
return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build();
|
return batchDigest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
MultiServerPostBatchWorker worker =
|
||||||
|
new MultiServerPostBatchWorker(clients, minAbsoluteRedundancy, completeBatch, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
|
// Calculate the correct message ID and return it
|
||||||
|
batchDigest.reset();
|
||||||
|
batchDigest.update(completeBatch);
|
||||||
|
return batchDigest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
MultiServerBeginBatchWorker worker =
|
||||||
|
new MultiServerBeginBatchWorker(clients, minAbsoluteRedundancy, beginBatchMessage, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList,
|
||||||
|
int startPosition, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
BatchDataContainer batchDataContainer = new BatchDataContainer(signerId, batchId, batchDataList, startPosition);
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
MultiServerPostBatchDataWorker worker =
|
||||||
|
new MultiServerPostBatchDataWorker(clients, minAbsoluteRedundancy, batchDataContainer, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
postBatchData(signerId, batchId, batchDataList, 0, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList,
|
||||||
|
int startPosition, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
postBatchData(signerId.toByteArray(), batchId, batchDataList, startPosition, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
postBatchData(signerId, batchId, batchDataList, 0, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
MultiServerCloseBatchWorker worker =
|
||||||
|
new MultiServerCloseBatchWorker(clients, minAbsoluteRedundancy, closeBatchMessage, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,17 +181,16 @@ public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
||||||
* Return the number of databases in which the message was found
|
* Return the number of databases in which the message was found
|
||||||
* Only try once per DB
|
* Only try once per DB
|
||||||
* Ignore communication exceptions in specific databases
|
* Ignore communication exceptions in specific databases
|
||||||
* @param id is the requested message ID
|
|
||||||
* @return the number of DBs in which retrieval was successful
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void getRedundancy(MessageID id, ClientCallback<Float> callback) {
|
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
|
||||||
|
|
||||||
// Create job
|
// Create job
|
||||||
BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.GET_REDUNDANCY, id, 1);
|
MultiServerGetRedundancyWorker worker =
|
||||||
|
new MultiServerGetRedundancyWorker(clients, minAbsoluteRedundancy, id, GET_REDUNDANCY_RETRY_NUM, callback);
|
||||||
|
|
||||||
// Submit job and create callback
|
// Submit job
|
||||||
Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new GetRedundancyFutureCallback(listeningExecutor, callback));
|
executorService.submit(worker);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,27 +198,49 @@ public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
||||||
* Go through the DBs and try to retrieve messages according to the specified filter
|
* 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 at the operation is successful for some DB: return the results and stop iterating
|
||||||
* If no operation is successful: return null (NOT blank list)
|
* 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
|
@Override
|
||||||
public void readMessages(MessageFilterList filterList, ClientCallback<List<BulletinBoardMessage>> callback) {
|
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
|
||||||
// Create job
|
// Create job
|
||||||
BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.READ_MESSAGES,
|
MultiServerReadMessagesWorker worker =
|
||||||
filterList, READ_MESSAGES_RETRY_NUM);
|
new MultiServerReadMessagesWorker(clients, minAbsoluteRedundancy, filterList, READ_MESSAGES_RETRY_NUM, callback);
|
||||||
|
|
||||||
// Submit job and create callback
|
// Submit job
|
||||||
Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new ReadMessagesFutureCallback(listeningExecutor, callback));
|
executorService.submit(worker);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) {
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
MultiServerReadBatchWorker worker =
|
||||||
|
new MultiServerReadBatchWorker(clients, minAbsoluteRedundancy, batchSpecificationMessage, READ_MESSAGES_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, MessageHandler messageHandler) {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
listeningExecutor.shutdown();
|
|
||||||
while (! listeningExecutor.isShutdown()) {
|
for (SingleServerBulletinBoardClient client : clients){
|
||||||
listeningExecutor.awaitTermination(10, TimeUnit.SECONDS);
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
executorService.shutdown();
|
||||||
|
while (! executorService.isShutdown()) {
|
||||||
|
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
System.err.println(e.getCause() + " " + e.getMessage());
|
System.err.println(e.getCause() + " " + e.getMessage());
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
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,28 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerBeginBatchWorker extends MultiServerGenericPostWorker<BeginBatchMessage> {
|
||||||
|
|
||||||
|
public MultiServerBeginBatchWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, BeginBatchMessage payload, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(SingleServerBulletinBoardClient client, BeginBatchMessage payload) {
|
||||||
|
client.beginBatch(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.CloseBatchMessage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerCloseBatchWorker extends MultiServerGenericPostWorker<CloseBatchMessage> {
|
||||||
|
|
||||||
|
public MultiServerCloseBatchWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, CloseBatchMessage payload, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(SingleServerBulletinBoardClient client, CloseBatchMessage payload) {
|
||||||
|
client.closeBatch(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.MultiServerWorker;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public abstract class MultiServerGenericPostWorker<T> extends MultiServerWorker<T, Boolean> {
|
||||||
|
|
||||||
|
public MultiServerGenericPostWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, T payload, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doPost(SingleServerBulletinBoardClient client, T payload);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the servers via HTTP Post
|
||||||
|
* It accesses the servers one by one and tries to post the payload to each in turn
|
||||||
|
* The method will only iterate once through the server list
|
||||||
|
* Successful post to a server results in removing the server from the list
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
// Iterate through servers
|
||||||
|
|
||||||
|
Iterator<SingleServerBulletinBoardClient> clientIterator = getClientIterator();
|
||||||
|
|
||||||
|
while (clientIterator.hasNext()) {
|
||||||
|
|
||||||
|
// Send request to Server
|
||||||
|
SingleServerBulletinBoardClient client = clientIterator.next();
|
||||||
|
|
||||||
|
doPost(client, payload);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
if (result){
|
||||||
|
if (minServers.decrementAndGet() <= 0){
|
||||||
|
succeed(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (maxFailedServers.decrementAndGet() < 0){
|
||||||
|
fail(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.MultiServerWorker;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerWorker<IN, OUT>{
|
||||||
|
|
||||||
|
private final Iterator<SingleServerBulletinBoardClient> clientIterator;
|
||||||
|
|
||||||
|
public MultiServerGenericReadWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, IN payload, int maxRetry,
|
||||||
|
FutureCallback<OUT> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, true, minServers, payload, maxRetry, futureCallback); // Shuffle clients on creation to balance load
|
||||||
|
|
||||||
|
clientIterator = getClientIterator();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doRead(IN payload, SingleServerBulletinBoardClient client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the servers via HTTP Post
|
||||||
|
* It accesses the servers in a random order until one answers it
|
||||||
|
* Successful retrieval from any server terminates the method and returns the received values; The list is not changed
|
||||||
|
*/
|
||||||
|
public void run(){
|
||||||
|
|
||||||
|
// Iterate through servers
|
||||||
|
|
||||||
|
if (clientIterator.hasNext()) {
|
||||||
|
|
||||||
|
// Get next server
|
||||||
|
SingleServerBulletinBoardClient client = clientIterator.next();
|
||||||
|
|
||||||
|
// Retrieve answer from server
|
||||||
|
doRead(payload, client);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fail(new CommunicationException("Could not contact any server"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(OUT msg) {
|
||||||
|
succeed(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
run(); // Retry with next server
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.MultiServerWorker;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerGetRedundancyWorker extends MultiServerWorker<MessageID, Float> {
|
||||||
|
|
||||||
|
private AtomicInteger serversContainingMessage;
|
||||||
|
private AtomicInteger totalContactedServers;
|
||||||
|
|
||||||
|
public MultiServerGetRedundancyWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, MessageID payload, int maxRetry,
|
||||||
|
FutureCallback<Float> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback); // Shuffle clients on creation to balance load
|
||||||
|
|
||||||
|
serversContainingMessage = new AtomicInteger(0);
|
||||||
|
totalContactedServers = new AtomicInteger(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the servers via HTTP Post
|
||||||
|
* It accesses the servers in a random order until one answers it
|
||||||
|
* Successful retrieval from any server terminates the method and returns the received values; The list is not changed
|
||||||
|
*/
|
||||||
|
public void run(){
|
||||||
|
|
||||||
|
Iterator<SingleServerBulletinBoardClient> clientIterator = getClientIterator();
|
||||||
|
|
||||||
|
// Iterate through clients
|
||||||
|
|
||||||
|
while (clientIterator.hasNext()) {
|
||||||
|
|
||||||
|
SingleServerBulletinBoardClient client = clientIterator.next();
|
||||||
|
|
||||||
|
// Send request to client
|
||||||
|
client.getRedundancy(payload,this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Float result) {
|
||||||
|
|
||||||
|
if (result > 0.5) {
|
||||||
|
serversContainingMessage.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalContactedServers.incrementAndGet() >= getClientNumber()){
|
||||||
|
succeed(((float) serversContainingMessage.get()) / ((float) getClientNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
onSuccess(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.bulletinboard.BatchDataContainer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerPostBatchDataWorker extends MultiServerGenericPostWorker<BatchDataContainer> {
|
||||||
|
|
||||||
|
public MultiServerPostBatchDataWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, BatchDataContainer payload, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(SingleServerBulletinBoardClient client, BatchDataContainer payload) {
|
||||||
|
client.postBatchData(payload.signerId, payload.batchId, payload.batchDataList, payload.startPosition, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.CompleteBatch;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerPostBatchWorker extends MultiServerGenericPostWorker<CompleteBatch> {
|
||||||
|
|
||||||
|
public MultiServerPostBatchWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, CompleteBatch payload, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(SingleServerBulletinBoardClient client, CompleteBatch payload) {
|
||||||
|
client.postBatch(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerPostMessageWorker extends MultiServerGenericPostWorker<BulletinBoardMessage> {
|
||||||
|
|
||||||
|
public MultiServerPostMessageWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, BulletinBoardMessage payload, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(SingleServerBulletinBoardClient client, BulletinBoardMessage payload) {
|
||||||
|
client.postMessage(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.CompleteBatch;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BatchSpecificationMessage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerReadBatchWorker extends MultiServerGenericReadWorker<BatchSpecificationMessage, CompleteBatch> {
|
||||||
|
|
||||||
|
public MultiServerReadBatchWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, BatchSpecificationMessage payload, int maxRetry,
|
||||||
|
FutureCallback<CompleteBatch> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRead(BatchSpecificationMessage payload, SingleServerBulletinBoardClient client) {
|
||||||
|
client.readBatch(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerReadMessagesWorker extends MultiServerGenericReadWorker<MessageFilterList,List<BulletinBoardMessage>>{
|
||||||
|
|
||||||
|
public MultiServerReadMessagesWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, MessageFilterList payload, int maxRetry,
|
||||||
|
FutureCallback<List<BulletinBoardMessage>> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRead(MessageFilterList payload, SingleServerBulletinBoardClient client) {
|
||||||
|
client.readMessages(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BEGIN_BATCH_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a post operation
|
||||||
|
*/
|
||||||
|
public class SingleServerBeginBatchWorker extends SingleServerGenericPostWorker<BeginBatchMessage> {
|
||||||
|
|
||||||
|
public SingleServerBeginBatchWorker(String serverAddress, BeginBatchMessage payload, int maxRetry) {
|
||||||
|
super(serverAddress, BEGIN_BATCH_PATH, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.CloseBatchMessage;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.CLOSE_BATCH_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a close batch operation
|
||||||
|
*/
|
||||||
|
public class SingleServerCloseBatchWorker extends SingleServerGenericPostWorker<CloseBatchMessage> {
|
||||||
|
|
||||||
|
public SingleServerCloseBatchWorker(String serverAddress, CloseBatchMessage payload, int maxRetry) {
|
||||||
|
super(serverAddress, CLOSE_BATCH_PATH, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BoolMsg;
|
||||||
|
import meerkat.rest.Constants;
|
||||||
|
|
||||||
|
import javax.ws.rs.ProcessingException;
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.Entity;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a post operation
|
||||||
|
*/
|
||||||
|
public class SingleServerGenericPostWorker<T> extends SingleServerWorker<T, Boolean> {
|
||||||
|
|
||||||
|
private final String subPath;
|
||||||
|
|
||||||
|
public SingleServerGenericPostWorker(String serverAddress, String subPath, T payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
this.subPath = subPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the server via HTTP Post
|
||||||
|
* It accesses the server and tries to post the payload to it
|
||||||
|
* Successful post to a server results
|
||||||
|
* @return TRUE if the operation is successful
|
||||||
|
* @throws CommunicationException if the operation is unsuccessful
|
||||||
|
*/
|
||||||
|
public Boolean call() throws CommunicationException{
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(subPath);
|
||||||
|
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(
|
||||||
|
Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// If a BoolMsg entity is returned: the post was successful
|
||||||
|
response.readEntity(BoolMsg.class);
|
||||||
|
return Boolean.TRUE;
|
||||||
|
|
||||||
|
} catch (ProcessingException | IllegalStateException e) {
|
||||||
|
|
||||||
|
// Post to this server failed
|
||||||
|
throw new CommunicationException("Could not contact the server");
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import meerkat.rest.Constants;
|
||||||
|
|
||||||
|
import javax.ws.rs.ProcessingException;
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.Entity;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class SingleServerGetRedundancyWorker extends SingleServerWorker<MessageID, Float> {
|
||||||
|
|
||||||
|
public SingleServerGetRedundancyWorker(String serverAddress, MessageID payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the server via HTTP Post
|
||||||
|
* It queries the server for a message with the given ID
|
||||||
|
* @return TRUE if the message exists in the server and FALSE otherwise
|
||||||
|
* @throws CommunicationException if the server does not return a valid answer
|
||||||
|
*/
|
||||||
|
public Float call() throws CommunicationException{
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget;
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
MessageFilterList msgFilterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MSG_ID)
|
||||||
|
.setId(payload.getID())
|
||||||
|
.build()
|
||||||
|
).build();
|
||||||
|
|
||||||
|
// Send request to Server
|
||||||
|
|
||||||
|
webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
|
||||||
|
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msgFilterList, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
|
// Retrieve answer
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// If a BulletinBoardMessageList is returned: the read was successful
|
||||||
|
BulletinBoardMessageList msgList = response.readEntity(BulletinBoardMessageList.class);
|
||||||
|
|
||||||
|
if (msgList.getMessageList().size() > 0){
|
||||||
|
// Message exists in the server
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Message does not exist in the server
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (ProcessingException | IllegalStateException e) {
|
||||||
|
|
||||||
|
// Read failed
|
||||||
|
throw new CommunicationException("Server access failed");
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BatchMessage;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.POST_BATCH_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a post batch operation
|
||||||
|
*/
|
||||||
|
public class SingleServerPostBatchWorker extends SingleServerGenericPostWorker<BatchMessage> {
|
||||||
|
|
||||||
|
public SingleServerPostBatchWorker(String serverAddress, BatchMessage payload, int maxRetry) {
|
||||||
|
super(serverAddress, POST_BATCH_PATH, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.POST_MESSAGE_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a post operation
|
||||||
|
*/
|
||||||
|
public class SingleServerPostMessageWorker extends SingleServerGenericPostWorker<BulletinBoardMessage> {
|
||||||
|
|
||||||
|
public SingleServerPostMessageWorker(String serverAddress, BulletinBoardMessage payload, int maxRetry) {
|
||||||
|
super(serverAddress, POST_MESSAGE_PATH, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.CompleteBatch;
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.comm.MessageInputStream;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import meerkat.rest.Constants;
|
||||||
|
|
||||||
|
import javax.ws.rs.ProcessingException;
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.Entity;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.GenericType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.READ_BATCH_PATH;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BATCH_ID_TAG_PREFIX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class SingleServerReadBatchWorker extends SingleServerWorker<BatchSpecificationMessage, List<BatchData>> {
|
||||||
|
|
||||||
|
public SingleServerReadBatchWorker(String serverAddress, BatchSpecificationMessage payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the server via HTTP Post
|
||||||
|
* Upon successful retrieval from the server the method returns the received values
|
||||||
|
* @return the complete batch as read from the server
|
||||||
|
* @throws CommunicationException if the server's response is invalid
|
||||||
|
*/
|
||||||
|
public List<BatchData> call() throws CommunicationException{
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget;
|
||||||
|
|
||||||
|
// Get the batch data
|
||||||
|
|
||||||
|
webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(READ_BATCH_PATH);
|
||||||
|
InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF), InputStream.class);
|
||||||
|
|
||||||
|
MessageInputStream<BatchData> inputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchData.class);
|
||||||
|
|
||||||
|
return inputStream.asList();
|
||||||
|
|
||||||
|
} catch (IOException | InvocationTargetException e) {
|
||||||
|
|
||||||
|
// Read failed
|
||||||
|
throw new CommunicationException("Could not contact the server or server returned illegal result");
|
||||||
|
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
|
||||||
|
throw new CommunicationException("MessageInputStream error");
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.comm.MessageInputStream;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessageList;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.MessageFilterList;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
|
||||||
|
import meerkat.rest.Constants;
|
||||||
|
|
||||||
|
import javax.ws.rs.ProcessingException;
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.Entity;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class SingleServerReadMessagesWorker extends SingleServerWorker<MessageFilterList, List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
public SingleServerReadMessagesWorker(String serverAddress, MessageFilterList payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the server via HTTP Post
|
||||||
|
* Upon successful retrieval from the server the method returns the received values
|
||||||
|
* @return The list of messages returned by the server
|
||||||
|
* @throws CommunicationException if the server's response is invalid
|
||||||
|
*/
|
||||||
|
public List<BulletinBoardMessage> call() throws CommunicationException{
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget;
|
||||||
|
|
||||||
|
// Send request to Server
|
||||||
|
webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
|
||||||
|
InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF), InputStream.class);
|
||||||
|
|
||||||
|
MessageInputStream<BulletinBoardMessage> inputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
return inputStream.asList();
|
||||||
|
|
||||||
|
} catch (IOException | InvocationTargetException e) {
|
||||||
|
|
||||||
|
// Read failed
|
||||||
|
throw new CommunicationException("Could not contact the server or server returned illegal result");
|
||||||
|
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
|
||||||
|
throw new CommunicationException("MessageInputStream error");
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,212 +0,0 @@
|
||||||
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.BulletinBoardClientParams;
|
|
||||||
import meerkat.util.BulletinBoardMessageComparator;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.Semaphore;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,556 @@
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.protobuf.*;
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.bulletinboard.AsyncBulletinBoardClient;
|
||||||
|
import meerkat.bulletinboard.CompleteBatch;
|
||||||
|
import meerkat.bulletinboard.GenericBatchDigitalSignature;
|
||||||
|
import meerkat.bulletinboard.ThreadedBulletinBoardClient;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.crypto.concrete.ECDSASignature;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import meerkat.protobuf.Crypto;
|
||||||
|
|
||||||
|
import meerkat.protobuf.Voting.*;
|
||||||
|
import meerkat.util.BulletinBoardMessageComparator;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
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.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
||||||
|
*/
|
||||||
|
public class ThreadedBulletinBoardClientIntegrationTest {
|
||||||
|
|
||||||
|
// Signature resources
|
||||||
|
|
||||||
|
private GenericBatchDigitalSignature signers[];
|
||||||
|
private ByteString[] signerIDs;
|
||||||
|
|
||||||
|
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
|
||||||
|
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";
|
||||||
|
|
||||||
|
private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt";
|
||||||
|
private static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt";
|
||||||
|
|
||||||
|
// Server data
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Client and callbacks
|
||||||
|
|
||||||
|
private AsyncBulletinBoardClient bulletinBoardClient;
|
||||||
|
|
||||||
|
private PostCallback postCallback;
|
||||||
|
private PostCallback failPostCallback = new PostCallback(true,false);
|
||||||
|
|
||||||
|
private RedundancyCallback redundancyCallback;
|
||||||
|
private ReadCallback readCallback;
|
||||||
|
private ReadBatchCallback readBatchCallback;
|
||||||
|
|
||||||
|
// Sync and misc
|
||||||
|
|
||||||
|
private Semaphore jobSemaphore;
|
||||||
|
private Vector<Throwable> thrown;
|
||||||
|
private Random random;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
|
||||||
|
public ThreadedBulletinBoardClientIntegrationTest(){
|
||||||
|
|
||||||
|
signers = new GenericBatchDigitalSignature[2];
|
||||||
|
signerIDs = new ByteString[signers.length];
|
||||||
|
signers[0] = new GenericBatchDigitalSignature(new ECDSASignature());
|
||||||
|
signers[1] = new GenericBatchDigitalSignature(new ECDSASignature());
|
||||||
|
|
||||||
|
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
|
||||||
|
char[] password = KEYFILE_PASSWORD1.toCharArray();
|
||||||
|
|
||||||
|
KeyStore.Builder keyStoreBuilder;
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback definitions
|
||||||
|
|
||||||
|
protected void genericHandleFailure(Throwable t){
|
||||||
|
System.err.println(t.getCause() + " " + t.getMessage());
|
||||||
|
thrown.add(t);
|
||||||
|
jobSemaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PostCallback implements FutureCallback<Boolean>{
|
||||||
|
|
||||||
|
private boolean isAssert;
|
||||||
|
private boolean assertValue;
|
||||||
|
|
||||||
|
public PostCallback() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostCallback(boolean isAssert) {
|
||||||
|
this(isAssert,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostCallback(boolean isAssert, boolean assertValue) {
|
||||||
|
this.isAssert = isAssert;
|
||||||
|
this.assertValue = assertValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean msg) {
|
||||||
|
System.err.println("Post operation completed");
|
||||||
|
jobSemaphore.release();
|
||||||
|
//TODO: Change Assert mechanism to exception one
|
||||||
|
if (isAssert) {
|
||||||
|
if (assertValue) {
|
||||||
|
assertThat("Post operation failed", msg, is(Boolean.TRUE));
|
||||||
|
} else {
|
||||||
|
assertThat("Post operation succeeded unexpectedly", msg, is(Boolean.FALSE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RedundancyCallback implements FutureCallback<Float>{
|
||||||
|
|
||||||
|
private float minRedundancy;
|
||||||
|
|
||||||
|
public RedundancyCallback(float minRedundancy) {
|
||||||
|
this.minRedundancy = minRedundancy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Float redundancy) {
|
||||||
|
System.err.println("Redundancy found is: " + redundancy);
|
||||||
|
jobSemaphore.release();
|
||||||
|
assertThat(redundancy, greaterThanOrEqualTo(minRedundancy));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReadCallback implements FutureCallback<List<BulletinBoardMessage>>{
|
||||||
|
|
||||||
|
private List<BulletinBoardMessage> expectedMsgList;
|
||||||
|
|
||||||
|
public ReadCallback(List<BulletinBoardMessage> expectedMsgList) {
|
||||||
|
this.expectedMsgList = expectedMsgList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(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 onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReadBatchCallback implements FutureCallback<CompleteBatch> {
|
||||||
|
|
||||||
|
private CompleteBatch expectedBatch;
|
||||||
|
|
||||||
|
public ReadBatchCallback(CompleteBatch expectedBatch) {
|
||||||
|
this.expectedBatch = expectedBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(CompleteBatch batch) {
|
||||||
|
|
||||||
|
System.err.println(batch);
|
||||||
|
jobSemaphore.release();
|
||||||
|
|
||||||
|
assertThat("Batch returned is incorrect", batch, is(equalTo(expectedBatch)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Randomness generators
|
||||||
|
|
||||||
|
private byte randomByte(){
|
||||||
|
return (byte) random.nextInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] randomByteArray(int length) {
|
||||||
|
|
||||||
|
byte[] randomBytes = new byte[length];
|
||||||
|
|
||||||
|
for (int i = 0; i < length ; i++){
|
||||||
|
randomBytes[i] = randomByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
return randomBytes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompleteBatch createRandomBatch(int signer, int batchId, int length) throws SignatureException {
|
||||||
|
|
||||||
|
CompleteBatch completeBatch = new CompleteBatch();
|
||||||
|
|
||||||
|
// Create data
|
||||||
|
|
||||||
|
completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder()
|
||||||
|
.setSignerId(signerIDs[signer])
|
||||||
|
.setBatchId(batchId)
|
||||||
|
.addTag("Test")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
for (int i = 0 ; i < length ; i++){
|
||||||
|
|
||||||
|
BatchData batchData = BatchData.newBuilder()
|
||||||
|
.setData(ByteString.copyFrom(randomByteArray(i)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
completeBatch.appendBatchData(batchData);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
completeBatch.setTimestamp(Timestamp.newBuilder()
|
||||||
|
.setSeconds(Math.abs(90))
|
||||||
|
.setNanos(50)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
signers[signer].updateContent(completeBatch);
|
||||||
|
|
||||||
|
completeBatch.setSignature(signers[signer].sign());
|
||||||
|
|
||||||
|
return completeBatch;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes care of initializing the client and the test resources
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void init(){
|
||||||
|
|
||||||
|
bulletinBoardClient = new ThreadedBulletinBoardClient();
|
||||||
|
|
||||||
|
random = new Random(0); // We use insecure randomness in tests for repeatability
|
||||||
|
|
||||||
|
List<String> testDB = new LinkedList<>();
|
||||||
|
testDB.add(BASE_URL);
|
||||||
|
|
||||||
|
bulletinBoardClient.init(BulletinBoardClientParams.newBuilder()
|
||||||
|
.addAllBulletinBoardAddress(testDB)
|
||||||
|
.setMinRedundancy((float) 1.0)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
postCallback = new PostCallback();
|
||||||
|
redundancyCallback = new RedundancyCallback((float) 1.0);
|
||||||
|
|
||||||
|
thrown = new Vector<>();
|
||||||
|
jobSemaphore = new Semaphore(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the client and makes sure the test fails when an exception occurred in a separate thread
|
||||||
|
*/
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
bulletinBoardClient.close();
|
||||||
|
|
||||||
|
if (thrown.size() > 0) {
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the standard post, redundancy and read methods
|
||||||
|
*/
|
||||||
|
@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};
|
||||||
|
|
||||||
|
BulletinBoardMessage msg;
|
||||||
|
|
||||||
|
MessageFilterList filterList;
|
||||||
|
List<BulletinBoardMessage> msgList;
|
||||||
|
|
||||||
|
MessageID messageID;
|
||||||
|
|
||||||
|
msg = BulletinBoardMessage.newBuilder()
|
||||||
|
.setMsg(UnsignedBulletinBoardMessage.newBuilder()
|
||||||
|
.addTag("Signature")
|
||||||
|
.addTag("Trustee")
|
||||||
|
.setData(ByteString.copyFrom(b1))
|
||||||
|
.setTimestamp(Timestamp.newBuilder()
|
||||||
|
.setSeconds(20)
|
||||||
|
.setNanos(30)
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.addSig(Crypto.Signature.newBuilder()
|
||||||
|
.setType(Crypto.SignatureType.DSA)
|
||||||
|
.setData(ByteString.copyFrom(b2))
|
||||||
|
.setSignerId(ByteString.copyFrom(b3))
|
||||||
|
.build())
|
||||||
|
.addSig(Crypto.Signature.newBuilder()
|
||||||
|
.setType(Crypto.SignatureType.ECDSA)
|
||||||
|
.setData(ByteString.copyFrom(b3))
|
||||||
|
.setSignerId(ByteString.copyFrom(b2))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
messageID = bulletinBoardClient.postMessage(msg,postCallback);
|
||||||
|
|
||||||
|
try {
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.err.println(e.getCause() + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
bulletinBoardClient.getRedundancy(messageID,redundancyCallback);
|
||||||
|
|
||||||
|
filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(
|
||||||
|
MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.TAG)
|
||||||
|
.setTag("Signature")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.addFilter(
|
||||||
|
MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.TAG)
|
||||||
|
.setTag("Trustee")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
msgList = new LinkedList<>();
|
||||||
|
msgList.add(msg);
|
||||||
|
|
||||||
|
readCallback = new ReadCallback(msgList);
|
||||||
|
|
||||||
|
bulletinBoardClient.readMessages(filterList, readCallback);
|
||||||
|
try {
|
||||||
|
jobSemaphore.acquire(2);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.err.println(e.getCause() + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests posting a batch by parts
|
||||||
|
* Also tests not being able to post to a closed batch
|
||||||
|
* @throws CommunicationException, SignatureException, InterruptedException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
|
||||||
|
|
||||||
|
final int SIGNER = 1;
|
||||||
|
final int BATCH_ID = 100;
|
||||||
|
final int BATCH_LENGTH = 100;
|
||||||
|
|
||||||
|
CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH);
|
||||||
|
|
||||||
|
// Begin batch
|
||||||
|
|
||||||
|
bulletinBoardClient.beginBatch(completeBatch.getBeginBatchMessage(), postCallback);
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
// Post data
|
||||||
|
|
||||||
|
bulletinBoardClient.postBatchData(signerIDs[SIGNER], BATCH_ID, completeBatch.getBatchDataList(), postCallback);
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
// Close batch
|
||||||
|
|
||||||
|
CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder()
|
||||||
|
.setBatchId(BATCH_ID)
|
||||||
|
.setBatchLength(BATCH_LENGTH)
|
||||||
|
.setTimestamp(Timestamp.newBuilder()
|
||||||
|
.setSeconds(50)
|
||||||
|
.setNanos(80)
|
||||||
|
.build())
|
||||||
|
.setSig(completeBatch.getSignature())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
bulletinBoardClient.closeBatch(closeBatchMessage, postCallback);
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
// Attempt to open batch again
|
||||||
|
|
||||||
|
bulletinBoardClient.beginBatch(completeBatch.getBeginBatchMessage(), failPostCallback);
|
||||||
|
|
||||||
|
// Attempt to add batch data
|
||||||
|
|
||||||
|
bulletinBoardClient.postBatchData(signerIDs[SIGNER], BATCH_ID, completeBatch.getBatchDataList(), failPostCallback);
|
||||||
|
|
||||||
|
jobSemaphore.acquire(2);
|
||||||
|
|
||||||
|
// Read batch data
|
||||||
|
|
||||||
|
BatchSpecificationMessage batchSpecificationMessage =
|
||||||
|
BatchSpecificationMessage.newBuilder()
|
||||||
|
.setSignerId(signerIDs[SIGNER])
|
||||||
|
.setBatchId(BATCH_ID)
|
||||||
|
.setStartPosition(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
readBatchCallback = new ReadBatchCallback(completeBatch);
|
||||||
|
|
||||||
|
bulletinBoardClient.readBatch(batchSpecificationMessage, readBatchCallback);
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a complete batch message
|
||||||
|
* Checks reading od the message
|
||||||
|
* @throws CommunicationException, SignatureException, InterruptedException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
|
||||||
|
|
||||||
|
final int SIGNER = 0;
|
||||||
|
final int BATCH_ID = 101;
|
||||||
|
final int BATCH_LENGTH = 50;
|
||||||
|
|
||||||
|
// Post batch
|
||||||
|
|
||||||
|
CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH);
|
||||||
|
|
||||||
|
bulletinBoardClient.postBatch(completeBatch,postCallback);
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
// Read batch
|
||||||
|
|
||||||
|
BatchSpecificationMessage batchSpecificationMessage =
|
||||||
|
BatchSpecificationMessage.newBuilder()
|
||||||
|
.setSignerId(signerIDs[SIGNER])
|
||||||
|
.setBatchId(BATCH_ID)
|
||||||
|
.setStartPosition(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
readBatchCallback = new ReadBatchCallback(completeBatch);
|
||||||
|
|
||||||
|
bulletinBoardClient.readBatch(batchSpecificationMessage, readBatchCallback);
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that an unopened batch cannot be closed
|
||||||
|
* @throws CommunicationException, InterruptedException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testInvalidBatchClose() throws CommunicationException, InterruptedException {
|
||||||
|
|
||||||
|
final int NON_EXISTENT_BATCH_ID = 999;
|
||||||
|
|
||||||
|
CloseBatchMessage closeBatchMessage =
|
||||||
|
CloseBatchMessage.newBuilder()
|
||||||
|
.setBatchId(NON_EXISTENT_BATCH_ID)
|
||||||
|
.setBatchLength(1)
|
||||||
|
.setSig(Crypto.Signature.getDefaultInstance())
|
||||||
|
.setTimestamp(Timestamp.newBuilder()
|
||||||
|
.setSeconds(9)
|
||||||
|
.setNanos(12)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Try to close the (unopened) batch;
|
||||||
|
|
||||||
|
bulletinBoardClient.closeBatch(closeBatchMessage, failPostCallback);
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ dependencies {
|
||||||
|
|
||||||
// JDBC connections
|
// JDBC connections
|
||||||
compile 'org.springframework:spring-jdbc:4.2.+'
|
compile 'org.springframework:spring-jdbc:4.2.+'
|
||||||
compile 'org.xerial:sqlite-jdbc:3.7.+'
|
compile 'org.xerial:sqlite-jdbc:3.8.+'
|
||||||
compile 'mysql:mysql-connector-java:5.1.+'
|
compile 'mysql:mysql-connector-java:5.1.+'
|
||||||
compile 'com.h2database:h2:1.0.+'
|
compile 'com.h2database:h2:1.0.+'
|
||||||
|
|
||||||
|
@ -79,9 +79,30 @@ test {
|
||||||
exclude '**/*IntegrationTest*'
|
exclude '**/*IntegrationTest*'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task myTest(type: Test) {
|
||||||
|
include '**/*MySQL*Test*'
|
||||||
|
outputs.upToDateWhen { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
task h2Test(type: Test) {
|
||||||
|
include '**/*H2*Test*'
|
||||||
|
outputs.upToDateWhen { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
task liteTest(type: Test) {
|
||||||
|
include '**/*SQLite*Test*'
|
||||||
|
outputs.upToDateWhen { false }
|
||||||
|
}
|
||||||
|
|
||||||
task dbTest(type: Test) {
|
task dbTest(type: Test) {
|
||||||
include '**/*H2*Test*'
|
include '**/*H2*Test*'
|
||||||
include '**/*MySql*Test'
|
include '**/*MySQL*Test*'
|
||||||
|
include '**/*SQLite*Test*'
|
||||||
|
outputs.upToDateWhen { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
task manualIntegration(type: Test) {
|
||||||
|
include '**/*IntegrationTest*'
|
||||||
}
|
}
|
||||||
|
|
||||||
task integrationTest(type: Test) {
|
task integrationTest(type: Test) {
|
||||||
|
|
|
@ -3,26 +3,36 @@ package meerkat.bulletinboard.sqlserver;
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import com.google.protobuf.ProtocolStringList;
|
import com.google.protobuf.*;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.*;
|
||||||
|
import meerkat.bulletinboard.sqlserver.mappers.*;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.*;
|
||||||
|
|
||||||
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.comm.CommunicationException;
|
||||||
|
|
||||||
|
import meerkat.comm.MessageOutputStream;
|
||||||
|
import meerkat.crypto.concrete.ECDSASignature;
|
||||||
|
import meerkat.crypto.concrete.SHA256Digest;
|
||||||
|
|
||||||
import meerkat.protobuf.BulletinBoardAPI.*;
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
import meerkat.protobuf.Crypto.Signature;
|
import meerkat.protobuf.Crypto.Signature;
|
||||||
import meerkat.protobuf.Crypto.SignatureVerificationKey;
|
import meerkat.protobuf.Crypto.SignatureVerificationKey;
|
||||||
import meerkat.crypto.Digest;
|
|
||||||
import meerkat.crypto.concrete.SHA256Digest;
|
|
||||||
|
import static meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider.*;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import meerkat.util.BulletinBoardUtils;
|
||||||
|
import meerkat.util.TimestampComparator;
|
||||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||||
import org.springframework.jdbc.support.KeyHolder;
|
import org.springframework.jdbc.support.KeyHolder;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a generic SQL implementation of the BulletinBoardServer API.
|
* This is a generic SQL implementation of the BulletinBoardServer API.
|
||||||
*/
|
*/
|
||||||
|
@ -40,24 +50,120 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
*/
|
*/
|
||||||
public static enum QueryType {
|
public static enum QueryType {
|
||||||
|
|
||||||
FIND_MSG_ID(new String[] {"MsgId"}),
|
FIND_MSG_ID(
|
||||||
INSERT_MSG(new String[] {"MsgId","Msg"}),
|
new String[] {"MsgId"},
|
||||||
INSERT_NEW_TAG(new String[] {"Tag"}),
|
new int[] {Types.BLOB}
|
||||||
CONNECT_TAG(new String[] {"EntryNum","Tag"}),
|
),
|
||||||
ADD_SIGNATURE(new String[] {"EntryNum","SignerId","Signature"}),
|
|
||||||
GET_SIGNATURES(new String[] {"EntryNum"}),
|
FIND_TAG_ID(
|
||||||
GET_MESSAGES(new String[] {});
|
new String[] {"Tag"},
|
||||||
|
new int[] {Types.VARCHAR}
|
||||||
|
),
|
||||||
|
|
||||||
|
INSERT_MSG(
|
||||||
|
new String[] {"MsgId","TimeStamp","Msg"},
|
||||||
|
new int[] {Types.BLOB, Types.TIMESTAMP, Types.BLOB}
|
||||||
|
),
|
||||||
|
|
||||||
|
INSERT_NEW_TAG(
|
||||||
|
new String[] {"Tag"},
|
||||||
|
new int[] {Types.VARCHAR}
|
||||||
|
),
|
||||||
|
|
||||||
|
CONNECT_TAG(
|
||||||
|
new String[] {"EntryNum","Tag"},
|
||||||
|
new int[] {Types.INTEGER, Types.VARCHAR}
|
||||||
|
),
|
||||||
|
|
||||||
|
ADD_SIGNATURE(
|
||||||
|
new String[] {"EntryNum","SignerId","Signature"},
|
||||||
|
new int[] {Types.INTEGER, Types.BLOB, Types.BLOB}
|
||||||
|
),
|
||||||
|
|
||||||
|
GET_SIGNATURES(
|
||||||
|
new String[] {"EntryNum"},
|
||||||
|
new int[] {Types.INTEGER}
|
||||||
|
),
|
||||||
|
|
||||||
|
GET_MESSAGES(
|
||||||
|
new String[] {},
|
||||||
|
new int[] {}
|
||||||
|
),
|
||||||
|
|
||||||
|
COUNT_MESSAGES(
|
||||||
|
new String[] {},
|
||||||
|
new int[] {}
|
||||||
|
),
|
||||||
|
|
||||||
|
GET_MESSAGE_STUBS(
|
||||||
|
new String[] {},
|
||||||
|
new int[] {}
|
||||||
|
),
|
||||||
|
|
||||||
|
GET_LAST_MESSAGE_ENTRY(
|
||||||
|
new String[] {},
|
||||||
|
new int[] {}
|
||||||
|
),
|
||||||
|
|
||||||
|
GET_BATCH_MESSAGE_ENTRY(
|
||||||
|
new String[] {"SignerId", "BatchId"},
|
||||||
|
new int[] {Types.BLOB, Types.INTEGER}
|
||||||
|
),
|
||||||
|
|
||||||
|
CHECK_BATCH_LENGTH(
|
||||||
|
new String[] {"SignerId", "BatchId"},
|
||||||
|
new int[] {Types.BLOB, Types.INTEGER}
|
||||||
|
),
|
||||||
|
|
||||||
|
GET_BATCH_MESSAGE_DATA(
|
||||||
|
new String[] {"SignerId", "BatchId", "StartPosition"},
|
||||||
|
new int[] {Types.BLOB, Types.INTEGER, Types.INTEGER}
|
||||||
|
),
|
||||||
|
|
||||||
|
INSERT_BATCH_DATA(
|
||||||
|
new String[] {"SignerId", "BatchId", "SerialNum", "Data"},
|
||||||
|
new int[] {Types.BLOB, Types.INTEGER, Types.INTEGER, Types.BLOB}
|
||||||
|
),
|
||||||
|
|
||||||
|
CONNECT_BATCH_TAG(
|
||||||
|
new String[] {"SignerId", "BatchId", "Tag"},
|
||||||
|
new int[] {Types.BLOB, Types.INTEGER, Types.VARCHAR}
|
||||||
|
),
|
||||||
|
|
||||||
|
GET_BATCH_TAGS(
|
||||||
|
new String[] {"SignerId", "BatchId"},
|
||||||
|
new int[] {Types.BLOB, Types.INTEGER}
|
||||||
|
),
|
||||||
|
|
||||||
|
REMOVE_BATCH_TAGS(
|
||||||
|
new String[] {"SignerId", "BatchId"},
|
||||||
|
new int[] {Types.BLOB, Types.INTEGER}
|
||||||
|
);
|
||||||
|
|
||||||
private String[] paramNames;
|
private String[] paramNames;
|
||||||
|
private int[] paramTypes;
|
||||||
|
|
||||||
private QueryType(String[] paramNames) {
|
private QueryType(String[] paramNames, int[] paramTypes) {
|
||||||
this.paramNames = paramNames;
|
this.paramNames = paramNames;
|
||||||
|
this.paramTypes = paramTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getParamNames() {
|
public String[] getParamNames() {
|
||||||
return paramNames;
|
return paramNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getParamName(int num) {
|
||||||
|
return paramNames[num];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getParamTypes() {
|
||||||
|
return paramTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getParamType(int num) {
|
||||||
|
return paramTypes[num];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,7 +175,8 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
MSG_ID("MsgId", Types.BLOB),
|
MSG_ID("MsgId", Types.BLOB),
|
||||||
SIGNER_ID("SignerId", Types.BLOB),
|
SIGNER_ID("SignerId", Types.BLOB),
|
||||||
TAG("Tag", Types.VARCHAR),
|
TAG("Tag", Types.VARCHAR),
|
||||||
LIMIT("Limit", Types.INTEGER);
|
LIMIT("Limit", Types.INTEGER),
|
||||||
|
TIMESTAMP("TimeStamp", Types.TIMESTAMP);
|
||||||
|
|
||||||
private FilterTypeParam(String paramName, int paramType) {
|
private FilterTypeParam(String paramName, int paramType) {
|
||||||
this.paramName = paramName;
|
this.paramName = paramName;
|
||||||
|
@ -85,8 +192,9 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
case MSG_ID:
|
case MSG_ID:
|
||||||
return MSG_ID;
|
return MSG_ID;
|
||||||
|
|
||||||
case EXACT_ENTRY: // Go through
|
case EXACT_ENTRY: // Go through
|
||||||
case MAX_ENTRY:
|
case MAX_ENTRY: // Go through
|
||||||
|
case MIN_ENTRY:
|
||||||
return ENTRY_NUM;
|
return ENTRY_NUM;
|
||||||
|
|
||||||
case SIGNER_ID:
|
case SIGNER_ID:
|
||||||
|
@ -98,6 +206,10 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
case MAX_MESSAGES:
|
case MAX_MESSAGES:
|
||||||
return LIMIT;
|
return LIMIT;
|
||||||
|
|
||||||
|
case BEFORE_TIME: // Go through
|
||||||
|
case AFTER_TIME:
|
||||||
|
return TIMESTAMP;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -152,16 +264,22 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the value of the parameter specified in a message filter
|
||||||
|
* @param messageFilter is the filter
|
||||||
|
* @return the object parameter for the SQL query embedded in the filter (this depends on the filter type)
|
||||||
|
*/
|
||||||
private Object getParam(MessageFilter messageFilter) {
|
private Object getParam(MessageFilter messageFilter) {
|
||||||
|
|
||||||
switch (messageFilter.getType()) {
|
switch (messageFilter.getType()) {
|
||||||
|
|
||||||
case MSG_ID: // Go through
|
case MSG_ID: // Go through
|
||||||
case SIGNER_ID:
|
case SIGNER_ID:
|
||||||
return messageFilter.getId().toByteArray();
|
return messageFilter.getId().toByteArray();
|
||||||
|
|
||||||
case EXACT_ENTRY: // Go through
|
case EXACT_ENTRY: // Go through
|
||||||
case MAX_ENTRY:
|
case MAX_ENTRY: // Go through
|
||||||
|
case MIN_ENTRY:
|
||||||
return messageFilter.getEntry();
|
return messageFilter.getEntry();
|
||||||
|
|
||||||
case TAG:
|
case TAG:
|
||||||
|
@ -170,7 +288,11 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
case MAX_MESSAGES:
|
case MAX_MESSAGES:
|
||||||
return messageFilter.getMaxMessages();
|
return messageFilter.getMaxMessages();
|
||||||
|
|
||||||
default:
|
case BEFORE_TIME: // Go through
|
||||||
|
case AFTER_TIME:
|
||||||
|
return BulletinBoardUtils.toSQLTimestamp(messageFilter.getTimestamp());
|
||||||
|
|
||||||
|
default: // Unsupported filter type
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +315,8 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
|
|
||||||
protected NamedParameterJdbcTemplate jdbcTemplate;
|
protected NamedParameterJdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
protected Digest digest;
|
protected BatchDigest digest;
|
||||||
|
protected BatchDigitalSignature signer;
|
||||||
|
|
||||||
protected List<SignatureVerificationKey> trusteeSignatureVerificationArray;
|
protected List<SignatureVerificationKey> trusteeSignatureVerificationArray;
|
||||||
protected int minTrusteeSignatures;
|
protected int minTrusteeSignatures;
|
||||||
|
@ -216,8 +339,6 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
*/
|
*/
|
||||||
private void createSchema() throws SQLException {
|
private void createSchema() throws SQLException {
|
||||||
|
|
||||||
final int TIMEOUT = 20;
|
|
||||||
|
|
||||||
for (String command : sqlQueryProvider.getSchemaCreationCommands()) {
|
for (String command : sqlQueryProvider.getSchemaCreationCommands()) {
|
||||||
jdbcTemplate.update(command,(Map) null);
|
jdbcTemplate.update(command,(Map) null);
|
||||||
}
|
}
|
||||||
|
@ -231,7 +352,8 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
public void init(String meerkatDB) throws CommunicationException {
|
public void init(String meerkatDB) throws CommunicationException {
|
||||||
// TODO write signature reading part.
|
// TODO write signature reading part.
|
||||||
|
|
||||||
digest = new SHA256Digest();
|
digest = new GenericBatchDigest(new SHA256Digest());
|
||||||
|
signer = new GenericBatchDigitalSignature(new ECDSASignature());
|
||||||
|
|
||||||
jdbcTemplate = new NamedParameterJdbcTemplate(sqlQueryProvider.getDataSource());
|
jdbcTemplate = new NamedParameterJdbcTemplate(sqlQueryProvider.getDataSource());
|
||||||
|
|
||||||
|
@ -264,12 +386,12 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
|
|
||||||
String sql;
|
String sql;
|
||||||
|
|
||||||
sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.INSERT_NEW_TAG);
|
sql = sqlQueryProvider.getSQLString(QueryType.INSERT_NEW_TAG);
|
||||||
Map namedParameters[] = new HashMap[tags.length];
|
Map namedParameters[] = new HashMap[tags.length];
|
||||||
|
|
||||||
for (int i = 0 ; i < tags.length ; i++){
|
for (int i = 0 ; i < tags.length ; i++){
|
||||||
namedParameters[i] = new HashMap();
|
namedParameters[i] = new HashMap();
|
||||||
namedParameters[i].put("Tag", tags[i]);
|
namedParameters[i].put(QueryType.INSERT_NEW_TAG.getParamName(0), tags[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
jdbcTemplate.batchUpdate(sql, namedParameters);
|
jdbcTemplate.batchUpdate(sql, namedParameters);
|
||||||
|
@ -287,10 +409,17 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
/**
|
||||||
|
* This method posts a messages to the server
|
||||||
if (!verifyMessage(msg)) {
|
* @param msg is the message to post
|
||||||
|
* @param checkSignature decides whether ot not the method should check the signature before it posts the message
|
||||||
|
* @return TRUE if the post is successful and FALSE otherwise
|
||||||
|
* @throws CommunicationException
|
||||||
|
*/
|
||||||
|
public BoolMsg postMessage(BulletinBoardMessage msg, boolean checkSignature) throws CommunicationException{
|
||||||
|
|
||||||
|
if (checkSignature && !verifyMessage(msg)) {
|
||||||
return boolToBoolMsg(false);
|
return boolToBoolMsg(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,28 +428,28 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
|
|
||||||
byte[] msgID;
|
byte[] msgID;
|
||||||
long entryNum;
|
long entryNum;
|
||||||
|
|
||||||
ProtocolStringList tagList;
|
ProtocolStringList tagList;
|
||||||
String[] tags;
|
String[] tags;
|
||||||
|
|
||||||
List<Signature> signatureList;
|
List<Signature> signatureList;
|
||||||
Signature[] signatures;
|
Signature[] signatures;
|
||||||
|
|
||||||
// Calculate message ID (depending only on the the unsigned message)
|
// Calculate message ID (depending only on the the unsigned message)
|
||||||
|
|
||||||
digest.reset();
|
digest.reset();
|
||||||
digest.update(msg.getMsg());
|
digest.update(msg.getMsg());
|
||||||
|
|
||||||
msgID = digest.digest();
|
msgID = digest.digest();
|
||||||
|
|
||||||
// Add message to table if needed and store entry number of message.
|
// Add message to table if needed and store entry number of message.
|
||||||
|
|
||||||
|
sql = sqlQueryProvider.getSQLString(QueryType.FIND_MSG_ID);
|
||||||
sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.FIND_MSG_ID);
|
|
||||||
Map namedParameters = new HashMap();
|
Map namedParameters = new HashMap();
|
||||||
namedParameters.put("MsgId",msgID);
|
|
||||||
|
|
||||||
List<Long> entryNums = jdbcTemplate.query(sql, new MapSqlParameterSource(namedParameters), new EntryNumMapper());
|
namedParameters.put(QueryType.FIND_MSG_ID.getParamName(0),msgID);
|
||||||
|
|
||||||
|
List<Long> entryNums = jdbcTemplate.query(sql, namedParameters, new LongMapper());
|
||||||
|
|
||||||
if (entryNums.size() > 0){
|
if (entryNums.size() > 0){
|
||||||
|
|
||||||
|
@ -328,99 +457,106 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
|
|
||||||
} else{
|
} else{
|
||||||
|
|
||||||
sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.INSERT_MSG);
|
sql = sqlQueryProvider.getSQLString(QueryType.INSERT_MSG);
|
||||||
namedParameters.put("Msg", msg.getMsg().toByteArray());
|
|
||||||
|
namedParameters.put(QueryType.INSERT_MSG.getParamName(1), BulletinBoardUtils.toSQLTimestamp(msg.getMsg().getTimestamp()));
|
||||||
|
namedParameters.put(QueryType.INSERT_MSG.getParamName(2), msg.getMsg().toByteArray());
|
||||||
|
|
||||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||||
jdbcTemplate.update(sql,new MapSqlParameterSource(namedParameters),keyHolder);
|
jdbcTemplate.update(sql, new MapSqlParameterSource(namedParameters), keyHolder);
|
||||||
|
|
||||||
entryNum = keyHolder.getKey().longValue();
|
entryNum = keyHolder.getKey().longValue();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve tags and store new ones in tag table.
|
// Retrieve tags and store new ones in tag table.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
tagList = msg.getMsg().getTagList();
|
tagList = msg.getMsg().getTagList();
|
||||||
tags = new String[tagList.size()];
|
tags = new String[tagList.size()];
|
||||||
tags = tagList.toArray(tags);
|
tags = tagList.toArray(tags);
|
||||||
|
|
||||||
insertNewTags(tags);
|
insertNewTags(tags);
|
||||||
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new CommunicationException(e.getMessage());
|
throw new CommunicationException(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect message to tags.
|
// Connect message to tags.
|
||||||
|
|
||||||
sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.CONNECT_TAG);
|
sql = sqlQueryProvider.getSQLString(QueryType.CONNECT_TAG);
|
||||||
|
|
||||||
namedParameterArray = new HashMap[tags.length];
|
namedParameterArray = new HashMap[tags.length];
|
||||||
|
|
||||||
for (int i = 0 ; i < tags.length ; i++) {
|
for (int i = 0 ; i < tags.length ; i++) {
|
||||||
namedParameterArray[i] = new HashMap();
|
namedParameterArray[i] = new HashMap();
|
||||||
namedParameterArray[i].put("EntryNum", entryNum);
|
namedParameterArray[i].put(QueryType.CONNECT_TAG.getParamName(0), entryNum);
|
||||||
namedParameterArray[i].put("Tag", tags[i]);
|
namedParameterArray[i].put(QueryType.CONNECT_TAG.getParamName(1), tags[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
jdbcTemplate.batchUpdate(sql, namedParameterArray);
|
jdbcTemplate.batchUpdate(sql, namedParameterArray);
|
||||||
|
|
||||||
// Retrieve signatures.
|
// Retrieve signatures.
|
||||||
|
|
||||||
signatureList = msg.getSigList();
|
signatureList = msg.getSigList();
|
||||||
signatures = new Signature[signatureList.size()];
|
signatures = new Signature[signatureList.size()];
|
||||||
signatures = signatureList.toArray(signatures);
|
signatures = signatureList.toArray(signatures);
|
||||||
|
|
||||||
// Connect message to signatures.
|
// Connect message to signatures.
|
||||||
|
|
||||||
sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.ADD_SIGNATURE);
|
sql = sqlQueryProvider.getSQLString(QueryType.ADD_SIGNATURE);
|
||||||
|
|
||||||
namedParameterArray = new HashMap[signatures.length];
|
namedParameterArray = new HashMap[signatures.length];
|
||||||
|
|
||||||
for (int i = 0 ; i < signatures.length ; i++) {
|
for (int i = 0 ; i < signatures.length ; i++) {
|
||||||
namedParameterArray[i] = new HashMap();
|
namedParameterArray[i] = new HashMap();
|
||||||
namedParameterArray[i].put("EntryNum", entryNum);
|
namedParameterArray[i].put(QueryType.ADD_SIGNATURE.getParamName(0), entryNum);
|
||||||
namedParameterArray[i].put("SignerId", signatures[i].getSignerId().toByteArray());
|
namedParameterArray[i].put(QueryType.ADD_SIGNATURE.getParamName(1), signatures[i].getSignerId().toByteArray());
|
||||||
namedParameterArray[i].put("Signature", signatures[i].toByteArray());
|
namedParameterArray[i].put(QueryType.ADD_SIGNATURE.getParamName(2), signatures[i].toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
jdbcTemplate.batchUpdate(sql,namedParameterArray);
|
jdbcTemplate.batchUpdate(sql,namedParameterArray);
|
||||||
|
|
||||||
return boolToBoolMsg(true);
|
return boolToBoolMsg(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException {
|
public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
||||||
|
return postMessage(msg, true); // Perform a post and check the signature for authenticity
|
||||||
|
}
|
||||||
|
|
||||||
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));
|
* This is a container class for and SQL string builder and a MapSqlParameterSource to be used with it
|
||||||
|
*/
|
||||||
|
class SQLAndParameters {
|
||||||
|
|
||||||
MapSqlParameterSource namedParameters;
|
public StringBuilder sql;
|
||||||
int paramNum;
|
public MapSqlParameterSource parameters;
|
||||||
|
|
||||||
MessageMapper messageMapper = new MessageMapper();
|
public SQLAndParameters(int numOfFilters) {
|
||||||
SignatureMapper signatureMapper = new SignatureMapper();
|
sql = new StringBuilder(50 * numOfFilters);
|
||||||
|
parameters = new MapSqlParameterSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SQLAndParameters getSQLFromFilters(MessageFilterList filterList) {
|
||||||
|
|
||||||
|
SQLAndParameters result = new SQLAndParameters(filterList.getFilterCount());
|
||||||
|
|
||||||
List<MessageFilter> filters = new ArrayList<MessageFilter>(filterList.getFilterList());
|
List<MessageFilter> filters = new ArrayList<MessageFilter>(filterList.getFilterList());
|
||||||
|
|
||||||
boolean isFirstFilter = true;
|
|
||||||
|
|
||||||
Collections.sort(filters, new FilterTypeComparator());
|
Collections.sort(filters, new FilterTypeComparator());
|
||||||
|
|
||||||
// Check if Tag/Signature tables are required for filtering purposes
|
boolean isFirstFilter = true;
|
||||||
|
|
||||||
sqlBuilder.append(sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.GET_MESSAGES));
|
|
||||||
// Add conditions
|
|
||||||
|
|
||||||
namedParameters = new MapSqlParameterSource();
|
|
||||||
|
|
||||||
if (!filters.isEmpty()) {
|
if (!filters.isEmpty()) {
|
||||||
sqlBuilder.append(" WHERE ");
|
result.sql.append(" WHERE ");
|
||||||
|
|
||||||
for (paramNum = 0 ; paramNum < filters.size() ; paramNum++) {
|
for (int paramNum = 0 ; paramNum < filters.size() ; paramNum++) {
|
||||||
|
|
||||||
MessageFilter filter = filters.get(paramNum);
|
MessageFilter filter = filters.get(paramNum);
|
||||||
|
|
||||||
|
@ -428,15 +564,15 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
if (isFirstFilter) {
|
if (isFirstFilter) {
|
||||||
isFirstFilter = false;
|
isFirstFilter = false;
|
||||||
} else {
|
} else {
|
||||||
sqlBuilder.append(" AND ");
|
result.sql.append(" AND ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlBuilder.append(sqlQueryProvider.getCondition(filter.getType(), paramNum));
|
result.sql.append(sqlQueryProvider.getCondition(filter.getType(), paramNum));
|
||||||
|
|
||||||
SQLQueryProvider.FilterTypeParam filterTypeParam = SQLQueryProvider.FilterTypeParam.getFilterTypeParamName(filter.getType());
|
FilterTypeParam filterTypeParam = FilterTypeParam.getFilterTypeParamName(filter.getType());
|
||||||
|
|
||||||
namedParameters.addValue(
|
result.parameters.addValue(
|
||||||
filterTypeParam.getParamName() + Integer.toString(paramNum),
|
filterTypeParam.getParamName() + Integer.toString(paramNum),
|
||||||
getParam(filter),
|
getParam(filter),
|
||||||
filterTypeParam.getParamType(),
|
filterTypeParam.getParamType(),
|
||||||
|
@ -446,35 +582,381 @@ public class BulletinBoardSQLServer implements BulletinBoardServer{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to retrieve just basic information about messages to allow calculation of checksum
|
||||||
|
* @param filterList is a filter list that defines which messages the client is interested in
|
||||||
|
* @return a list of Bulletin Board Messages that contain just the entry number, timestamp and message ID for each message
|
||||||
|
* The message ID is returned inside the message data field
|
||||||
|
*/
|
||||||
|
protected List<BulletinBoardMessage> readMessageStubs(MessageFilterList filterList) {
|
||||||
|
|
||||||
|
StringBuilder sqlBuilder = new StringBuilder(50 * (filterList.getFilterCount() + 1));
|
||||||
|
|
||||||
|
sqlBuilder.append(sqlQueryProvider.getSQLString(QueryType.GET_MESSAGE_STUBS));
|
||||||
|
|
||||||
|
// Get Conditions
|
||||||
|
|
||||||
|
SQLAndParameters sqlAndParameters = getSQLFromFilters(filterList);
|
||||||
|
|
||||||
|
sqlBuilder.append(sqlAndParameters.sql);
|
||||||
|
|
||||||
// Run query
|
// Run query
|
||||||
|
|
||||||
List<BulletinBoardMessage.Builder> msgBuilders = jdbcTemplate.query(sqlBuilder.toString(), namedParameters, messageMapper);
|
return jdbcTemplate.query(sqlBuilder.toString(), sqlAndParameters.parameters, new MessageStubMapper());
|
||||||
|
|
||||||
// Compile list of messages
|
}
|
||||||
|
|
||||||
for (BulletinBoardMessage.Builder msgBuilder : msgBuilders) {
|
|
||||||
|
|
||||||
// Retrieve signatures
|
@Override
|
||||||
|
public void readMessages(MessageFilterList filterList, MessageOutputStream<BulletinBoardMessage> out) throws CommunicationException {
|
||||||
|
|
||||||
namedParameters = new MapSqlParameterSource();
|
BulletinBoardMessageList.Builder resultListBuilder = BulletinBoardMessageList.newBuilder();
|
||||||
namedParameters.addValue("EntryNum", msgBuilder.getEntryNum());
|
|
||||||
|
|
||||||
List<Signature> signatures = jdbcTemplate.query(
|
// SQL length is roughly 50 characters per filter + 50 for the query itself
|
||||||
sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.GET_SIGNATURES),
|
StringBuilder sqlBuilder = new StringBuilder(50 * (filterList.getFilterCount() + 1));
|
||||||
namedParameters,
|
|
||||||
signatureMapper);
|
|
||||||
|
|
||||||
// Append signatures
|
// Check if Tag/Signature tables are required for filtering purposes
|
||||||
msgBuilder.addAllSig(signatures);
|
|
||||||
|
|
||||||
// Finalize message and add to message list.
|
sqlBuilder.append(sqlQueryProvider.getSQLString(QueryType.GET_MESSAGES));
|
||||||
|
|
||||||
resultListBuilder.addMessage(msgBuilder.build());
|
// Get conditions
|
||||||
|
|
||||||
|
SQLAndParameters sqlAndParameters = getSQLFromFilters(filterList);
|
||||||
|
sqlBuilder.append(sqlAndParameters.sql);
|
||||||
|
|
||||||
|
// Run query and stream the output using a MessageCallbackHandler
|
||||||
|
|
||||||
|
jdbcTemplate.query(sqlBuilder.toString(), sqlAndParameters.parameters, new MessageCallbackHandler(jdbcTemplate, sqlQueryProvider, out));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns a string representation of the tag associated with a batch ID
|
||||||
|
* @param batchId is the given batch ID
|
||||||
|
* @return the String representation of the tag
|
||||||
|
*/
|
||||||
|
private String batchIdToTag(int batchId) {
|
||||||
|
return BATCH_ID_TAG_PREFIX + Integer.toString(batchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if a specified batch exists and is already closed
|
||||||
|
* @param signerId is the ID of the publisher of the batch
|
||||||
|
* @param batchId is the unique (per signer) batch ID
|
||||||
|
* @return TRUE if the batch is closed and FALSE if it is still open or doesn't exist at all
|
||||||
|
*/
|
||||||
|
private boolean isBatchClosed(ByteString signerId, int batchId) throws CommunicationException {
|
||||||
|
|
||||||
|
MessageFilterList filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.SIGNER_ID)
|
||||||
|
.setId(signerId)
|
||||||
|
.build())
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.TAG)
|
||||||
|
.setTag(BATCH_TAG)
|
||||||
|
.build())
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.TAG)
|
||||||
|
.setTag(batchIdToTag(batchId))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// SQL length is roughly 50 characters per filter + 50 for the query itself
|
||||||
|
StringBuilder sqlBuilder = new StringBuilder(50 * (filterList.getFilterCount() + 1));
|
||||||
|
|
||||||
|
// Check if Tag/Signature tables are required for filtering purposes
|
||||||
|
|
||||||
|
sqlBuilder.append(sqlQueryProvider.getSQLString(QueryType.COUNT_MESSAGES));
|
||||||
|
|
||||||
|
// Get conditions
|
||||||
|
|
||||||
|
SQLAndParameters sqlAndParameters = getSQLFromFilters(filterList);
|
||||||
|
sqlBuilder.append(sqlAndParameters.sql);
|
||||||
|
|
||||||
|
// Run query and stream the output using a MessageCallbackHandler
|
||||||
|
|
||||||
|
List<Long> count = jdbcTemplate.query(sqlBuilder.toString(), sqlAndParameters.parameters, new LongMapper());
|
||||||
|
|
||||||
|
return (count.size() > 0) && (count.get(0) > 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BoolMsg beginBatch(BeginBatchMessage message) throws CommunicationException {
|
||||||
|
|
||||||
|
// Check if batch is closed
|
||||||
|
if (isBatchClosed(message.getSignerId(), message.getBatchId())) {
|
||||||
|
return BoolMsg.newBuilder().setValue(false).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new tags to table
|
||||||
|
ProtocolStringList tagList = message.getTagList();
|
||||||
|
String[] tags = new String[tagList.size()];
|
||||||
|
tags = tagList.toArray(tags);
|
||||||
|
try {
|
||||||
|
insertNewTags(tags);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new CommunicationException(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect tags
|
||||||
|
String sql = sqlQueryProvider.getSQLString(QueryType.CONNECT_BATCH_TAG);
|
||||||
|
MapSqlParameterSource namedParameters[] = new MapSqlParameterSource[tags.length];
|
||||||
|
|
||||||
|
for (int i=0 ; i < tags.length ; i++) {
|
||||||
|
namedParameters[i] = new MapSqlParameterSource();
|
||||||
|
namedParameters[i].addValue(QueryType.CONNECT_BATCH_TAG.getParamName(0),message.getSignerId().toByteArray());
|
||||||
|
namedParameters[i].addValue(QueryType.CONNECT_BATCH_TAG.getParamName(1),message.getBatchId());
|
||||||
|
namedParameters[i].addValue(QueryType.CONNECT_BATCH_TAG.getParamName(2),tags[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
jdbcTemplate.batchUpdate(sql,namedParameters);
|
||||||
|
|
||||||
|
return BoolMsg.newBuilder().setValue(true).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BoolMsg postBatchMessage(BatchMessage batchMessage) throws CommunicationException{
|
||||||
|
|
||||||
|
// Check if batch is closed
|
||||||
|
if (isBatchClosed(batchMessage.getSignerId(), batchMessage.getBatchId())) {
|
||||||
|
return BoolMsg.newBuilder().setValue(false).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add data
|
||||||
|
String sql = sqlQueryProvider.getSQLString(QueryType.INSERT_BATCH_DATA);
|
||||||
|
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
|
||||||
|
|
||||||
|
namedParameters.addValue(QueryType.INSERT_BATCH_DATA.getParamName(0),batchMessage.getSignerId().toByteArray());
|
||||||
|
namedParameters.addValue(QueryType.INSERT_BATCH_DATA.getParamName(1),batchMessage.getBatchId());
|
||||||
|
namedParameters.addValue(QueryType.INSERT_BATCH_DATA.getParamName(2),batchMessage.getSerialNum());
|
||||||
|
namedParameters.addValue(QueryType.INSERT_BATCH_DATA.getParamName(3),batchMessage.getData().toByteArray());
|
||||||
|
|
||||||
|
jdbcTemplate.update(sql, namedParameters);
|
||||||
|
|
||||||
|
return BoolMsg.newBuilder().setValue(true).build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BoolMsg closeBatchMessage(CloseBatchMessage message) throws CommunicationException {
|
||||||
|
|
||||||
|
ByteString signerId = message.getSig().getSignerId();
|
||||||
|
int batchId = message.getBatchId();
|
||||||
|
|
||||||
|
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||||
|
|
||||||
|
// Check batch size
|
||||||
|
|
||||||
|
String sql = sqlQueryProvider.getSQLString(QueryType.CHECK_BATCH_LENGTH);
|
||||||
|
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
|
||||||
|
|
||||||
|
|
||||||
|
namedParameters.addValue(QueryType.CHECK_BATCH_LENGTH.getParamName(0),signerId.toByteArray());
|
||||||
|
namedParameters.addValue(QueryType.CHECK_BATCH_LENGTH.getParamName(1),batchId);
|
||||||
|
|
||||||
|
List<Long> lengthResult = jdbcTemplate.query(sql, namedParameters, new LongMapper());
|
||||||
|
|
||||||
|
if (lengthResult.get(0) != message.getBatchLength()) {
|
||||||
|
return BoolMsg.newBuilder().setValue(false).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Get Tags and add them to CompleteBatch
|
||||||
|
|
||||||
|
sql = sqlQueryProvider.getSQLString(QueryType.GET_BATCH_TAGS);
|
||||||
|
namedParameters = new MapSqlParameterSource();
|
||||||
|
|
||||||
|
namedParameters.addValue(QueryType.GET_BATCH_TAGS.getParamName(0),signerId);
|
||||||
|
namedParameters.addValue(QueryType.GET_BATCH_TAGS.getParamName(1),batchId);
|
||||||
|
|
||||||
|
List<String> tags = jdbcTemplate.query(sql, namedParameters, new StringMapper());
|
||||||
|
|
||||||
|
CompleteBatch completeBatch = new CompleteBatch(
|
||||||
|
BeginBatchMessage.newBuilder()
|
||||||
|
.setSignerId(signerId)
|
||||||
|
.setBatchId(batchId)
|
||||||
|
.addAllTag(tags)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add timestamp to CompleteBatch
|
||||||
|
completeBatch.setTimestamp(message.getTimestamp());
|
||||||
|
|
||||||
|
// Add actual batch data to CompleteBatch
|
||||||
|
|
||||||
|
sql = sqlQueryProvider.getSQLString(QueryType.GET_BATCH_MESSAGE_DATA);
|
||||||
|
namedParameters = new MapSqlParameterSource();
|
||||||
|
|
||||||
|
namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0),signerId.toByteArray());
|
||||||
|
namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1),batchId);
|
||||||
|
namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2),0); // Read from the beginning
|
||||||
|
|
||||||
|
completeBatch.appendBatchData(jdbcTemplate.query(sql, namedParameters, new BatchDataMapper()));
|
||||||
|
|
||||||
|
// Verify signature
|
||||||
|
|
||||||
|
completeBatch.setSignature(message.getSig());
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// TODO: Actual verification
|
||||||
|
// //signer.verify(completeBatch);
|
||||||
|
// } catch (CertificateException | InvalidKeyException | SignatureException e) {
|
||||||
|
// return BoolMsg.newBuilder().setValue(false).build();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Batch verified: finalize it
|
||||||
|
|
||||||
|
// Calculate message ID
|
||||||
|
digest.reset();
|
||||||
|
digest.update(completeBatch);
|
||||||
|
MessageID msgID = MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build();
|
||||||
|
|
||||||
|
// Create Bulletin Board message
|
||||||
|
BulletinBoardMessage bulletinBoardMessage = BulletinBoardMessage.newBuilder()
|
||||||
|
.addSig(message.getSig())
|
||||||
|
.setMsg(UnsignedBulletinBoardMessage.newBuilder()
|
||||||
|
.addAllTag(tags)
|
||||||
|
.addTag(BATCH_TAG)
|
||||||
|
.addTag(batchIdToTag(batchId))
|
||||||
|
.setData(message.getSig().getSignerId())
|
||||||
|
.setTimestamp(message.getTimestamp())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Post message without checking signature validity
|
||||||
|
postMessage(bulletinBoardMessage, false);
|
||||||
|
|
||||||
|
// Remove tags from temporary table
|
||||||
|
sql = sqlQueryProvider.getSQLString(QueryType.REMOVE_BATCH_TAGS);
|
||||||
|
namedParameters = new MapSqlParameterSource();
|
||||||
|
|
||||||
|
namedParameters.addValue(QueryType.REMOVE_BATCH_TAGS.getParamName(0), signerId.toByteArray());
|
||||||
|
namedParameters.addValue(QueryType.REMOVE_BATCH_TAGS.getParamName(1), batchId);
|
||||||
|
|
||||||
|
jdbcTemplate.update(sql, namedParameters);
|
||||||
|
|
||||||
|
// Return TRUE
|
||||||
|
|
||||||
|
return BoolMsg.newBuilder().setValue(true).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBatch(BatchSpecificationMessage message, MessageOutputStream<BatchData> out) throws CommunicationException, IllegalArgumentException{
|
||||||
|
|
||||||
|
// Check that batch is closed
|
||||||
|
if (!isBatchClosed(message.getSignerId(), message.getBatchId())) {
|
||||||
|
throw new IllegalArgumentException("No such batch");
|
||||||
|
}
|
||||||
|
|
||||||
|
String sql = sqlQueryProvider.getSQLString(QueryType.GET_BATCH_MESSAGE_DATA);
|
||||||
|
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
|
||||||
|
|
||||||
|
namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0),message.getSignerId().toByteArray());
|
||||||
|
namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1),message.getBatchId());
|
||||||
|
namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2),message.getStartPosition());
|
||||||
|
|
||||||
|
jdbcTemplate.query(sql, namedParameters, new BatchDataCallbackHandler(out));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the entry number of the last entry in the database
|
||||||
|
* @return the entry number, or -1 if no entries are found
|
||||||
|
*/
|
||||||
|
protected long getLastMessageEntry() {
|
||||||
|
|
||||||
|
String sql = sqlQueryProvider.getSQLString(QueryType.GET_LAST_MESSAGE_ENTRY);
|
||||||
|
|
||||||
|
List<Long> resultList = jdbcTemplate.query(sql, new LongMapper());
|
||||||
|
|
||||||
|
if (resultList.size() <= 0){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultList.get(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for the latest time of sync of the DB relative to a given query and returns the metadata needed to complete the sync
|
||||||
|
* The checksum up to (and including) each given timestamp is calculated using bitwise XOR on 8-byte sized blocks of the message IDs
|
||||||
|
* @param syncQuery contains a succinct representation of states to compare to
|
||||||
|
* @return the current last entry num and latest time of sync if there is one; -1 as last entry and empty timestamp otherwise
|
||||||
|
* @throws CommunicationException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SyncQueryResponse querySync(SyncQuery syncQuery) throws CommunicationException {
|
||||||
|
|
||||||
|
if (syncQuery == null){
|
||||||
|
return SyncQueryResponse.newBuilder()
|
||||||
|
.setLastEntryNum(-1)
|
||||||
|
.setLastTimeOfSync(com.google.protobuf.Timestamp.getDefaultInstance())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
com.google.protobuf.Timestamp lastTimeOfSync = null;
|
||||||
|
|
||||||
|
TimestampComparator timestampComparator = new TimestampComparator();
|
||||||
|
|
||||||
|
long lastEntryNum = getLastMessageEntry();
|
||||||
|
|
||||||
|
Iterator<SingleSyncQuery> queryIterator = syncQuery.getQueryList().iterator();
|
||||||
|
|
||||||
|
SingleSyncQuery currentQuery = queryIterator.next();
|
||||||
|
|
||||||
|
List<BulletinBoardMessage> messageStubs = readMessageStubs(syncQuery.getFilterList());
|
||||||
|
|
||||||
|
Checksum checksum = new SimpleChecksum();
|
||||||
|
|
||||||
|
for (BulletinBoardMessage message : messageStubs){
|
||||||
|
|
||||||
|
// Check for end of current query
|
||||||
|
if (timestampComparator.compare(message.getMsg().getTimestamp(), currentQuery.getTimeOfSync()) > 0){
|
||||||
|
|
||||||
|
if (checksum.getChecksum() == currentQuery.getChecksum()){
|
||||||
|
lastTimeOfSync = currentQuery.getTimeOfSync();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryIterator.hasNext()){
|
||||||
|
currentQuery = queryIterator.next();
|
||||||
|
} else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance checksum
|
||||||
|
|
||||||
|
ByteString messageID = message.getMsg().getData(); // The data field contains the message ID
|
||||||
|
|
||||||
|
checksum.update(messageID);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Combine results and return.
|
if (checksum.getChecksum() == currentQuery.getChecksum()){
|
||||||
return resultListBuilder.build();
|
lastTimeOfSync = currentQuery.getTimeOfSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastTimeOfSync == null){
|
||||||
|
return SyncQueryResponse.newBuilder()
|
||||||
|
.setLastEntryNum(-1)
|
||||||
|
.setLastTimeOfSync(com.google.protobuf.Timestamp.getDefaultInstance())
|
||||||
|
.build();
|
||||||
|
} else{
|
||||||
|
return SyncQueryResponse.newBuilder()
|
||||||
|
.setLastEntryNum(lastEntryNum)
|
||||||
|
.setLastTimeOfSync(lastTimeOfSync)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import javax.naming.InitialContext;
|
||||||
|
|
||||||
import javax.naming.NamingException;
|
import javax.naming.NamingException;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -42,9 +43,20 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider
|
||||||
case FIND_MSG_ID:
|
case FIND_MSG_ID:
|
||||||
return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId";
|
return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId";
|
||||||
|
|
||||||
|
case FIND_TAG_ID:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT TagId FROM TagTable WHERE Tag = :{0}",
|
||||||
|
QueryType.FIND_TAG_ID.getParamName(0));
|
||||||
|
|
||||||
case GET_MESSAGES:
|
case GET_MESSAGES:
|
||||||
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable";
|
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable";
|
||||||
|
|
||||||
|
case COUNT_MESSAGES:
|
||||||
|
return "SELECT COUNT(MsgTable.EntryNum) FROM MsgTable";
|
||||||
|
|
||||||
|
case GET_MESSAGE_STUBS:
|
||||||
|
return "SELECT MsgTable.EntryNum, MsgTable.MsgId, MsgTable.ExactTime FROM MsgTable";
|
||||||
|
|
||||||
case GET_SIGNATURES:
|
case GET_SIGNATURES:
|
||||||
return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum";
|
return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum";
|
||||||
|
|
||||||
|
@ -55,6 +67,66 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider
|
||||||
return "INSERT INTO TagTable(Tag) SELECT DISTINCT :Tag AS NewTag FROM UtilityTable WHERE"
|
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)";
|
+ " NOT EXISTS (SELECT 1 FROM TagTable AS SubTable WHERE SubTable.Tag = :Tag)";
|
||||||
|
|
||||||
|
case GET_LAST_MESSAGE_ENTRY:
|
||||||
|
return "SELECT MAX(MsgTable.EntryNum) FROM MsgTable";
|
||||||
|
|
||||||
|
case GET_BATCH_MESSAGE_ENTRY:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"
|
||||||
|
+ " INNER JOIN SignatureTable ON MsgTable.EntryNum = SignatureTable.EntryNum"
|
||||||
|
+ " INNER JOIN MsgTagTable ON MsgTable.EntryNum = MsgTagTable.EntryNum"
|
||||||
|
+ " INNER JOIN TagTable ON MsgTagTable.TagId = TagTable.TagId"
|
||||||
|
+ " WHERE SignatureTable.SignerId = :{0}"
|
||||||
|
+ " AND TagTable.Tag = :{1}",
|
||||||
|
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(0),
|
||||||
|
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(1));
|
||||||
|
|
||||||
|
case GET_BATCH_MESSAGE_DATA:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT Data FROM BatchTable"
|
||||||
|
+ " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}"
|
||||||
|
+ " ORDER BY SerialNum ASC",
|
||||||
|
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0),
|
||||||
|
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1),
|
||||||
|
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2));
|
||||||
|
|
||||||
|
case INSERT_BATCH_DATA:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"INSERT INTO BatchTable (SignerId, BatchId, SerialNum, Data)"
|
||||||
|
+ " VALUES (:{0}, :{1}, :{2}, :{3})",
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(0),
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(1),
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(2),
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(3));
|
||||||
|
|
||||||
|
case CHECK_BATCH_LENGTH:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT COUNT(Data) AS BatchLength FROM BatchTable"
|
||||||
|
+ " WHERE SignerId = :{0} AND BatchId = :{1}",
|
||||||
|
QueryType.CHECK_BATCH_LENGTH.getParamName(0),
|
||||||
|
QueryType.CHECK_BATCH_LENGTH.getParamName(1));
|
||||||
|
|
||||||
|
case CONNECT_BATCH_TAG:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"INSERT INTO BatchTagTable (SignerId, BatchId, TagId) SELECT :{0}, :{1}, TagId FROM TagTable"
|
||||||
|
+ " WHERE Tag = :{2}",
|
||||||
|
QueryType.CONNECT_BATCH_TAG.getParamName(0),
|
||||||
|
QueryType.CONNECT_BATCH_TAG.getParamName(1),
|
||||||
|
QueryType.CONNECT_BATCH_TAG.getParamName(2));
|
||||||
|
|
||||||
|
case GET_BATCH_TAGS:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT Tag FROM TagTable INNER JOIN BatchTagTable ON TagTable.TagId = BatchTagTable.TagId"
|
||||||
|
+ " WHERE SignerId = :{0} AND BatchId = :{1} ORDER BY Tag ASC",
|
||||||
|
QueryType.GET_BATCH_TAGS.getParamName(0),
|
||||||
|
QueryType.GET_BATCH_TAGS.getParamName(1));
|
||||||
|
|
||||||
|
case REMOVE_BATCH_TAGS:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"DELETE FROM BatchTagTable WHERE SignerId = :{0} AND BatchId = :{1}",
|
||||||
|
QueryType.REMOVE_BATCH_TAGS.getParamName(0),
|
||||||
|
QueryType.REMOVE_BATCH_TAGS.getParamName(1));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Cannot serve a query of type " + queryType);
|
throw new IllegalArgumentException("Cannot serve a query of type " + queryType);
|
||||||
}
|
}
|
||||||
|
@ -71,10 +143,12 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider
|
||||||
return "MsgTable.EntryNum = :EntryNum" + serialString;
|
return "MsgTable.EntryNum = :EntryNum" + serialString;
|
||||||
case MAX_ENTRY:
|
case MAX_ENTRY:
|
||||||
return "MsgTable.EntryNum <= :EntryNum" + serialString;
|
return "MsgTable.EntryNum <= :EntryNum" + serialString;
|
||||||
|
case MIN_ENTRY:
|
||||||
|
return "MsgTable.EntryNum >= :EntryNum" + serialString;
|
||||||
case MAX_MESSAGES:
|
case MAX_MESSAGES:
|
||||||
return "LIMIT :Limit" + serialString;
|
return "LIMIT :Limit" + serialString;
|
||||||
case MSG_ID:
|
case MSG_ID:
|
||||||
return "MsgTable.MsgId = MsgId" + serialString;
|
return "MsgTable.MsgId = :MsgId" + serialString;
|
||||||
case SIGNER_ID:
|
case SIGNER_ID:
|
||||||
return "EXISTS (SELECT 1 FROM SignatureTable"
|
return "EXISTS (SELECT 1 FROM SignatureTable"
|
||||||
+ " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)";
|
+ " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)";
|
||||||
|
@ -82,6 +156,13 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider
|
||||||
return "EXISTS (SELECT 1 FROM TagTable"
|
return "EXISTS (SELECT 1 FROM TagTable"
|
||||||
+ " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId"
|
+ " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId"
|
||||||
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)";
|
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)";
|
||||||
|
|
||||||
|
case BEFORE_TIME:
|
||||||
|
return "MsgTable.ExactTime <= :TimeStamp";
|
||||||
|
|
||||||
|
case AFTER_TIME:
|
||||||
|
return "MsgTable.ExactTime >= :TimeStamp";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
|
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
|
||||||
}
|
}
|
||||||
|
@ -94,6 +175,7 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider
|
||||||
switch(filterType) {
|
switch(filterType) {
|
||||||
case EXACT_ENTRY: // Go through
|
case EXACT_ENTRY: // Go through
|
||||||
case MAX_ENTRY: // Go through
|
case MAX_ENTRY: // Go through
|
||||||
|
case MIN_ENTRY: // Go through
|
||||||
case MAX_MESSAGES:
|
case MAX_MESSAGES:
|
||||||
return "INT";
|
return "INT";
|
||||||
|
|
||||||
|
@ -124,7 +206,7 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider
|
||||||
public List<String> getSchemaCreationCommands() {
|
public List<String> getSchemaCreationCommands() {
|
||||||
List<String> list = new LinkedList<String>();
|
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 MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY, MsgId TINYBLOB UNIQUE, ExactTime TIMESTAMP, 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 TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50) UNIQUE)");
|
||||||
|
|
||||||
|
@ -139,6 +221,14 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider
|
||||||
list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)");
|
list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)");
|
||||||
list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId, EntryNum)");
|
list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId, EntryNum)");
|
||||||
|
|
||||||
|
list.add("CREATE TABLE IF NOT EXISTS BatchTable (SignerId TINYBLOB, BatchId INT, SerialNum INT, Data BLOB,"
|
||||||
|
+ " UNIQUE(SignerId, BatchId, SerialNum))");
|
||||||
|
|
||||||
|
list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (SignerId TINYBLOB, BatchId INT, TagId INT,"
|
||||||
|
+ " FOREIGN KEY (TagId) REFERENCES TagTable(TagId))");
|
||||||
|
|
||||||
|
list.add("CREATE INDEX IF NOT EXISTS BatchIndex ON BatchTagTable(SignerId, BatchId)");
|
||||||
|
|
||||||
// This is used to create a simple table with one entry.
|
// This is used to create a simple table with one entry.
|
||||||
// It is used for implementing a workaround for the missing INSERT IGNORE syntax
|
// 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("CREATE TABLE IF NOT EXISTS UtilityTable (Entry INT)");
|
||||||
|
@ -152,6 +242,9 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider
|
||||||
List<String> list = new LinkedList<String>();
|
List<String> list = new LinkedList<String>();
|
||||||
|
|
||||||
list.add("DROP TABLE IF EXISTS UtilityTable");
|
list.add("DROP TABLE IF EXISTS UtilityTable");
|
||||||
|
list.add("DROP INDEX IF EXISTS BatchIndex");
|
||||||
|
list.add("DROP TABLE IF EXISTS BatchTagTable");
|
||||||
|
list.add("DROP TABLE IF EXISTS BatchTable");
|
||||||
list.add("DROP INDEX IF EXISTS SignerIdIndex");
|
list.add("DROP INDEX IF EXISTS SignerIdIndex");
|
||||||
list.add("DROP TABLE IF EXISTS MsgTagTable");
|
list.add("DROP TABLE IF EXISTS MsgTagTable");
|
||||||
list.add("DROP TABLE IF EXISTS SignatureTable");
|
list.add("DROP TABLE IF EXISTS SignatureTable");
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package meerkat.bulletinboard.sqlserver;
|
package meerkat.bulletinboard.sqlserver;
|
||||||
|
|
||||||
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
|
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
|
||||||
|
import meerkat.bulletinboard.BulletinBoardConstants;
|
||||||
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider;
|
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.FilterType;
|
import meerkat.protobuf.BulletinBoardAPI.FilterType;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -32,21 +34,117 @@ public class MySQLQueryProvider implements SQLQueryProvider {
|
||||||
public String getSQLString(QueryType queryType) throws IllegalArgumentException{
|
public String getSQLString(QueryType queryType) throws IllegalArgumentException{
|
||||||
|
|
||||||
switch(queryType) {
|
switch(queryType) {
|
||||||
|
|
||||||
case ADD_SIGNATURE:
|
case ADD_SIGNATURE:
|
||||||
return "INSERT IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum, :SignerId, :Signature)";
|
return MessageFormat.format(
|
||||||
|
"INSERT IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:{0}, :{1}, :{2})",
|
||||||
|
QueryType.ADD_SIGNATURE.getParamName(0),
|
||||||
|
QueryType.ADD_SIGNATURE.getParamName(1),
|
||||||
|
QueryType.ADD_SIGNATURE.getParamName(2));
|
||||||
|
|
||||||
case CONNECT_TAG:
|
case CONNECT_TAG:
|
||||||
return "INSERT IGNORE INTO MsgTagTable (TagId, EntryNum)"
|
return MessageFormat.format(
|
||||||
+ " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag";
|
"INSERT IGNORE INTO MsgTagTable (TagId, EntryNum)"
|
||||||
|
+ " SELECT TagTable.TagId, :{0} AS EntryNum FROM TagTable WHERE Tag = :{1}",
|
||||||
|
QueryType.CONNECT_TAG.getParamName(0),
|
||||||
|
QueryType.CONNECT_TAG.getParamName(1));
|
||||||
|
|
||||||
case FIND_MSG_ID:
|
case FIND_MSG_ID:
|
||||||
return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId";
|
return MessageFormat.format(
|
||||||
|
"SELECT EntryNum From MsgTable WHERE MsgId = :{0}",
|
||||||
|
QueryType.FIND_MSG_ID.getParamName(0));
|
||||||
|
|
||||||
|
case FIND_TAG_ID:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT TagId FROM TagTable WHERE Tag = :{0}",
|
||||||
|
QueryType.FIND_TAG_ID.getParamName(0));
|
||||||
|
|
||||||
case GET_MESSAGES:
|
case GET_MESSAGES:
|
||||||
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable";
|
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable";
|
||||||
|
|
||||||
|
case COUNT_MESSAGES:
|
||||||
|
return "SELECT COUNT(MsgTable.EntryNum) FROM MsgTable";
|
||||||
|
|
||||||
|
case GET_MESSAGE_STUBS:
|
||||||
|
return "SELECT MsgTable.EntryNum, MsgTable.MsgId, MsgTable.ExactTime FROM MsgTable";
|
||||||
|
|
||||||
case GET_SIGNATURES:
|
case GET_SIGNATURES:
|
||||||
return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum";
|
return MessageFormat.format(
|
||||||
|
"SELECT Signature FROM SignatureTable WHERE EntryNum = :{0}",
|
||||||
|
QueryType.GET_SIGNATURES.getParamName(0));
|
||||||
|
|
||||||
case INSERT_MSG:
|
case INSERT_MSG:
|
||||||
return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId, :Msg)";
|
return MessageFormat.format(
|
||||||
|
"INSERT INTO MsgTable (MsgId, ExactTime, Msg) VALUES(:{0}, :{1}, :{2})",
|
||||||
|
QueryType.INSERT_MSG.getParamName(0),
|
||||||
|
QueryType.INSERT_MSG.getParamName(1),
|
||||||
|
QueryType.INSERT_MSG.getParamName(2));
|
||||||
|
|
||||||
case INSERT_NEW_TAG:
|
case INSERT_NEW_TAG:
|
||||||
return "INSERT IGNORE INTO TagTable(Tag) VALUES (:Tag)";
|
return MessageFormat.format(
|
||||||
|
"INSERT IGNORE INTO TagTable(Tag) VALUES (:{0})",
|
||||||
|
QueryType.INSERT_NEW_TAG.getParamName(0));
|
||||||
|
|
||||||
|
case GET_LAST_MESSAGE_ENTRY:
|
||||||
|
return "SELECT MAX(MsgTable.EntryNum) FROM MsgTable";
|
||||||
|
|
||||||
|
case GET_BATCH_MESSAGE_ENTRY:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"
|
||||||
|
+ " INNER JOIN SignatureTable ON MsgTable.EntryNum = SignatureTable.EntryNum"
|
||||||
|
+ " INNER JOIN MsgTagTable ON MsgTable.EntryNum = MsgTagTable.EntryNum"
|
||||||
|
+ " INNER JOIN TagTable ON MsgTagTable.TagId = TagTable.TagId"
|
||||||
|
+ " WHERE SignatureTable.SignerId = :{0}"
|
||||||
|
+ " AND TagTable.Tag = :{1}",
|
||||||
|
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(0),
|
||||||
|
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(1));
|
||||||
|
|
||||||
|
case GET_BATCH_MESSAGE_DATA:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT Data FROM BatchTable"
|
||||||
|
+ " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}"
|
||||||
|
+ " ORDER BY SerialNum ASC",
|
||||||
|
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0),
|
||||||
|
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1),
|
||||||
|
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2));
|
||||||
|
|
||||||
|
case INSERT_BATCH_DATA:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"INSERT INTO BatchTable (SignerId, BatchId, SerialNum, Data)"
|
||||||
|
+ " VALUES (:{0}, :{1}, :{2}, :{3})",
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(0),
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(1),
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(2),
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(3));
|
||||||
|
|
||||||
|
case CHECK_BATCH_LENGTH:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT COUNT(Data) AS BatchLength FROM BatchTable"
|
||||||
|
+ " WHERE SignerId = :{0} AND BatchId = :{1}",
|
||||||
|
QueryType.CHECK_BATCH_LENGTH.getParamName(0),
|
||||||
|
QueryType.CHECK_BATCH_LENGTH.getParamName(1));
|
||||||
|
|
||||||
|
case CONNECT_BATCH_TAG:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"INSERT INTO BatchTagTable (SignerId, BatchId, TagId) SELECT :{0}, :{1}, TagId FROM TagTable"
|
||||||
|
+ " WHERE Tag = :{2}",
|
||||||
|
QueryType.CONNECT_BATCH_TAG.getParamName(0),
|
||||||
|
QueryType.CONNECT_BATCH_TAG.getParamName(1),
|
||||||
|
QueryType.CONNECT_BATCH_TAG.getParamName(2));
|
||||||
|
|
||||||
|
case GET_BATCH_TAGS:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT Tag FROM TagTable INNER JOIN BatchTagTable ON TagTable.TagId = BatchTagTable.TagId"
|
||||||
|
+ " WHERE SignerId = :{0} AND BatchId = :{1} ORDER BY Tag ASC",
|
||||||
|
QueryType.GET_BATCH_TAGS.getParamName(0),
|
||||||
|
QueryType.GET_BATCH_TAGS.getParamName(1));
|
||||||
|
|
||||||
|
case REMOVE_BATCH_TAGS:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"DELETE FROM BatchTagTable WHERE SignerId = :{0} AND BatchId = :{1}",
|
||||||
|
QueryType.REMOVE_BATCH_TAGS.getParamName(0),
|
||||||
|
QueryType.REMOVE_BATCH_TAGS.getParamName(1));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Cannot serve a query of type " + queryType);
|
throw new IllegalArgumentException("Cannot serve a query of type " + queryType);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +161,8 @@ public class MySQLQueryProvider implements SQLQueryProvider {
|
||||||
return "MsgTable.EntryNum = :EntryNum" + serialString;
|
return "MsgTable.EntryNum = :EntryNum" + serialString;
|
||||||
case MAX_ENTRY:
|
case MAX_ENTRY:
|
||||||
return "MsgTable.EntryNum <= :EntryNum" + serialString;
|
return "MsgTable.EntryNum <= :EntryNum" + serialString;
|
||||||
|
case MIN_ENTRY:
|
||||||
|
return "MsgTable.EntryNum >= :EntryNum" + serialString;
|
||||||
case MAX_MESSAGES:
|
case MAX_MESSAGES:
|
||||||
return "LIMIT :Limit" + serialString;
|
return "LIMIT :Limit" + serialString;
|
||||||
case MSG_ID:
|
case MSG_ID:
|
||||||
|
@ -74,6 +174,13 @@ public class MySQLQueryProvider implements SQLQueryProvider {
|
||||||
return "EXISTS (SELECT 1 FROM TagTable"
|
return "EXISTS (SELECT 1 FROM TagTable"
|
||||||
+ " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId"
|
+ " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId"
|
||||||
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)";
|
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)";
|
||||||
|
|
||||||
|
case BEFORE_TIME:
|
||||||
|
return "MsgTable.ExactTime <= :TimeStamp";
|
||||||
|
|
||||||
|
case AFTER_TIME:
|
||||||
|
return "MsgTable.ExactTime >= :TimeStamp";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
|
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
|
||||||
}
|
}
|
||||||
|
@ -86,6 +193,7 @@ public class MySQLQueryProvider implements SQLQueryProvider {
|
||||||
switch(filterType) {
|
switch(filterType) {
|
||||||
case EXACT_ENTRY: // Go through
|
case EXACT_ENTRY: // Go through
|
||||||
case MAX_ENTRY: // Go through
|
case MAX_ENTRY: // Go through
|
||||||
|
case MIN_ENTRY: // Go through
|
||||||
case MAX_MESSAGES:
|
case MAX_MESSAGES:
|
||||||
return "INT";
|
return "INT";
|
||||||
|
|
||||||
|
@ -96,6 +204,10 @@ public class MySQLQueryProvider implements SQLQueryProvider {
|
||||||
case TAG:
|
case TAG:
|
||||||
return "VARCHAR";
|
return "VARCHAR";
|
||||||
|
|
||||||
|
case AFTER_TIME: // Go through
|
||||||
|
case BEFORE_TIME:
|
||||||
|
return "TIMESTAMP";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
|
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
|
||||||
}
|
}
|
||||||
|
@ -111,6 +223,7 @@ public class MySQLQueryProvider implements SQLQueryProvider {
|
||||||
dataSource.setDatabaseName(dbName);
|
dataSource.setDatabaseName(dbName);
|
||||||
dataSource.setUser(username);
|
dataSource.setUser(username);
|
||||||
dataSource.setPassword(password);
|
dataSource.setPassword(password);
|
||||||
|
dataSource.setAllowMultiQueries(true);
|
||||||
|
|
||||||
return dataSource;
|
return dataSource;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +232,8 @@ public class MySQLQueryProvider implements SQLQueryProvider {
|
||||||
public List<String> getSchemaCreationCommands() {
|
public List<String> getSchemaCreationCommands() {
|
||||||
List<String> list = new LinkedList<String>();
|
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 MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY,"
|
||||||
|
+ " MsgId TINYBLOB, ExactTime TIMESTAMP, 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 TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50), UNIQUE(Tag))");
|
||||||
|
|
||||||
|
@ -129,7 +243,14 @@ public class MySQLQueryProvider implements SQLQueryProvider {
|
||||||
+ " CONSTRAINT UNIQUE (EntryNum, TagID))");
|
+ " CONSTRAINT UNIQUE (EntryNum, TagID))");
|
||||||
|
|
||||||
list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB,"
|
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))");
|
+ " INDEX(SignerId(32)), CONSTRAINT Unique_Signature UNIQUE(SignerId(32), EntryNum),"
|
||||||
|
+ " CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))");
|
||||||
|
|
||||||
|
list.add("CREATE TABLE IF NOT EXISTS BatchTable (SignerId TINYBLOB, BatchId INT, SerialNum INT, Data BLOB,"
|
||||||
|
+ " CONSTRAINT Unique_Batch UNIQUE(SignerId(32), BatchId, SerialNum))");
|
||||||
|
|
||||||
|
list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (SignerId TINYBLOB, BatchId INT, TagId INT,"
|
||||||
|
+ " INDEX(SignerId(32), BatchId), CONSTRAINT FOREIGN KEY (TagId) REFERENCES TagTable(TagId))");
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -138,6 +259,8 @@ public class MySQLQueryProvider implements SQLQueryProvider {
|
||||||
public List<String> getSchemaDeletionCommands() {
|
public List<String> getSchemaDeletionCommands() {
|
||||||
List<String> list = new LinkedList<String>();
|
List<String> list = new LinkedList<String>();
|
||||||
|
|
||||||
|
list.add("DROP TABLE IF EXISTS BatchTagTable");
|
||||||
|
list.add("DROP TABLE IF EXISTS BatchTable");
|
||||||
list.add("DROP TABLE IF EXISTS MsgTagTable");
|
list.add("DROP TABLE IF EXISTS MsgTagTable");
|
||||||
list.add("DROP TABLE IF EXISTS SignatureTable");
|
list.add("DROP TABLE IF EXISTS SignatureTable");
|
||||||
list.add("DROP TABLE IF EXISTS TagTable");
|
list.add("DROP TABLE IF EXISTS TagTable");
|
||||||
|
|
|
@ -4,6 +4,7 @@ import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
import org.sqlite.SQLiteDataSource;
|
import org.sqlite.SQLiteDataSource;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -25,19 +26,97 @@ public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvi
|
||||||
switch(queryType) {
|
switch(queryType) {
|
||||||
case ADD_SIGNATURE:
|
case ADD_SIGNATURE:
|
||||||
return "INSERT OR IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum,:SignerId,:Signature)";
|
return "INSERT OR IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum,:SignerId,:Signature)";
|
||||||
|
|
||||||
case CONNECT_TAG:
|
case CONNECT_TAG:
|
||||||
return "INSERT OR IGNORE INTO MsgTagTable (TagId, EntryNum)"
|
return "INSERT OR IGNORE INTO MsgTagTable (TagId, EntryNum)"
|
||||||
+ " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag";
|
+ " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag";
|
||||||
|
|
||||||
case FIND_MSG_ID:
|
case FIND_MSG_ID:
|
||||||
return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId";
|
return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId";
|
||||||
|
|
||||||
|
case FIND_TAG_ID:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT TagId FROM TagTable WHERE Tag = :{0}",
|
||||||
|
QueryType.FIND_TAG_ID.getParamName(0));
|
||||||
|
|
||||||
case GET_MESSAGES:
|
case GET_MESSAGES:
|
||||||
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable";
|
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable";
|
||||||
|
|
||||||
|
case COUNT_MESSAGES:
|
||||||
|
return "SELECT COUNT(MsgTable.EntryNum) FROM MsgTable";
|
||||||
|
|
||||||
|
case GET_MESSAGE_STUBS:
|
||||||
|
return "SELECT MsgTable.EntryNum, MsgTable.MsgId, MsgTable.ExactTime FROM MsgTable";
|
||||||
|
|
||||||
case GET_SIGNATURES:
|
case GET_SIGNATURES:
|
||||||
return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum";
|
return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum";
|
||||||
|
|
||||||
case INSERT_MSG:
|
case INSERT_MSG:
|
||||||
return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId,:Msg)";
|
return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId,:Msg)";
|
||||||
|
|
||||||
case INSERT_NEW_TAG:
|
case INSERT_NEW_TAG:
|
||||||
return "INSERT OR IGNORE INTO TagTable(Tag) VALUES (:Tag)";
|
return "INSERT OR IGNORE INTO TagTable(Tag) VALUES (:Tag)";
|
||||||
|
|
||||||
|
case GET_LAST_MESSAGE_ENTRY:
|
||||||
|
return "SELECT MAX(MsgTable.EntryNum) FROM MsgTable";
|
||||||
|
|
||||||
|
case GET_BATCH_MESSAGE_ENTRY:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"
|
||||||
|
+ " INNER JOIN SignatureTable ON MsgTable.EntryNum = SignatureTable.EntryNum"
|
||||||
|
+ " INNER JOIN MsgTagTable ON MsgTable.EntryNum = MsgTagTable.EntryNum"
|
||||||
|
+ " INNER JOIN TagTable ON MsgTagTable.TagId = TagTable.TagId"
|
||||||
|
+ " WHERE SignatureTable.SignerId = :{0}"
|
||||||
|
+ " AND TagTable.Tag = :{1}",
|
||||||
|
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(0),
|
||||||
|
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(1));
|
||||||
|
|
||||||
|
case GET_BATCH_MESSAGE_DATA:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT Data FROM BatchTable"
|
||||||
|
+ " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}"
|
||||||
|
+ " ORDER BY SerialNum ASC",
|
||||||
|
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0),
|
||||||
|
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1),
|
||||||
|
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2));
|
||||||
|
|
||||||
|
case INSERT_BATCH_DATA:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"INSERT INTO BatchTable (SignerId, BatchId, SerialNum, Data)"
|
||||||
|
+ " VALUES (:{0}, :{1}, :{2}, :{3})",
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(0),
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(1),
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(2),
|
||||||
|
QueryType.INSERT_BATCH_DATA.getParamName(3));
|
||||||
|
|
||||||
|
case CHECK_BATCH_LENGTH:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT COUNT(Data) AS BatchLength FROM BatchTable"
|
||||||
|
+ " WHERE SignerId = :{0} AND BatchId = :{1}",
|
||||||
|
QueryType.CHECK_BATCH_LENGTH.getParamName(0),
|
||||||
|
QueryType.CHECK_BATCH_LENGTH.getParamName(1));
|
||||||
|
|
||||||
|
case CONNECT_BATCH_TAG:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"INSERT INTO BatchTagTable (SignerId, BatchId, TagId) SELECT :{0}, :{1}, TagId FROM TagTable"
|
||||||
|
+ " WHERE Tag = :{2}",
|
||||||
|
QueryType.CONNECT_BATCH_TAG.getParamName(0),
|
||||||
|
QueryType.CONNECT_BATCH_TAG.getParamName(1),
|
||||||
|
QueryType.CONNECT_BATCH_TAG.getParamName(2));
|
||||||
|
|
||||||
|
case GET_BATCH_TAGS:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"SELECT Tag FROM TagTable INNER JOIN BatchTagTable ON TagTable.TagId = BatchTagTable.TagId"
|
||||||
|
+ " WHERE SignerId = :{0} AND BatchId = :{1} ORDER BY Tag ASC",
|
||||||
|
QueryType.GET_BATCH_TAGS.getParamName(0),
|
||||||
|
QueryType.GET_BATCH_TAGS.getParamName(1));
|
||||||
|
|
||||||
|
case REMOVE_BATCH_TAGS:
|
||||||
|
return MessageFormat.format(
|
||||||
|
"DELETE FROM BatchTagTable WHERE SignerId = :{0} AND BatchId = :{1}",
|
||||||
|
QueryType.REMOVE_BATCH_TAGS.getParamName(0),
|
||||||
|
QueryType.REMOVE_BATCH_TAGS.getParamName(1));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Cannot serve a query of type " + queryType);
|
throw new IllegalArgumentException("Cannot serve a query of type " + queryType);
|
||||||
}
|
}
|
||||||
|
@ -52,19 +131,34 @@ public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvi
|
||||||
switch(filterType) {
|
switch(filterType) {
|
||||||
case EXACT_ENTRY:
|
case EXACT_ENTRY:
|
||||||
return "MsgTable.EntryNum = :EntryNum" + serialString;
|
return "MsgTable.EntryNum = :EntryNum" + serialString;
|
||||||
|
|
||||||
case MAX_ENTRY:
|
case MAX_ENTRY:
|
||||||
return "MsgTable.EntryNum <= :EntryNum" + serialString;
|
return "MsgTable.EntryNum <= :EntryNum" + serialString;
|
||||||
|
|
||||||
|
case MIN_ENTRY:
|
||||||
|
return "MsgTable.EntryNum <= :EntryNum" + serialString;
|
||||||
|
|
||||||
case MAX_MESSAGES:
|
case MAX_MESSAGES:
|
||||||
return "LIMIT = :Limit" + serialString;
|
return "LIMIT = :Limit" + serialString;
|
||||||
|
|
||||||
case MSG_ID:
|
case MSG_ID:
|
||||||
return "MsgTable.MsgId = :MsgId" + serialString;
|
return "MsgTable.MsgId = :MsgId" + serialString;
|
||||||
|
|
||||||
case SIGNER_ID:
|
case SIGNER_ID:
|
||||||
return "EXISTS (SELECT 1 FROM SignatureTable"
|
return "EXISTS (SELECT 1 FROM SignatureTable"
|
||||||
+ " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)";
|
+ " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)";
|
||||||
|
|
||||||
case TAG:
|
case TAG:
|
||||||
return "EXISTS (SELECT 1 FROM TagTable"
|
return "EXISTS (SELECT 1 FROM TagTable"
|
||||||
+ " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId"
|
+ " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId"
|
||||||
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)";
|
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)";
|
||||||
|
|
||||||
|
case BEFORE_TIME:
|
||||||
|
return "MsgTable.ExactTime <= :TimeStamp";
|
||||||
|
|
||||||
|
case AFTER_TIME:
|
||||||
|
return "MsgTable.ExactTime >= :TimeStamp";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
|
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
|
||||||
}
|
}
|
||||||
|
@ -73,15 +167,32 @@ public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException {
|
public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException {
|
||||||
return null; //TODO: write this.
|
|
||||||
|
switch(filterType) {
|
||||||
|
case EXACT_ENTRY: // Go through
|
||||||
|
case MAX_ENTRY: // Go through
|
||||||
|
case MIN_ENTRY: // Go through
|
||||||
|
case MAX_MESSAGES:
|
||||||
|
return "INTEGER";
|
||||||
|
|
||||||
|
case MSG_ID: // Go through
|
||||||
|
case SIGNER_ID:
|
||||||
|
return "BLOB";
|
||||||
|
|
||||||
|
case TAG:
|
||||||
|
return "VARCHAR";
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DataSource getDataSource() {
|
public DataSource getDataSource() {
|
||||||
// TODO: Fix this
|
|
||||||
SQLiteDataSource dataSource = new SQLiteDataSource();
|
SQLiteDataSource dataSource = new SQLiteDataSource();
|
||||||
dataSource.setUrl("jdbc:sqlite:" + dbName);
|
dataSource.setUrl("jdbc:sqlite:" + dbName);
|
||||||
dataSource.setDatabaseName("meerkat"); //TODO: Make generic
|
|
||||||
|
|
||||||
return dataSource;
|
return dataSource;
|
||||||
}
|
}
|
||||||
|
@ -98,10 +209,9 @@ public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvi
|
||||||
+ " REFERENCES MsgTable(EntryNum), FOREIGN KEY (TagId) REFERENCES TagTable(TagId), UNIQUE (EntryNum, TagID))");
|
+ " 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,"
|
list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INTEGER, SignerId BLOB, Signature BLOB,"
|
||||||
+ " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))");
|
+ " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum), UNIQUE(SignerId, EntryNum))");
|
||||||
|
|
||||||
list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)");
|
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;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package meerkat.bulletinboard.sqlserver.mappers;
|
||||||
|
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
import meerkat.comm.MessageOutputStream;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BatchData;
|
||||||
|
import org.springframework.jdbc.core.RowCallbackHandler;
|
||||||
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 19-Dec-15.
|
||||||
|
*/
|
||||||
|
public class BatchDataCallbackHandler implements RowCallbackHandler {
|
||||||
|
|
||||||
|
private final MessageOutputStream<BatchData> out;
|
||||||
|
|
||||||
|
public BatchDataCallbackHandler(MessageOutputStream<BatchData> out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processRow(ResultSet rs) throws SQLException {
|
||||||
|
try {
|
||||||
|
out.writeMessage(BatchData.parseFrom(rs.getBytes(1)));
|
||||||
|
} catch (IOException e) {
|
||||||
|
//TODO: Log
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package meerkat.bulletinboard.sqlserver.mappers;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BatchData;
|
||||||
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 19-Dec-15.
|
||||||
|
*/
|
||||||
|
public class BatchDataMapper implements RowMapper<BatchData> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BatchData mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
return BatchData.parseFrom(rs.getBytes(1));
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
return BatchData.getDefaultInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import java.sql.SQLException;
|
||||||
/**
|
/**
|
||||||
* Created by Arbel Deutsch Peled on 11-Dec-15.
|
* Created by Arbel Deutsch Peled on 11-Dec-15.
|
||||||
*/
|
*/
|
||||||
public class EntryNumMapper implements RowMapper<Long> {
|
public class LongMapper implements RowMapper<Long> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long mapRow(ResultSet rs, int rowNum) throws SQLException {
|
public Long mapRow(ResultSet rs, int rowNum) throws SQLException {
|
|
@ -0,0 +1,80 @@
|
||||||
|
package meerkat.bulletinboard.sqlserver.mappers;
|
||||||
|
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.*;
|
||||||
|
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider.*;
|
||||||
|
import meerkat.comm.MessageOutputStream;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import meerkat.protobuf.Crypto;
|
||||||
|
import org.springframework.jdbc.core.RowCallbackHandler;
|
||||||
|
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 21-Feb-16.
|
||||||
|
*/
|
||||||
|
public class MessageCallbackHandler implements RowCallbackHandler {
|
||||||
|
|
||||||
|
NamedParameterJdbcTemplate jdbcTemplate;
|
||||||
|
SQLQueryProvider sqlQueryProvider;
|
||||||
|
MessageOutputStream<BulletinBoardMessage> out;
|
||||||
|
|
||||||
|
public MessageCallbackHandler(NamedParameterJdbcTemplate jdbcTemplate, SQLQueryProvider sqlQueryProvider, MessageOutputStream<BulletinBoardMessage> out) {
|
||||||
|
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
this.sqlQueryProvider = sqlQueryProvider;
|
||||||
|
this.out = out;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processRow(ResultSet rs) throws SQLException {
|
||||||
|
|
||||||
|
BulletinBoardMessage.Builder result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
result = BulletinBoardMessage.newBuilder()
|
||||||
|
.setEntryNum(rs.getLong(1))
|
||||||
|
.setMsg(UnsignedBulletinBoardMessage.parseFrom(rs.getBytes(2)));
|
||||||
|
|
||||||
|
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
//TODO: log
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve signatures
|
||||||
|
|
||||||
|
MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource();
|
||||||
|
sqlParameterSource.addValue(QueryType.GET_SIGNATURES.getParamName(0), result.getEntryNum());
|
||||||
|
|
||||||
|
List<Crypto.Signature> signatures = jdbcTemplate.query(
|
||||||
|
sqlQueryProvider.getSQLString(QueryType.GET_SIGNATURES),
|
||||||
|
sqlParameterSource,
|
||||||
|
new SignatureMapper());
|
||||||
|
|
||||||
|
// Append signatures
|
||||||
|
result.addAllSig(signatures);
|
||||||
|
|
||||||
|
// Finalize message and add to message list.
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
out.writeMessage(result.build());
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
//TODO: log
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package meerkat.bulletinboard.sqlserver.mappers;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage;
|
||||||
|
import meerkat.util.BulletinBoardUtils;
|
||||||
|
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 MessageStubMapper implements RowMapper<BulletinBoardMessage> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BulletinBoardMessage mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
|
||||||
|
return BulletinBoardMessage.newBuilder()
|
||||||
|
.setEntryNum(rs.getLong(1))
|
||||||
|
.setMsg(UnsignedBulletinBoardMessage.newBuilder()
|
||||||
|
.setData(ByteString.copyFrom(rs.getBytes(2)))
|
||||||
|
.setTimestamp(BulletinBoardUtils.toTimestampProto(rs.getTimestamp(3)))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package meerkat.bulletinboard.sqlserver.mappers;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 20-Dec-15.
|
||||||
|
*/
|
||||||
|
public class StringMapper implements RowMapper<String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
return rs.getString(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,13 +3,10 @@ package meerkat.bulletinboard.webapp;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletContextEvent;
|
import javax.servlet.ServletContextEvent;
|
||||||
import javax.servlet.ServletContextListener;
|
import javax.servlet.ServletContextListener;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.*;
|
||||||
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.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.StreamingOutput;
|
||||||
|
|
||||||
import meerkat.bulletinboard.BulletinBoardServer;
|
import meerkat.bulletinboard.BulletinBoardServer;
|
||||||
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
|
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
|
||||||
|
@ -17,13 +14,20 @@ import meerkat.bulletinboard.sqlserver.H2QueryProvider;
|
||||||
import meerkat.bulletinboard.sqlserver.MySQLQueryProvider;
|
import meerkat.bulletinboard.sqlserver.MySQLQueryProvider;
|
||||||
import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider;
|
import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider;
|
||||||
import meerkat.comm.CommunicationException;
|
import meerkat.comm.CommunicationException;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.BoolMsg;
|
import meerkat.comm.MessageOutputStream;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
|
import meerkat.protobuf.BulletinBoardAPI;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessageList;
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.MessageFilterList;
|
import static meerkat.bulletinboard.BulletinBoardConstants.*;
|
||||||
import meerkat.rest.Constants;
|
import static meerkat.rest.Constants.*;
|
||||||
|
|
||||||
@Path(Constants.BULLETIN_BOARD_SERVER_PATH)
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the BulletinBoardServer which functions as a WebApp
|
||||||
|
*/
|
||||||
|
@Path(BULLETIN_BOARD_SERVER_PATH)
|
||||||
public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextListener{
|
public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextListener{
|
||||||
|
|
||||||
private static final String BULLETIN_BOARD_ATTRIBUTE_NAME = "bulletinBoard";
|
private static final String BULLETIN_BOARD_ATTRIBUTE_NAME = "bulletinBoard";
|
||||||
|
@ -79,24 +83,146 @@ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path(Constants.POST_MESSAGE_PATH)
|
@Path(POST_MESSAGE_PATH)
|
||||||
@POST
|
@POST
|
||||||
@Consumes(Constants.MEDIATYPE_PROTOBUF)
|
@Consumes(MEDIATYPE_PROTOBUF)
|
||||||
@Produces(Constants.MEDIATYPE_PROTOBUF)
|
@Produces(MEDIATYPE_PROTOBUF)
|
||||||
@Override
|
@Override
|
||||||
public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
||||||
init();
|
init();
|
||||||
return bulletinBoard.postMessage(msg);
|
return bulletinBoard.postMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path(Constants.READ_MESSAGES_PATH)
|
|
||||||
@POST
|
|
||||||
@Consumes(Constants.MEDIATYPE_PROTOBUF)
|
|
||||||
@Produces(Constants.MEDIATYPE_PROTOBUF)
|
|
||||||
@Override
|
@Override
|
||||||
public BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException {
|
public void readMessages(MessageFilterList filterList, MessageOutputStream<BulletinBoardMessage> out) throws CommunicationException {
|
||||||
init();
|
init();
|
||||||
return bulletinBoard.readMessages(filterList);
|
bulletinBoard.readMessages(filterList, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Path(READ_MESSAGES_PATH)
|
||||||
|
@POST
|
||||||
|
@Consumes(MEDIATYPE_PROTOBUF)
|
||||||
|
/**
|
||||||
|
* Wrapper for the readMessages method which streams the output into the response
|
||||||
|
*/
|
||||||
|
public StreamingOutput readMessages(final MessageFilterList filterList) {
|
||||||
|
|
||||||
|
return new StreamingOutput() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(OutputStream output) throws IOException, WebApplicationException {
|
||||||
|
MessageOutputStream<BulletinBoardMessage> out = new MessageOutputStream<>(output);
|
||||||
|
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
bulletinBoard.readMessages(filterList, out);
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
//TODO: Log
|
||||||
|
out.writeMessage(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path(BEGIN_BATCH_PATH)
|
||||||
|
@POST
|
||||||
|
@Consumes(MEDIATYPE_PROTOBUF)
|
||||||
|
@Produces(MEDIATYPE_PROTOBUF)
|
||||||
|
@Override
|
||||||
|
public BoolMsg beginBatch(BeginBatchMessage message) {
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
return bulletinBoard.beginBatch(message);
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path(POST_BATCH_PATH)
|
||||||
|
@POST
|
||||||
|
@Consumes(MEDIATYPE_PROTOBUF)
|
||||||
|
@Produces(MEDIATYPE_PROTOBUF)
|
||||||
|
@Override
|
||||||
|
public BoolMsg postBatchMessage(BatchMessage batchMessage) {
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
return bulletinBoard.postBatchMessage(batchMessage);
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path(CLOSE_BATCH_PATH)
|
||||||
|
@POST
|
||||||
|
@Consumes(MEDIATYPE_PROTOBUF)
|
||||||
|
@Produces(MEDIATYPE_PROTOBUF)
|
||||||
|
@Override
|
||||||
|
public BoolMsg closeBatchMessage(CloseBatchMessage message) {
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
return bulletinBoard.closeBatchMessage(message);
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBatch(BatchSpecificationMessage message, MessageOutputStream<BatchData> out) {
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
bulletinBoard.readBatch(message, out);
|
||||||
|
} catch (CommunicationException | IllegalArgumentException e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path(READ_BATCH_PATH)
|
||||||
|
@POST
|
||||||
|
@Consumes(MEDIATYPE_PROTOBUF)
|
||||||
|
/**
|
||||||
|
* Wrapper for the readBatch method which streams the output into the response
|
||||||
|
*/
|
||||||
|
public StreamingOutput readBatch(final BatchSpecificationMessage message) {
|
||||||
|
|
||||||
|
return new StreamingOutput() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(OutputStream output) throws IOException, WebApplicationException {
|
||||||
|
MessageOutputStream<BatchData> out = new MessageOutputStream<>(output);
|
||||||
|
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
bulletinBoard.readBatch(message, out);
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
//TODO: Log
|
||||||
|
out.writeMessage(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path(SYNC_QUERY_PATH)
|
||||||
|
@POST
|
||||||
|
@Consumes(MEDIATYPE_PROTOBUF)
|
||||||
|
@Produces(MEDIATYPE_PROTOBUF)
|
||||||
|
@Override
|
||||||
|
public SyncQueryResponse querySync(SyncQuery syncQuery) throws CommunicationException {
|
||||||
|
try{
|
||||||
|
init();
|
||||||
|
return bulletinBoard.querySync(syncQuery);
|
||||||
|
} catch (CommunicationException | IllegalArgumentException e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<param-value>mypass</param-value></context-param>
|
<param-value>mypass</param-value></context-param>
|
||||||
<context-param>
|
<context-param>
|
||||||
<param-name>dbType</param-name>
|
<param-name>dbType</param-name>
|
||||||
<param-value>SQLite</param-value></context-param>
|
<param-value>H2</param-value></context-param>
|
||||||
<listener>
|
<listener>
|
||||||
<listener-class>meerkat.bulletinboard.webapp.BulletinBoardWebApp</listener-class>
|
<listener-class>meerkat.bulletinboard.webapp.BulletinBoardWebApp</listener-class>
|
||||||
</listener>
|
</listener>
|
||||||
|
|
|
@ -4,8 +4,11 @@ package meerkat.bulletinboard;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.TextFormat;
|
import com.google.protobuf.TextFormat;
|
||||||
|
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.comm.MessageInputStream;
|
||||||
import meerkat.protobuf.Crypto.*;
|
import meerkat.protobuf.Crypto.*;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.*;
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.*;
|
||||||
import meerkat.rest.Constants;
|
import meerkat.rest.Constants;
|
||||||
import meerkat.rest.ProtobufMessageBodyReader;
|
import meerkat.rest.ProtobufMessageBodyReader;
|
||||||
import meerkat.rest.ProtobufMessageBodyWriter;
|
import meerkat.rest.ProtobufMessageBodyWriter;
|
||||||
|
@ -17,7 +20,10 @@ import javax.ws.rs.client.Client;
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
import javax.ws.rs.client.ClientBuilder;
|
||||||
import javax.ws.rs.client.Entity;
|
import javax.ws.rs.client.Entity;
|
||||||
import javax.ws.rs.client.WebTarget;
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class BulletinBoardSQLServerIntegrationTest {
|
public class BulletinBoardSQLServerIntegrationTest {
|
||||||
|
|
||||||
|
@ -43,6 +49,16 @@ public class BulletinBoardSQLServerIntegrationTest {
|
||||||
byte[] b3 = {(byte) 21, (byte) 22, (byte) 23, (byte) 24};
|
byte[] b3 = {(byte) 21, (byte) 22, (byte) 23, (byte) 24};
|
||||||
byte[] b4 = {(byte) 4, (byte) 5, (byte) 100, (byte) -50, (byte) 0};
|
byte[] b4 = {(byte) 4, (byte) 5, (byte) 100, (byte) -50, (byte) 0};
|
||||||
|
|
||||||
|
Timestamp t1 = Timestamp.newBuilder()
|
||||||
|
.setSeconds(8276482)
|
||||||
|
.setNanos(4314)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Timestamp t2 = Timestamp.newBuilder()
|
||||||
|
.setSeconds(987591)
|
||||||
|
.setNanos(1513)
|
||||||
|
.build();
|
||||||
|
|
||||||
WebTarget webTarget;
|
WebTarget webTarget;
|
||||||
Response response;
|
Response response;
|
||||||
BoolMsg bool;
|
BoolMsg bool;
|
||||||
|
@ -50,12 +66,12 @@ public class BulletinBoardSQLServerIntegrationTest {
|
||||||
BulletinBoardMessage msg;
|
BulletinBoardMessage msg;
|
||||||
|
|
||||||
MessageFilterList filterList;
|
MessageFilterList filterList;
|
||||||
BulletinBoardMessageList msgList;
|
List<BulletinBoardMessage> msgList;
|
||||||
|
|
||||||
// Test writing mechanism
|
// Test writing mechanism
|
||||||
|
|
||||||
System.err.println("******** Testing: " + Constants.POST_MESSAGE_PATH);
|
System.err.println("******** Testing: " + POST_MESSAGE_PATH);
|
||||||
webTarget = client.target(BASE_URL).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.POST_MESSAGE_PATH);
|
webTarget = client.target(BASE_URL).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH);
|
||||||
System.err.println(webTarget.getUri());
|
System.err.println(webTarget.getUri());
|
||||||
|
|
||||||
msg = BulletinBoardMessage.newBuilder()
|
msg = BulletinBoardMessage.newBuilder()
|
||||||
|
@ -63,6 +79,7 @@ public class BulletinBoardSQLServerIntegrationTest {
|
||||||
.addTag("Signature")
|
.addTag("Signature")
|
||||||
.addTag("Trustee")
|
.addTag("Trustee")
|
||||||
.setData(ByteString.copyFrom(b1))
|
.setData(ByteString.copyFrom(b1))
|
||||||
|
.setTimestamp(t1)
|
||||||
.build())
|
.build())
|
||||||
.addSig(Signature.newBuilder()
|
.addSig(Signature.newBuilder()
|
||||||
.setType(SignatureType.DSA)
|
.setType(SignatureType.DSA)
|
||||||
|
@ -86,6 +103,7 @@ public class BulletinBoardSQLServerIntegrationTest {
|
||||||
.addTag("Vote")
|
.addTag("Vote")
|
||||||
.addTag("Trustee")
|
.addTag("Trustee")
|
||||||
.setData(ByteString.copyFrom(b4))
|
.setData(ByteString.copyFrom(b4))
|
||||||
|
.setTimestamp(t2)
|
||||||
.build())
|
.build())
|
||||||
.addSig(Signature.newBuilder()
|
.addSig(Signature.newBuilder()
|
||||||
.setType(SignatureType.ECDSA)
|
.setType(SignatureType.ECDSA)
|
||||||
|
@ -101,8 +119,8 @@ public class BulletinBoardSQLServerIntegrationTest {
|
||||||
|
|
||||||
// Test reading mechanism
|
// Test reading mechanism
|
||||||
|
|
||||||
System.err.println("******** Testing: " + Constants.READ_MESSAGES_PATH);
|
System.err.println("******** Testing: " + READ_MESSAGES_PATH);
|
||||||
webTarget = client.target(BASE_URL).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH);
|
webTarget = client.target(BASE_URL).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
|
||||||
filterList = MessageFilterList.newBuilder()
|
filterList = MessageFilterList.newBuilder()
|
||||||
.addFilter(
|
.addFilter(
|
||||||
MessageFilter.newBuilder()
|
MessageFilter.newBuilder()
|
||||||
|
@ -112,13 +130,20 @@ public class BulletinBoardSQLServerIntegrationTest {
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
|
InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF), InputStream.class);
|
||||||
System.err.println(response);
|
|
||||||
msgList = response.readEntity(BulletinBoardMessageList.class);
|
MessageInputStream<BulletinBoardMessage> inputStream =
|
||||||
System.err.println("List size: " + msgList.getMessageCount());
|
MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
msgList = inputStream.asList();
|
||||||
|
System.err.println("List size: " + msgList.size());
|
||||||
System.err.println("This is the list:");
|
System.err.println("This is the list:");
|
||||||
System.err.println(TextFormat.printToString(msgList));
|
|
||||||
assert msgList.getMessageCount() == 1;
|
for (BulletinBoardMessage message : msgList) {
|
||||||
|
System.err.println(TextFormat.printToString(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert msgList.size() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package meerkat.bulletinboard;
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.lang.management.ThreadMXBean;
|
import java.lang.management.ThreadMXBean;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
@ -12,26 +15,30 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
import java.security.UnrecoverableKeyException;
|
import java.security.UnrecoverableKeyException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
import meerkat.comm.CommunicationException;
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.comm.MessageInputStream;
|
||||||
|
import meerkat.comm.MessageOutputStream;
|
||||||
|
import meerkat.comm.MessageInputStream.MessageInputStreamFactory;
|
||||||
|
import meerkat.crypto.Digest;
|
||||||
import meerkat.crypto.concrete.ECDSASignature;
|
import meerkat.crypto.concrete.ECDSASignature;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
|
import meerkat.crypto.concrete.SHA256Digest;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.FilterType;
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.MessageFilter;
|
import meerkat.util.BulletinBoardMessageGenerator;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.MessageFilterList;
|
import org.h2.util.DateTimeUtils;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
public class GenericBulletinBoardServerTest {
|
public class GenericBulletinBoardServerTest {
|
||||||
|
|
||||||
protected BulletinBoardServer bulletinBoardServer;
|
protected BulletinBoardServer bulletinBoardServer;
|
||||||
private ECDSASignature signers[];
|
private GenericBatchDigitalSignature[] signers;
|
||||||
private ByteString[] signerIDs;
|
private ByteString[] signerIDs;
|
||||||
|
|
||||||
private Random random;
|
private Random random;
|
||||||
|
@ -51,8 +58,14 @@ public class GenericBulletinBoardServerTest {
|
||||||
private String[] tags;
|
private String[] tags;
|
||||||
private byte[][] data;
|
private byte[][] data;
|
||||||
|
|
||||||
|
private List<CompleteBatch> completeBatches;
|
||||||
|
|
||||||
private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests
|
private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests
|
||||||
|
|
||||||
|
private BulletinBoardMessageGenerator bulletinBoardMessageGenerator;
|
||||||
|
|
||||||
|
private Digest digest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bulletinBoardServer is an initialized server.
|
* @param bulletinBoardServer is an initialized server.
|
||||||
* @throws InstantiationException
|
* @throws InstantiationException
|
||||||
|
@ -71,10 +84,10 @@ public class GenericBulletinBoardServerTest {
|
||||||
|
|
||||||
this.bulletinBoardServer = bulletinBoardServer;
|
this.bulletinBoardServer = bulletinBoardServer;
|
||||||
|
|
||||||
signers = new ECDSASignature[2];
|
signers = new GenericBatchDigitalSignature[2];
|
||||||
signerIDs = new ByteString[signers.length];
|
signerIDs = new ByteString[signers.length];
|
||||||
signers[0] = new ECDSASignature();
|
signers[0] = new GenericBatchDigitalSignature(new ECDSASignature());
|
||||||
signers[1] = new ECDSASignature();
|
signers[1] = new GenericBatchDigitalSignature(new ECDSASignature());
|
||||||
|
|
||||||
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
|
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
|
||||||
char[] password = KEYFILE_PASSWORD1.toCharArray();
|
char[] password = KEYFILE_PASSWORD1.toCharArray();
|
||||||
|
@ -115,12 +128,21 @@ public class GenericBulletinBoardServerTest {
|
||||||
System.err.println("Couldn't find signing key " + e.getMessage());
|
System.err.println("Couldn't find signing key " + e.getMessage());
|
||||||
fail("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
|
// We use insecure randomness in tests for repeatability
|
||||||
|
random = new Random(0);
|
||||||
|
bulletinBoardMessageGenerator = new BulletinBoardMessageGenerator(random);
|
||||||
|
|
||||||
|
digest = new SHA256Digest();
|
||||||
|
|
||||||
long end = threadBean.getCurrentThreadCpuTime();
|
long end = threadBean.getCurrentThreadCpuTime();
|
||||||
System.err.println("Finished initializing GenericBulletinBoardServerTest");
|
System.err.println("Finished initializing GenericBulletinBoardServerTest");
|
||||||
System.err.println("Time of operation: " + (end - start));
|
System.err.println("Time of operation: " + (end - start));
|
||||||
|
|
||||||
|
// Initialize Batch variables
|
||||||
|
|
||||||
|
completeBatches = new ArrayList<CompleteBatch>(10);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte randomByte(){
|
private byte randomByte(){
|
||||||
|
@ -170,7 +192,11 @@ public class GenericBulletinBoardServerTest {
|
||||||
|
|
||||||
for (i = 1; i <= MESSAGE_NUM; i++) {
|
for (i = 1; i <= MESSAGE_NUM; i++) {
|
||||||
unsignedMsgBuilder = UnsignedBulletinBoardMessage.newBuilder()
|
unsignedMsgBuilder = UnsignedBulletinBoardMessage.newBuilder()
|
||||||
.setData(ByteString.copyFrom(data[i - 1]));
|
.setData(ByteString.copyFrom(data[i - 1]))
|
||||||
|
.setTimestamp(Timestamp.newBuilder()
|
||||||
|
.setSeconds(i)
|
||||||
|
.setNanos(i)
|
||||||
|
.build());
|
||||||
|
|
||||||
// Add tags based on bit-representation of message number.
|
// Add tags based on bit-representation of message number.
|
||||||
|
|
||||||
|
@ -230,28 +256,39 @@ public class GenericBulletinBoardServerTest {
|
||||||
System.err.println("Starting to test tag and signature mechanism");
|
System.err.println("Starting to test tag and signature mechanism");
|
||||||
long start = threadBean.getCurrentThreadCpuTime();
|
long start = threadBean.getCurrentThreadCpuTime();
|
||||||
|
|
||||||
List<BulletinBoardMessage> messages;
|
List<BulletinBoardMessage> messages = new LinkedList<>();
|
||||||
|
|
||||||
// Check tag mechanism
|
// Check tag mechanism
|
||||||
|
|
||||||
for (int i = 0 ; i < TAG_NUM ; i++){
|
for (int i = 0 ; i < TAG_NUM ; i++){
|
||||||
|
|
||||||
// Retrieve messages having tag i
|
// Retrieve messages having tag i
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
messages = bulletinBoardServer.readMessages(
|
MessageFilterList filterList = MessageFilterList.newBuilder()
|
||||||
MessageFilterList.newBuilder()
|
.addFilter(MessageFilter.newBuilder()
|
||||||
.addFilter(MessageFilter.newBuilder()
|
.setType(FilterType.TAG)
|
||||||
.setType(FilterType.TAG)
|
.setTag(tags[i])
|
||||||
.setTag(tags[i])
|
.build()
|
||||||
.build()
|
)
|
||||||
)
|
.build();
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.getMessageList();
|
|
||||||
|
|
||||||
} catch (CommunicationException e) {
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
bulletinBoardServer.readMessages(filterList, new MessageOutputStream<BulletinBoardMessage>(outputStream));
|
||||||
|
|
||||||
|
MessageInputStream<BulletinBoardMessage> inputStream =
|
||||||
|
MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream(
|
||||||
|
outputStream.toByteArray()),
|
||||||
|
BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
messages = inputStream.asList();
|
||||||
|
|
||||||
|
} catch (CommunicationException | IOException e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
return;
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
fail(e.getMessage());
|
fail(e.getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -328,11 +365,26 @@ public class GenericBulletinBoardServerTest {
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
messages = bulletinBoardServer.readMessages(filterListBuilder.build()).getMessageList();
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
bulletinBoardServer.readMessages(filterListBuilder.build(), new MessageOutputStream<BulletinBoardMessage>(outputStream));
|
||||||
|
|
||||||
|
MessageInputStream<BulletinBoardMessage> inputStream =
|
||||||
|
MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream(
|
||||||
|
outputStream.toByteArray()),
|
||||||
|
BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
messages = inputStream.asList();
|
||||||
|
|
||||||
} catch (CommunicationException e) {
|
} catch (CommunicationException e) {
|
||||||
System.err.println("Failed retrieving multi-tag messages from DB: " + e.getMessage());
|
System.err.println("Failed retrieving multi-tag messages from DB: " + e.getMessage());
|
||||||
fail("Failed retrieving multi-tag messages from DB: " + e.getMessage());
|
fail("Failed retrieving multi-tag messages from DB: " + e.getMessage());
|
||||||
return;
|
return;
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | IOException e) {
|
||||||
|
System.err.println("Falied to read from stream while retrieving multi-tag messages: " + e.getMessage());
|
||||||
|
fail("Falied to read from stream while retrieving multi-tag messages: " + e.getMessage());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedMsgCount /= 2;
|
expectedMsgCount /= 2;
|
||||||
|
@ -359,11 +411,26 @@ public class GenericBulletinBoardServerTest {
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
messages = bulletinBoardServer.readMessages(filterListBuilder.build()).getMessageList();
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
bulletinBoardServer.readMessages(filterListBuilder.build(), new MessageOutputStream<BulletinBoardMessage>(outputStream));
|
||||||
|
|
||||||
|
MessageInputStream<BulletinBoardMessage> inputStream =
|
||||||
|
MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream(
|
||||||
|
outputStream.toByteArray()),
|
||||||
|
BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
messages = inputStream.asList();
|
||||||
|
|
||||||
} catch (CommunicationException e) {
|
} catch (CommunicationException e) {
|
||||||
System.err.println("Failed retrieving multi-signature message from DB: " + e.getMessage());
|
System.err.println("Failed retrieving multi-signature message from DB: " + e.getMessage());
|
||||||
fail("Failed retrieving multi-signature message from DB: " + e.getMessage());
|
fail("Failed retrieving multi-signature message from DB: " + e.getMessage());
|
||||||
return;
|
return;
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | IOException e) {
|
||||||
|
System.err.println("Falied to read from stream while retrieving multi-signature message: " + e.getMessage());
|
||||||
|
fail("Falied to read from stream while retrieving multi-signature message: " + e.getMessage());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(messages.size(), is(MESSAGE_NUM / 4));
|
assertThat(messages.size(), is(MESSAGE_NUM / 4));
|
||||||
|
@ -377,6 +444,263 @@ public class GenericBulletinBoardServerTest {
|
||||||
System.err.println("Time of operation: " + (end - start));
|
System.err.println("Time of operation: " + (end - start));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that posting a message before opening a batch does not work
|
||||||
|
* @throws CommunicationException
|
||||||
|
*/
|
||||||
|
public void testBatchPostAfterClose() throws CommunicationException, SignatureException {
|
||||||
|
|
||||||
|
final int BATCH_ID = 100;
|
||||||
|
|
||||||
|
CompleteBatch completeBatch = new CompleteBatch(Timestamp.newBuilder()
|
||||||
|
.setSeconds(978325)
|
||||||
|
.setNanos(8097234)
|
||||||
|
.build());
|
||||||
|
BoolMsg result;
|
||||||
|
|
||||||
|
// Create data
|
||||||
|
|
||||||
|
completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder()
|
||||||
|
.setSignerId(signerIDs[1])
|
||||||
|
.setBatchId(BATCH_ID)
|
||||||
|
.addTag("Test")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
BatchData batchData = BatchData.newBuilder()
|
||||||
|
.setData(ByteString.copyFrom((new byte[] {1,2,3,4})))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
completeBatch.appendBatchData(batchData);
|
||||||
|
|
||||||
|
signers[1].updateContent(completeBatch);
|
||||||
|
|
||||||
|
completeBatch.setSignature(signers[1].sign());
|
||||||
|
|
||||||
|
// Begin batch
|
||||||
|
|
||||||
|
result = bulletinBoardServer.beginBatch(completeBatch.getBeginBatchMessage());
|
||||||
|
|
||||||
|
assertThat("Was not able to open batch", result.getValue(), is(true));
|
||||||
|
|
||||||
|
// Post data
|
||||||
|
|
||||||
|
BatchMessage batchMessage = BatchMessage.newBuilder()
|
||||||
|
.setSignerId(signerIDs[1])
|
||||||
|
.setBatchId(BATCH_ID)
|
||||||
|
.setData(batchData)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
result = bulletinBoardServer.postBatchMessage(batchMessage);
|
||||||
|
|
||||||
|
assertThat("Was not able to post batch message", result.getValue(), is(true));
|
||||||
|
|
||||||
|
// Close batch
|
||||||
|
|
||||||
|
result = bulletinBoardServer.closeBatchMessage(completeBatch.getCloseBatchMessage());
|
||||||
|
|
||||||
|
assertThat("Was not able to close batch", result.getValue(), is(true));
|
||||||
|
|
||||||
|
// Attempt to open batch again
|
||||||
|
|
||||||
|
result = bulletinBoardServer.beginBatch(completeBatch.getBeginBatchMessage());
|
||||||
|
|
||||||
|
assertThat("Was able to open a closed batch", result.getValue(), is(false));
|
||||||
|
|
||||||
|
// Attempt to add batch data
|
||||||
|
|
||||||
|
result = bulletinBoardServer.postBatchMessage(batchMessage);
|
||||||
|
|
||||||
|
assertThat("Was able to change a closed batch", result.getValue(), is(false));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a complete batch message
|
||||||
|
* @throws CommunicationException
|
||||||
|
*/
|
||||||
|
public void testPostBatch() throws CommunicationException, SignatureException {
|
||||||
|
|
||||||
|
CompleteBatch completeBatch = new CompleteBatch(Timestamp.newBuilder()
|
||||||
|
.setSeconds(12345)
|
||||||
|
.setNanos(1111)
|
||||||
|
.build());
|
||||||
|
int currentBatch = completeBatches.size();
|
||||||
|
|
||||||
|
BoolMsg result;
|
||||||
|
|
||||||
|
// Define batch data
|
||||||
|
|
||||||
|
String[] tempBatchTags = new String[]{randomString(),randomString(),randomString()};
|
||||||
|
byte[][] tempBatchData = new byte[Math.abs(randomByte())][];
|
||||||
|
|
||||||
|
for (int i = 0 ; i < tempBatchData.length ; i++) {
|
||||||
|
|
||||||
|
tempBatchData[i] = new byte[Math.abs(randomByte())];
|
||||||
|
|
||||||
|
for (int j = 0; j < tempBatchData[i].length; j++) {
|
||||||
|
tempBatchData[i][j] = randomByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin batch
|
||||||
|
|
||||||
|
completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder()
|
||||||
|
.setSignerId(signerIDs[0])
|
||||||
|
.setBatchId(currentBatch)
|
||||||
|
.addAllTag(Arrays.asList(tempBatchTags))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
result = bulletinBoardServer.beginBatch(completeBatch.getBeginBatchMessage());
|
||||||
|
|
||||||
|
assertThat("Could not begin batch " + currentBatch, result.getValue(), is(true));
|
||||||
|
|
||||||
|
// Add batch data and randomize data posting order
|
||||||
|
|
||||||
|
List<Integer> dataOrder = new ArrayList<Integer>(tempBatchData.length);
|
||||||
|
for (int i = 0 ; i < tempBatchData.length ; i++) {
|
||||||
|
dataOrder.add(i);
|
||||||
|
completeBatch.appendBatchData(BatchData.newBuilder()
|
||||||
|
.setData(ByteString.copyFrom(tempBatchData[i]))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
Collections.shuffle(dataOrder);
|
||||||
|
|
||||||
|
// Post data
|
||||||
|
|
||||||
|
for (int i = 0 ; i < tempBatchData.length ; i++) {
|
||||||
|
|
||||||
|
int dataIndex = dataOrder.get(i);
|
||||||
|
|
||||||
|
result = bulletinBoardServer.postBatchMessage(BatchMessage.newBuilder()
|
||||||
|
.setSignerId(signerIDs[0])
|
||||||
|
.setBatchId(currentBatch)
|
||||||
|
.setSerialNum(dataIndex)
|
||||||
|
.setData(completeBatch.getBatchDataList().get(dataIndex))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
assertThat("Could not post batch data for batch ID " + currentBatch + " serial number " + dataIndex,
|
||||||
|
result.getValue(), is(true));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close batch
|
||||||
|
|
||||||
|
signers[0].updateContent(completeBatch);
|
||||||
|
completeBatch.setSignature(signers[0].sign());
|
||||||
|
|
||||||
|
result = bulletinBoardServer.closeBatchMessage(completeBatch.getCloseBatchMessage());
|
||||||
|
|
||||||
|
assertThat("Could not close batch " + currentBatch, result.getValue(), is(true));
|
||||||
|
|
||||||
|
// Update locally stored batches
|
||||||
|
completeBatches.add(completeBatch);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadBatch() throws CommunicationException {
|
||||||
|
|
||||||
|
for (CompleteBatch completeBatch : completeBatches) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
BatchSpecificationMessage batchSpecificationMessage =
|
||||||
|
BatchSpecificationMessage.newBuilder()
|
||||||
|
.setSignerId(completeBatch.getBeginBatchMessage().getSignerId())
|
||||||
|
.setBatchId(completeBatch.getBeginBatchMessage().getBatchId())
|
||||||
|
.setStartPosition(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
bulletinBoardServer.readBatch(batchSpecificationMessage, new MessageOutputStream<BatchData>(outputStream));
|
||||||
|
|
||||||
|
MessageInputStream<BatchData> inputStream =
|
||||||
|
MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream(
|
||||||
|
outputStream.toByteArray()),
|
||||||
|
BatchData.class);
|
||||||
|
|
||||||
|
List<BatchData> batchDataList = inputStream.asList();
|
||||||
|
|
||||||
|
assertThat("Non-matching batch data for batch " + completeBatch.getBeginBatchMessage().getBatchId(),
|
||||||
|
completeBatch.getBatchDataList().equals(batchDataList), is(true));
|
||||||
|
|
||||||
|
} catch (IOException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
assertThat("Error reading batch data list from input stream", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSyncQuery()
|
||||||
|
throws SignatureException, CommunicationException, IOException,NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||||
|
|
||||||
|
Checksum checksum = new SimpleChecksum();
|
||||||
|
|
||||||
|
Timestamp timestamp = Timestamp.newBuilder()
|
||||||
|
.setSeconds(1)
|
||||||
|
.setNanos(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BulletinBoardMessage newMessage = bulletinBoardMessageGenerator.generateRandomMessage(signers, timestamp, 10, 10);
|
||||||
|
|
||||||
|
BoolMsg result = bulletinBoardServer.postMessage(newMessage);
|
||||||
|
assertThat("Failed to post message to BB Server", result.getValue(), is(true));
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
digest.update(newMessage.getMsg());
|
||||||
|
|
||||||
|
ByteString messageID = ByteString.copyFrom(digest.digest());
|
||||||
|
|
||||||
|
MessageFilterList filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MSG_ID)
|
||||||
|
.setId(messageID)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
bulletinBoardServer.readMessages(filterList, new MessageOutputStream<BulletinBoardMessage>(outputStream));
|
||||||
|
|
||||||
|
MessageInputStream<BulletinBoardMessage> inputStream =
|
||||||
|
MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream(
|
||||||
|
outputStream.toByteArray()),
|
||||||
|
BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
long lastEntry = inputStream.asList().get(0).getEntryNum();
|
||||||
|
|
||||||
|
SyncQuery syncQuery = SyncQuery.newBuilder()
|
||||||
|
.setFilterList(MessageFilterList.getDefaultInstance())
|
||||||
|
.addQuery(SingleSyncQuery.newBuilder()
|
||||||
|
.setChecksum(2)
|
||||||
|
.setTimeOfSync(Timestamp.newBuilder()
|
||||||
|
.setSeconds(2)
|
||||||
|
.setNanos(0)
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
SyncQueryResponse queryResponse = bulletinBoardServer.querySync(syncQuery);
|
||||||
|
|
||||||
|
assertThat("Sync query replies with positive sync when no sync was expected", queryResponse.getLastEntryNum(), is(equalTo(-1l)));
|
||||||
|
|
||||||
|
syncQuery = SyncQuery.newBuilder()
|
||||||
|
.setFilterList(MessageFilterList.getDefaultInstance())
|
||||||
|
.addQuery(SingleSyncQuery.newBuilder()
|
||||||
|
.setChecksum(checksum.getChecksum(messageID))
|
||||||
|
.setTimeOfSync(timestamp)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
queryResponse = bulletinBoardServer.querySync(syncQuery);
|
||||||
|
|
||||||
|
assertThat("Sync query reply contained wrong last entry number", lastEntry, is(equalTo(queryResponse.getLastEntryNum())));
|
||||||
|
|
||||||
|
assertThat("Sync query reply contained wrong timestamp", timestamp, is(equalTo(queryResponse.getLastTimeOfSync())));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void close(){
|
public void close(){
|
||||||
signers[0].clearSigningKey();
|
signers[0].clearSigningKey();
|
||||||
|
|
|
@ -107,6 +107,39 @@ public class H2BulletinBoardServerTest {
|
||||||
System.err.println("Time of operation: " + (end - start));
|
System.err.println("Time of operation: " + (end - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchPostAfterClose() {
|
||||||
|
try{
|
||||||
|
serverTest.testBatchPostAfterClose();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatch() {
|
||||||
|
|
||||||
|
final int BATCH_NUM = 20;
|
||||||
|
|
||||||
|
try{
|
||||||
|
for (int i = 0 ; i < BATCH_NUM ; i++) {
|
||||||
|
serverTest.testPostBatch();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
serverTest.testReadBatch();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void close() {
|
public void close() {
|
||||||
System.err.println("Starting to close H2BulletinBoardServerTest");
|
System.err.println("Starting to close H2BulletinBoardServerTest");
|
||||||
|
|
|
@ -8,8 +8,11 @@ import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.lang.management.ThreadMXBean;
|
import java.lang.management.ThreadMXBean;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.security.SignatureException;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
@ -111,6 +114,49 @@ public class MySQLBulletinBoardServerTest {
|
||||||
System.err.println("Time of operation: " + (end - start));
|
System.err.println("Time of operation: " + (end - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchPostAfterClose() {
|
||||||
|
try{
|
||||||
|
serverTest.testBatchPostAfterClose();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatch() {
|
||||||
|
|
||||||
|
final int BATCH_NUM = 20;
|
||||||
|
|
||||||
|
try{
|
||||||
|
for (int i = 0 ; i < BATCH_NUM ; i++) {
|
||||||
|
serverTest.testPostBatch();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
serverTest.testReadBatch();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSyncQuery() {
|
||||||
|
try {
|
||||||
|
serverTest.testSyncQuery();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void close() {
|
public void close() {
|
||||||
System.err.println("Starting to close MySQLBulletinBoardServerTest");
|
System.err.println("Starting to close MySQLBulletinBoardServerTest");
|
||||||
|
|
|
@ -39,6 +39,7 @@ version += "${isSnapshot ? '-SNAPSHOT' : ''}"
|
||||||
dependencies {
|
dependencies {
|
||||||
// Logging
|
// Logging
|
||||||
compile 'org.slf4j:slf4j-api:1.7.7'
|
compile 'org.slf4j:slf4j-api:1.7.7'
|
||||||
|
compile 'javax.ws.rs:javax.ws.rs-api:2.0.+'
|
||||||
runtime 'ch.qos.logback:logback-classic:1.1.2'
|
runtime 'ch.qos.logback:logback-classic:1.1.2'
|
||||||
runtime 'ch.qos.logback:logback-core:1.1.2'
|
runtime 'ch.qos.logback:logback-core:1.1.2'
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ dependencies {
|
||||||
compile 'com.google.protobuf:protobuf-java:3.+'
|
compile 'com.google.protobuf:protobuf-java:3.+'
|
||||||
|
|
||||||
// ListeningExecutor
|
// ListeningExecutor
|
||||||
compile 'com.google.guava:guava:11.0.+'
|
compile 'com.google.guava:guava:15.0'
|
||||||
|
|
||||||
// Crypto
|
// Crypto
|
||||||
compile 'org.factcenter.qilin:qilin:1.2+'
|
compile 'org.factcenter.qilin:qilin:1.2+'
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 14-Dec-15.
|
||||||
|
*/
|
||||||
|
public interface AsyncBulletinBoardClient extends BulletinBoardClient {
|
||||||
|
|
||||||
|
public interface MessageHandler {
|
||||||
|
void handleNewMessages(List<BulletinBoardMessage> messageList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post a message to the bulletin board in an asynchronous manner
|
||||||
|
* @param msg is the message to be posted
|
||||||
|
* @param callback is a class containing methods to handle the result of the operation
|
||||||
|
* @return a unique message ID for the message, that can be later used to retrieve the batch
|
||||||
|
*/
|
||||||
|
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform an end-to-end post of a signed batch message
|
||||||
|
* @param completeBatch contains all the data of the batch including the meta-data and the signature
|
||||||
|
* @param callback is a class containing methods to handle the result of the operation
|
||||||
|
* @return a unique identifier for the batch message
|
||||||
|
*/
|
||||||
|
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This message informs the server about the existence of a new batch message and supplies it with the tags associated with it
|
||||||
|
* @param beginBatchMessage contains the data required to begin the batch
|
||||||
|
* @param callback is a callback function class for handling results of the operation
|
||||||
|
*/
|
||||||
|
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method posts batch data into an (assumed to be open) batch
|
||||||
|
* It does not close the batch
|
||||||
|
* @param signerId is the canonical form for the ID of the sender of this batch
|
||||||
|
* @param batchId is a unique (per signer) ID for this batch
|
||||||
|
* @param batchDataList is the (canonically ordered) list of data comprising the portion of the batch to be posted
|
||||||
|
* @param startPosition is the location (in the batch) of the first entry in batchDataList
|
||||||
|
* (optionally used to continue interrupted post operations)
|
||||||
|
* The first position in the batch is position 0
|
||||||
|
* @param callback is a callback function class for handling results of the operation
|
||||||
|
*/
|
||||||
|
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList,
|
||||||
|
int startPosition, FutureCallback<Boolean> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloading of the postBatchData method which starts at the first position in the batch
|
||||||
|
*/
|
||||||
|
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloading of the postBatchData method which uses ByteString
|
||||||
|
*/
|
||||||
|
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList,
|
||||||
|
int startPosition, FutureCallback<Boolean> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloading of the postBatchData method which uses ByteString and starts at the first position in the batch
|
||||||
|
*/
|
||||||
|
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to close a batch message
|
||||||
|
* @param closeBatchMessage contains the data required to close the batch
|
||||||
|
* @param callback is a callback function class for handling results of the operation
|
||||||
|
*/
|
||||||
|
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check how "safe" a given message is in an asynchronous manner
|
||||||
|
* The result of the computation is a rank between 0.0 and 1.0 indicating the fraction of servers containing the message
|
||||||
|
* @param id is the unique message identifier for retrieval
|
||||||
|
* @param callback is a callback function class for handling results of the operation
|
||||||
|
*/
|
||||||
|
public void getRedundancy(MessageID id, FutureCallback<Float> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read all messages posted matching the given filter in an asynchronous manner
|
||||||
|
* 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).
|
||||||
|
* @param callback is a callback function class for handling results of the operation
|
||||||
|
*/
|
||||||
|
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a given batch message from the bulletin board
|
||||||
|
* @param batchSpecificationMessage contains the data required to specify a single batch instance
|
||||||
|
* @param callback is a callback class for handling the result of the operation
|
||||||
|
*/
|
||||||
|
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to a notifier that will return any new messages on the server that match the given filters
|
||||||
|
* @param filterList defines the set of filters for message retrieval
|
||||||
|
* @param messageHandler defines the handler for new messages received
|
||||||
|
*/
|
||||||
|
public void subscribe(MessageFilterList filterList, MessageHandler messageHandler);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.crypto.Digest;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 18-Dec-15.
|
||||||
|
* Extends the Digest interface with a method for digesting Batch messages
|
||||||
|
*/
|
||||||
|
public interface BatchDigest extends Digest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the digest with the batch message data (ignore the signature)
|
||||||
|
* @param completeBatch is the batch message that needs to be digested
|
||||||
|
*/
|
||||||
|
public void update(CompleteBatch completeBatch);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.crypto.DigitalSignature;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BatchData;
|
||||||
|
import meerkat.protobuf.Crypto.Signature;
|
||||||
|
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 20-Dec-15.
|
||||||
|
* Extends the DigitalSignature interface with methods for signing and authenticating Batch messages
|
||||||
|
*/
|
||||||
|
public interface BatchDigitalSignature extends DigitalSignature {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the batch data to the signed content (ignoring the signature)
|
||||||
|
* @param completeBatch contains all the data about the batch
|
||||||
|
* @throws SignatureException
|
||||||
|
*/
|
||||||
|
public void updateContent(CompleteBatch completeBatch) throws SignatureException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a complete verification process on the given batch message
|
||||||
|
* @param completeBatch contains the batch data as well as the signature
|
||||||
|
* @return TRUE if the batch is verified and FALSE otherwise
|
||||||
|
* @throws SignatureException | SignatureException | InvalidKeyException when underlying methods do so
|
||||||
|
*/
|
||||||
|
public boolean verify(CompleteBatch completeBatch) throws SignatureException, CertificateException, InvalidKeyException;
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package meerkat.bulletinboard;
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
import meerkat.comm.*;
|
import meerkat.comm.CommunicationException;
|
||||||
import meerkat.protobuf.Voting.*;
|
import meerkat.protobuf.Voting.*;
|
||||||
|
|
||||||
import static meerkat.protobuf.BulletinBoardAPI.*;
|
import static meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
@ -12,11 +12,6 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public interface BulletinBoardClient {
|
public interface BulletinBoardClient {
|
||||||
|
|
||||||
interface ClientCallback<T> {
|
|
||||||
void handleCallback(T msg);
|
|
||||||
void handleFailure(Throwable t);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the client to use some specified servers
|
* Initialize the client to use some specified servers
|
||||||
* @param clientParams contains the parameters required for the client setup
|
* @param clientParams contains the parameters required for the client setup
|
||||||
|
@ -24,26 +19,31 @@ public interface BulletinBoardClient {
|
||||||
void init(BulletinBoardClientParams clientParams);
|
void init(BulletinBoardClientParams clientParams);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post a message to the bulletin board
|
* Post a message to the bulletin board in a synchronous manner
|
||||||
* @param msg
|
* @param msg is the message to be posted
|
||||||
|
* @return a unique message ID for the message, that can be later used to retrieve the batch
|
||||||
|
* @throws CommunicationException
|
||||||
*/
|
*/
|
||||||
MessageID postMessage(BulletinBoardMessage msg, ClientCallback<?> callback);
|
MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check how "safe" a given message is
|
* Check how "safe" a given message is in a synchronous manner
|
||||||
* @param id
|
* @param id is the unique message identifier for retrieval
|
||||||
* @return a normalized "redundancy score" from 0 (local only) to 1 (fully published)
|
* @return a normalized "redundancy score" from 0 (local only) to 1 (fully published)
|
||||||
*/
|
*/
|
||||||
void getRedundancy(MessageID id, ClientCallback<Float> callback);
|
float getRedundancy(MessageID id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read all messages posted matching the given filter
|
* Read all messages posted matching the given filter in a synchronous manner
|
||||||
* Note that if messages haven't been "fully posted", this might return a different
|
* 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
|
* set of messages in different calls. However, messages that are fully posted
|
||||||
* are guaranteed to be included.
|
* are guaranteed to be included.
|
||||||
* @param filterList return only messages that match the filters (null means no filtering).
|
* @param filterList return only messages that match the filters (null means no filtering).
|
||||||
|
* @return the list of messages
|
||||||
*/
|
*/
|
||||||
void readMessages(MessageFilterList filterList, ClientCallback<List<BulletinBoardMessage>> callback);
|
List<BulletinBoardMessage> readMessages(MessageFilterList filterList);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes all connections, if any.
|
* Closes all connections, if any.
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 21-Dec-15.
|
||||||
|
*/
|
||||||
|
public interface BulletinBoardConstants {
|
||||||
|
|
||||||
|
// Relative addresses for Bulletin Board operations
|
||||||
|
|
||||||
|
public static final String BULLETIN_BOARD_SERVER_PATH = "/bbserver";
|
||||||
|
public static final String READ_MESSAGES_PATH = "/readmessages";
|
||||||
|
public static final String READ_BATCH_PATH = "/readbatch";
|
||||||
|
public static final String POST_MESSAGE_PATH = "/postmessage";
|
||||||
|
public static final String BEGIN_BATCH_PATH = "/beginbatch";
|
||||||
|
public static final String POST_BATCH_PATH = "/postbatch";
|
||||||
|
public static final String CLOSE_BATCH_PATH = "/closebatch";
|
||||||
|
public static final String SYNC_QUERY_PATH = "/syncquery";
|
||||||
|
|
||||||
|
// Other Constants
|
||||||
|
|
||||||
|
public static final String BATCH_TAG = "@BATCH";
|
||||||
|
public static final String BATCH_ID_TAG_PREFIX = "BATCHID#";
|
||||||
|
|
||||||
|
}
|
|
@ -1,42 +1,94 @@
|
||||||
package meerkat.bulletinboard;
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
import meerkat.comm.CommunicationException;
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.comm.MessageOutputStream;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.*;
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Arbel on 07/11/15.
|
* Created by Arbel on 07/11/15.
|
||||||
*
|
*
|
||||||
* This interface refers to a single instance of a Bulletin Board.
|
* This interface refers to a single instance of a Bulletin Board
|
||||||
* An implementation of this interface may use any DB and be hosted on any machine.
|
* An implementation of this interface may use any DB and be hosted on any machine.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public interface BulletinBoardServer{
|
public interface BulletinBoardServer{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method initializes the server by reading the signature data and storing it.
|
* This method initializes the server by reading the signature data and storing it
|
||||||
* It also establishes the connection to the DB.
|
* It also establishes the connection to the DB
|
||||||
* @throws CommunicationException on DB connection error.
|
* @throws CommunicationException on DB connection error
|
||||||
*/
|
*/
|
||||||
public void init(String meerkatDB) throws CommunicationException;
|
public void init(String meerkatDB) throws CommunicationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post a message to bulletin board.
|
* Post a message to bulletin board.
|
||||||
* @param msg is the actual (signed) message
|
* @param msg is the actual (signed) message
|
||||||
* @return TRUE if the message has been authenticated and FALSE otherwise (in ProtoBuf form).
|
* @return TRUE if the message has been authenticated and FALSE otherwise (in ProtoBuf form)
|
||||||
* @throws CommunicationException on DB connection error.
|
* @throws CommunicationException on DB connection error
|
||||||
*/
|
*/
|
||||||
public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException;
|
public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read all messages posted matching the given filter.
|
* Read all messages posted matching the given filter
|
||||||
* @param filter return only messages that match the filter (empty list means no filtering).
|
* @param filterList return only messages that match the filters (empty list or null means no filtering)
|
||||||
* @return
|
* @param out is an output stream into which the matching messages are written
|
||||||
|
* @throws CommunicationException on DB connection error
|
||||||
*/
|
*/
|
||||||
BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException;
|
public void readMessages(MessageFilterList filterList, MessageOutputStream<BulletinBoardMessage> out) throws CommunicationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs server about a new batch message
|
||||||
|
* @param message contains the required data about the new batch
|
||||||
|
* @return TRUE if the batch request is accepted amd FALSE otherwise
|
||||||
|
* Specifically, if such a batch already exists and is not yet closed: the value returned will be TRUE
|
||||||
|
* However, if such a batch exists and is already closed: the value returned will be FALSE
|
||||||
|
* @throws CommunicationException on DB connection error
|
||||||
|
*/
|
||||||
|
public BoolMsg beginBatch(BeginBatchMessage message) throws CommunicationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a (part of a) batch message to the bulletin board
|
||||||
|
* Note that the existence and contents of a batch message are not available for reading before the batch is finalized
|
||||||
|
* @param batchMessage contains the (partial) data this message carries as well as meta-data required in order to place the data
|
||||||
|
* in the correct position inside the correct batch
|
||||||
|
* @return TRUE if the message is accepted and successfully saved and FALSE otherwise
|
||||||
|
* Specifically, if the batch is already closed: the value returned will be FALSE
|
||||||
|
* However, requiring to open a batch before insertion of messages is implementation-dependent
|
||||||
|
* @throws CommunicationException on DB connection error
|
||||||
|
*/
|
||||||
|
public BoolMsg postBatchMessage(BatchMessage batchMessage) throws CommunicationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to close and finalize a batch message
|
||||||
|
* @param message contains the data necessary to close the batch; in particular: the signature for the batch
|
||||||
|
* @return TRUE if the batch was successfully closed, FALSE otherwise
|
||||||
|
* Specifically, if the signature is invalid or if some of the batch parts have not yet been submitted: the value returned will be FALSE
|
||||||
|
* @throws CommunicationException on DB connection error
|
||||||
|
*/
|
||||||
|
public BoolMsg closeBatchMessage(CloseBatchMessage message) throws CommunicationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a batch message from the server (starting with the supplied position)
|
||||||
|
* @param message specifies the signer ID and the batch ID to read as well as an (optional) start position
|
||||||
|
* @param out is a stream of the ordered batch messages starting from the specified start position (if given) or from the beginning (if omitted)
|
||||||
|
* @throws CommunicationException on DB connection error
|
||||||
|
* @throws IllegalArgumentException if message does not specify a batch
|
||||||
|
*/
|
||||||
|
public void readBatch(BatchSpecificationMessage message, MessageOutputStream<BatchData> out) throws CommunicationException, IllegalArgumentException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the database for sync status with respect to a given sync query
|
||||||
|
* @param syncQuery contains a succinct representation of states to compare to
|
||||||
|
* @return a SyncQueryResponse object containing the representation of the most recent state the database matches
|
||||||
|
* @throws CommunicationException
|
||||||
|
*/
|
||||||
|
public SyncQueryResponse querySync(SyncQuery syncQuery) throws CommunicationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method closes the connection to the DB.
|
* This method closes the connection to the DB
|
||||||
* @throws CommunicationException on DB connection error.
|
* @throws CommunicationException on DB connection error
|
||||||
*/
|
*/
|
||||||
public void close() throws CommunicationException;
|
public void close() throws CommunicationException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import meerkat.crypto.Digest;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.MessageID;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 01-Mar-16.
|
||||||
|
* This interface is used to create checksums of Bulletin Board messages IDs
|
||||||
|
* This is useful in comparing database states
|
||||||
|
*/
|
||||||
|
public interface Checksum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Digest method which is used in creating message IDs from the messages themselves
|
||||||
|
* This method must be called with an initialized Digest before calling any methods that receive a parameter of type BulletinBoardMessage
|
||||||
|
* @param digest is the Digest that will be used to create message IDs from Bulletin Board Messages
|
||||||
|
*/
|
||||||
|
public void setDigest(Digest digest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to reset the current checksum state
|
||||||
|
*/
|
||||||
|
public void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current checksum with the given ID
|
||||||
|
* @param messageID is the message ID to be added
|
||||||
|
*/
|
||||||
|
public void update(MessageID messageID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current checksum with the given collection of IDs
|
||||||
|
* @param messageIDs contains the message IDs
|
||||||
|
*/
|
||||||
|
public void update(Collection<MessageID> messageIDs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current checksum with the given ID
|
||||||
|
* @param messageID is the message ID to be added
|
||||||
|
*/
|
||||||
|
public void update(ByteString messageID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current checksum with the given ID
|
||||||
|
* @param messageID is the message ID to be added
|
||||||
|
*/
|
||||||
|
public void update(byte[] messageID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current checksum with the message ID of the given message
|
||||||
|
* @param bulletinBoardMessage is the message whose ID should be added to the checksum
|
||||||
|
* @throws IllegalStateException if a Digest has not been set before calling this method
|
||||||
|
*/
|
||||||
|
public void digestAndUpdate(BulletinBoardMessage bulletinBoardMessage) throws IllegalStateException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current checksum with the message IDs of the given messages
|
||||||
|
* @param bulletinBoardMessages contains the messages whose IDs should be added to the checksum
|
||||||
|
* @throws IllegalStateException if a Digest has not been set before calling this method
|
||||||
|
*/
|
||||||
|
public void digestAndUpdate(Collection<BulletinBoardMessage> bulletinBoardMessages) throws IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current checksum without changing the checksum state
|
||||||
|
* @return the current checksum
|
||||||
|
*/
|
||||||
|
public long getChecksum();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current checksum with the given ID and returns the resulting checksum
|
||||||
|
* The checksum is not reset afterwards
|
||||||
|
* @param messageID is the message ID to be added
|
||||||
|
* @return the updated checksum
|
||||||
|
*/
|
||||||
|
public long getChecksum(MessageID messageID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current checksum with the given ID and returns the resulting checksum
|
||||||
|
* The checksum is not reset afterwards
|
||||||
|
* @param messageID is the message ID to be added
|
||||||
|
* @return the updated checksum
|
||||||
|
*/
|
||||||
|
public long getChecksum(ByteString messageID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current checksum with the given ID and returns the resulting checksum
|
||||||
|
* The checksum is not reset afterwards
|
||||||
|
* @param messageID is the message ID to be added
|
||||||
|
* @return the updated checksum
|
||||||
|
*/
|
||||||
|
public long getChecksum(byte[] messageID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current checksum with the given IDs and returns the resulting checksum
|
||||||
|
* The checksum is not reset afterwards
|
||||||
|
* @param messageIDs contains the message IDs to be added
|
||||||
|
* @return the updated checksum
|
||||||
|
*/
|
||||||
|
public long getChecksum(Collection<MessageID> messageIDs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current checksum with the message ID of the given message
|
||||||
|
* The checksum is not reset afterwards
|
||||||
|
* @param bulletinBoardMessage is the message whose ID should be added to the checksum
|
||||||
|
* @return the updated checksum
|
||||||
|
*/
|
||||||
|
public long digestAndGetChecksum(BulletinBoardMessage bulletinBoardMessage) throws IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current checksum with the message IDs of the given messages
|
||||||
|
* The checksum is not reset afterwards
|
||||||
|
* @param bulletinBoardMessages contains the messages whose IDs should be added to the checksum
|
||||||
|
* @return the updated checksum
|
||||||
|
*/
|
||||||
|
public long digestAndGetChecksum(Collection<BulletinBoardMessage> bulletinBoardMessages) throws IllegalStateException;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import meerkat.protobuf.Crypto.*;
|
||||||
|
import meerkat.util.BulletinBoardMessageComparator;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 14-Dec-15.
|
||||||
|
*
|
||||||
|
* A data structure for holding a complete batch message along with its signature
|
||||||
|
*/
|
||||||
|
public class CompleteBatch {
|
||||||
|
|
||||||
|
private BeginBatchMessage beginBatchMessage;
|
||||||
|
private List<BatchData> batchDataList;
|
||||||
|
private Signature signature;
|
||||||
|
private Timestamp timestamp;
|
||||||
|
|
||||||
|
public CompleteBatch() {
|
||||||
|
batchDataList = new LinkedList<BatchData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompleteBatch(BeginBatchMessage newBeginBatchMessage) {
|
||||||
|
this();
|
||||||
|
beginBatchMessage = newBeginBatchMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List<BatchData> newDataList) {
|
||||||
|
this(newBeginBatchMessage);
|
||||||
|
appendBatchData(newDataList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List<BatchData> newDataList, Signature newSignature) {
|
||||||
|
this(newBeginBatchMessage, newDataList);
|
||||||
|
signature = newSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List<BatchData> newDataList, Signature newSignature, Timestamp timestamp) {
|
||||||
|
this(newBeginBatchMessage, newDataList, newSignature);
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompleteBatch(Timestamp timestamp) {
|
||||||
|
this();
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BeginBatchMessage getBeginBatchMessage() {
|
||||||
|
return beginBatchMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BatchData> getBatchDataList() {
|
||||||
|
return batchDataList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Signature getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timestamp getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CloseBatchMessage getCloseBatchMessage() {
|
||||||
|
return CloseBatchMessage.newBuilder()
|
||||||
|
.setBatchId(getBeginBatchMessage().getBatchId())
|
||||||
|
.setBatchLength(getBatchDataList().size())
|
||||||
|
.setSig(getSignature())
|
||||||
|
.setTimestamp(getTimestamp())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBeginBatchMessage(BeginBatchMessage beginBatchMessage) {
|
||||||
|
this.beginBatchMessage = beginBatchMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendBatchData(BatchData newBatchData) {
|
||||||
|
batchDataList.add(newBatchData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendBatchData(List<BatchData> newBatchDataList) {
|
||||||
|
batchDataList.addAll(newBatchDataList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignature(Signature newSignature) {
|
||||||
|
signature = newSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(Timestamp timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
|
||||||
|
if (!(other instanceof CompleteBatch)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompleteBatch otherBatch = (CompleteBatch) other;
|
||||||
|
|
||||||
|
boolean result = true;
|
||||||
|
|
||||||
|
if (beginBatchMessage == null) {
|
||||||
|
if (otherBatch.getBeginBatchMessage() != null)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
result = result && beginBatchMessage.equals(otherBatch.getBeginBatchMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batchDataList == null) {
|
||||||
|
if (otherBatch.getBatchDataList() != null)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
result = result && batchDataList.equals(otherBatch.getBatchDataList());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signature == null) {
|
||||||
|
if (otherBatch.getSignature() != null)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
result = result && signature.equals(otherBatch.getSignature());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamp == null) {
|
||||||
|
if (otherBatch.getTimestamp() != null)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
result = result && timestamp.equals(otherBatch.getTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Batch " + beginBatchMessage.getSignerId().toString() + ":" + beginBatchMessage.getBatchId();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.protobuf.Message;
|
||||||
|
import meerkat.crypto.Digest;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.MessageID;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BatchData;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 19-Dec-15.
|
||||||
|
* Wrapper class for digesting Batches in a standardized way
|
||||||
|
*/
|
||||||
|
public class GenericBatchDigest implements BatchDigest{
|
||||||
|
|
||||||
|
private Digest digest;
|
||||||
|
|
||||||
|
public GenericBatchDigest(Digest digest) {
|
||||||
|
this.digest = digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(CompleteBatch completeBatch) {
|
||||||
|
|
||||||
|
update(completeBatch.getBeginBatchMessage());
|
||||||
|
|
||||||
|
for (BatchData batchData : completeBatch.getBatchDataList()) {
|
||||||
|
update(batchData);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(completeBatch.getTimestamp());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] digest() {
|
||||||
|
return digest.digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID digestAsMessageID() {
|
||||||
|
return digest.digestAsMessageID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Message msg) {
|
||||||
|
digest.update(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
digest.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GenericBatchDigest clone() throws CloneNotSupportedException{
|
||||||
|
return new GenericBatchDigest(digest.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.Message;
|
||||||
|
import meerkat.crypto.DigitalSignature;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BatchData;
|
||||||
|
import meerkat.protobuf.Crypto;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 20-Dec-15.
|
||||||
|
* Wrapper class for signing and verifying Batch signatures in a standardized way
|
||||||
|
*/
|
||||||
|
public class GenericBatchDigitalSignature implements BatchDigitalSignature{
|
||||||
|
|
||||||
|
private DigitalSignature digitalSignature;
|
||||||
|
|
||||||
|
public GenericBatchDigitalSignature(DigitalSignature digitalSignature) {
|
||||||
|
this.digitalSignature = digitalSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateContent(CompleteBatch completeBatch) throws SignatureException {
|
||||||
|
|
||||||
|
digitalSignature.updateContent(completeBatch.getBeginBatchMessage());
|
||||||
|
|
||||||
|
for (BatchData batchData : completeBatch.getBatchDataList()) {
|
||||||
|
digitalSignature.updateContent(batchData);
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalSignature.updateContent(completeBatch.getTimestamp());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(CompleteBatch completeBatch) throws SignatureException, CertificateException, InvalidKeyException {
|
||||||
|
|
||||||
|
digitalSignature.initVerify(completeBatch.getSignature());
|
||||||
|
updateContent(completeBatch);
|
||||||
|
return digitalSignature.verify();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadVerificationCertificates(InputStream certStream) throws CertificateException {
|
||||||
|
digitalSignature.loadVerificationCertificates(certStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearVerificationCertificates() {
|
||||||
|
digitalSignature.clearVerificationCertificates();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateContent(Message msg) throws SignatureException {
|
||||||
|
digitalSignature.updateContent(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Crypto.Signature sign() throws SignatureException {
|
||||||
|
return digitalSignature.sign();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initVerify(Crypto.Signature sig) throws CertificateException, InvalidKeyException {
|
||||||
|
digitalSignature.initVerify(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify() {
|
||||||
|
return digitalSignature.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password)
|
||||||
|
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
|
||||||
|
return digitalSignature.getPKCS12KeyStoreBuilder(keyStream, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadSigningCertificate(KeyStore.Builder keyStoreBuilder) throws IOException, CertificateException, UnrecoverableKeyException {
|
||||||
|
digitalSignature.loadSigningCertificate(keyStoreBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteString getSignerID() {
|
||||||
|
return digitalSignature.getSignerID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearSigningKey() {
|
||||||
|
digitalSignature.clearSigningKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import meerkat.crypto.Digest;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.MessageID;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 01-Mar-16.
|
||||||
|
* Implementation of Checksum via bitwise XOR of the bytes of message IDs
|
||||||
|
*/
|
||||||
|
public class SimpleChecksum implements Checksum{
|
||||||
|
|
||||||
|
private Digest digest;
|
||||||
|
private long checksum;
|
||||||
|
|
||||||
|
public SimpleChecksum() {
|
||||||
|
digest = null;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDigest(Digest digest) {
|
||||||
|
this.digest = digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
checksum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(MessageID messageID) {
|
||||||
|
ByteString messageIDByteString = messageID.getID();
|
||||||
|
update(messageIDByteString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Collection<MessageID> messageIDs) {
|
||||||
|
|
||||||
|
for (MessageID messageID : messageIDs){
|
||||||
|
update(messageID);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(ByteString messageID) {
|
||||||
|
for (int i = 0 ; i < messageID.size() ; i++){
|
||||||
|
checksum &= messageID.byteAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(byte[] messageID) {
|
||||||
|
for (int i = 0 ; i < messageID.length ; i++){
|
||||||
|
checksum &= messageID[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkDigest() throws IllegalStateException {
|
||||||
|
|
||||||
|
if (digest == null){
|
||||||
|
throw new IllegalStateException("Digest method not set. Use setDigest method before calling digestAndUpdate.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void digestAndUpdate(BulletinBoardMessage bulletinBoardMessage) throws IllegalStateException {
|
||||||
|
|
||||||
|
checkDigest();
|
||||||
|
|
||||||
|
digest.reset();
|
||||||
|
digest.update(bulletinBoardMessage);
|
||||||
|
update(digest.digest());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void digestAndUpdate(Collection<BulletinBoardMessage> bulletinBoardMessages) throws IllegalStateException {
|
||||||
|
|
||||||
|
for (BulletinBoardMessage bulletinBoardMessage : bulletinBoardMessages){
|
||||||
|
digestAndUpdate(bulletinBoardMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getChecksum() {
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getChecksum(MessageID messageID) {
|
||||||
|
update(messageID);
|
||||||
|
return getChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getChecksum(ByteString messageID) {
|
||||||
|
update(messageID);
|
||||||
|
return getChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getChecksum(byte[] messageID) {
|
||||||
|
update(messageID);
|
||||||
|
return getChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getChecksum(Collection<MessageID> messageIDs) {
|
||||||
|
update(messageIDs);
|
||||||
|
return getChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long digestAndGetChecksum(BulletinBoardMessage bulletinBoardMessage) throws IllegalStateException {
|
||||||
|
digestAndUpdate(bulletinBoardMessage);
|
||||||
|
return getChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long digestAndGetChecksum(Collection<BulletinBoardMessage> bulletinBoardMessages) throws IllegalStateException {
|
||||||
|
digestAndUpdate(bulletinBoardMessages);
|
||||||
|
return getChecksum();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package meerkat.comm;
|
||||||
|
|
||||||
|
import com.google.protobuf.Message;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 21-Feb-16.
|
||||||
|
* A input stream of Protobuf messages
|
||||||
|
*/
|
||||||
|
public class MessageInputStream<T extends Message> implements Iterable<T>{
|
||||||
|
|
||||||
|
private T.Builder builder;
|
||||||
|
|
||||||
|
private InputStream in;
|
||||||
|
|
||||||
|
MessageInputStream(InputStream in, Class<T> type) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||||
|
this.in = in;
|
||||||
|
this.builder = (T.Builder) type.getMethod("newBuilder").invoke(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
|
||||||
|
return new Iterator<T>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
try {
|
||||||
|
return isAvailable();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
try {
|
||||||
|
return readMessage();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class for actually creating a MessageInputStream
|
||||||
|
*/
|
||||||
|
public static class MessageInputStreamFactory {
|
||||||
|
|
||||||
|
public static <T extends Message> MessageInputStream<T> createMessageInputStream(InputStream in, Class<T> type)
|
||||||
|
throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||||
|
|
||||||
|
return new MessageInputStream<>(in, type);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public T readMessage() throws IOException{
|
||||||
|
|
||||||
|
builder.clear();
|
||||||
|
builder.mergeDelimitedFrom(in);
|
||||||
|
return (T) builder.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAvailable() throws IOException {
|
||||||
|
return (in.available() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> asList() throws IOException{
|
||||||
|
|
||||||
|
List<T> list = new LinkedList<>();
|
||||||
|
|
||||||
|
while (isAvailable()){
|
||||||
|
list.add(readMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package meerkat.comm;
|
||||||
|
|
||||||
|
import com.google.protobuf.Message;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 21-Feb-16.
|
||||||
|
* An output stream of Protobuf messages
|
||||||
|
*/
|
||||||
|
public class MessageOutputStream<T extends Message> {
|
||||||
|
|
||||||
|
private OutputStream out;
|
||||||
|
|
||||||
|
public MessageOutputStream(OutputStream out) throws IOException {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeMessage(T message) throws IOException {
|
||||||
|
message.writeDelimitedTo(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
package meerkat.comm;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by talm on 24/10/15.
|
|
||||||
*/
|
|
||||||
public class Timestamp {
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
package meerkat.crypto;
|
package meerkat.crypto;
|
||||||
|
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.MessageID;
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
|
@ -13,7 +14,13 @@ public interface Digest {
|
||||||
* (copied from {@link MessageDigest#digest()})
|
* (copied from {@link MessageDigest#digest()})
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
byte[] digest();
|
public byte[] digest();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes the hash computation and returns a MessageID Protobuf as output
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public MessageID digestAsMessageID();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the digest using the specified message (in serialized wire form)
|
* Updates the digest using the specified message (in serialized wire form)
|
||||||
|
@ -22,12 +29,12 @@ public interface Digest {
|
||||||
* @param msg
|
* @param msg
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
void update(Message msg);
|
public void update(Message msg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the digest for further use.
|
* Resets the digest for further use.
|
||||||
*/
|
*/
|
||||||
void reset();
|
public void reset();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clone the current digest state
|
* Clone the current digest state
|
||||||
|
|
|
@ -5,11 +5,13 @@ import com.google.protobuf.Message;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.security.UnrecoverableKeyException;
|
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
import static meerkat.protobuf.Crypto.*;
|
import static meerkat.protobuf.Crypto.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,6 +73,20 @@ public interface DigitalSignature {
|
||||||
*/
|
*/
|
||||||
public boolean verify();
|
public boolean verify();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a keystore from an input stream in PKCS12 format.
|
||||||
|
*
|
||||||
|
* @param keyStream
|
||||||
|
* @param password
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
* @throws CertificateException
|
||||||
|
* @throws KeyStoreException
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
*/
|
||||||
|
public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password)
|
||||||
|
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a private signing key. The keystore must include both the public and private
|
* Loads a private signing key. The keystore must include both the public and private
|
||||||
* key parts.
|
* key parts.
|
||||||
|
|
|
@ -30,7 +30,10 @@ import javax.security.auth.callback.UnsupportedCallbackException;
|
||||||
*
|
*
|
||||||
* This class is not thread-safe (each thread should have its own instance).
|
* This class is not thread-safe (each thread should have its own instance).
|
||||||
*/
|
*/
|
||||||
public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignature {
|
public class ECDSASignature implements DigitalSignature {
|
||||||
|
|
||||||
|
private static GlobalCryptoSetup globalCryptoSetup = GlobalCryptoSetup.getInstance();
|
||||||
|
|
||||||
final Logger logger = LoggerFactory.getLogger(getClass());
|
final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
final public static String KEYSTORE_TYPE = "PKCS12";
|
final public static String KEYSTORE_TYPE = "PKCS12";
|
||||||
|
@ -208,6 +211,7 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur
|
||||||
* @throws KeyStoreException
|
* @throws KeyStoreException
|
||||||
* @throws NoSuchAlgorithmException
|
* @throws NoSuchAlgorithmException
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password)
|
public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password)
|
||||||
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
|
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
|
||||||
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
|
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
|
||||||
|
|
|
@ -31,7 +31,10 @@ import java.util.Random;
|
||||||
/**
|
/**
|
||||||
* Created by talm on 17/11/15.
|
* Created by talm on 17/11/15.
|
||||||
*/
|
*/
|
||||||
public class ECElGamalEncryption extends GlobalCryptoSetup implements Encryption {
|
public class ECElGamalEncryption implements Encryption {
|
||||||
|
|
||||||
|
private static GlobalCryptoSetup globalCryptoSetup = GlobalCryptoSetup.getInstance();
|
||||||
|
|
||||||
final Logger logger = LoggerFactory.getLogger(getClass());
|
final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
public final static String KEY_ALGORITHM = "ECDH";
|
public final static String KEY_ALGORITHM = "ECDH";
|
||||||
|
|
|
@ -10,13 +10,26 @@ import java.security.Security;
|
||||||
/**
|
/**
|
||||||
* A class that performs required crypto setup
|
* A class that performs required crypto setup
|
||||||
*/
|
*/
|
||||||
public class GlobalCryptoSetup {
|
public final class GlobalCryptoSetup {
|
||||||
|
|
||||||
|
private static GlobalCryptoSetup globalCryptoSetup;
|
||||||
|
|
||||||
final static Logger logger = LoggerFactory.getLogger(GlobalCryptoSetup.class);
|
final static Logger logger = LoggerFactory.getLogger(GlobalCryptoSetup.class);
|
||||||
|
|
||||||
static boolean loadedBouncyCastle = false;
|
private boolean loadedBouncyCastle = false;
|
||||||
static Provider bouncyCastleProvider;
|
private Provider bouncyCastleProvider;
|
||||||
|
|
||||||
public static boolean hasSecp256k1Curve() {
|
private GlobalCryptoSetup() { doSetup(); }
|
||||||
|
|
||||||
|
public static GlobalCryptoSetup getInstance() {
|
||||||
|
if (globalCryptoSetup == null) {
|
||||||
|
globalCryptoSetup = new GlobalCryptoSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return globalCryptoSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSecp256k1Curve() {
|
||||||
// For now we just check if the java version is at least 8
|
// For now we just check if the java version is at least 8
|
||||||
String[] version = System.getProperty("java.version").split("\\.");
|
String[] version = System.getProperty("java.version").split("\\.");
|
||||||
int major = Integer.parseInt(version[0]);
|
int major = Integer.parseInt(version[0]);
|
||||||
|
@ -24,9 +37,11 @@ public class GlobalCryptoSetup {
|
||||||
return ((major > 1) || ((major > 0) && (minor > 7)));
|
return ((major > 1) || ((major > 0) && (minor > 7)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Provider getBouncyCastleProvider() { doSetup(); return bouncyCastleProvider; }
|
public Provider getBouncyCastleProvider() {
|
||||||
|
return bouncyCastleProvider;
|
||||||
|
}
|
||||||
|
|
||||||
public static synchronized void doSetup() {
|
public void doSetup() {
|
||||||
if (bouncyCastleProvider == null) {
|
if (bouncyCastleProvider == null) {
|
||||||
bouncyCastleProvider = new BouncyCastleProvider();
|
bouncyCastleProvider = new BouncyCastleProvider();
|
||||||
// Make bouncycastle our default provider if we're running on a JVM version < 8
|
// Make bouncycastle our default provider if we're running on a JVM version < 8
|
||||||
|
@ -39,5 +54,4 @@ public class GlobalCryptoSetup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GlobalCryptoSetup() { doSetup(); }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package meerkat.crypto.concrete;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
import meerkat.crypto.Digest;
|
import meerkat.crypto.Digest;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.MessageID;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ import java.security.NoSuchAlgorithmException;
|
||||||
/**
|
/**
|
||||||
* Created by talm on 11/9/15.
|
* Created by talm on 11/9/15.
|
||||||
*/
|
*/
|
||||||
public class SHA256Digest extends GlobalCryptoSetup implements Digest {
|
public class SHA256Digest implements Digest {
|
||||||
final Logger logger = LoggerFactory.getLogger(getClass());
|
final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
public static final String SHA256 = "SHA-256";
|
public static final String SHA256 = "SHA-256";
|
||||||
|
|
||||||
|
@ -60,6 +61,11 @@ public class SHA256Digest extends GlobalCryptoSetup implements Digest {
|
||||||
return hash.digest();
|
return hash.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID digestAsMessageID() {
|
||||||
|
return MessageID.newBuilder().setID(ByteString.copyFrom(digest())).build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(Message msg) {
|
public void update(Message msg) {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
package meerkat.util;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import meerkat.crypto.DigitalSignature;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 21-Feb-16.
|
||||||
|
* This class contains methods used to generate random Bulletin Board Messages
|
||||||
|
*/
|
||||||
|
public class BulletinBoardMessageGenerator {
|
||||||
|
|
||||||
|
private Random random;
|
||||||
|
|
||||||
|
public BulletinBoardMessageGenerator(Random random) {
|
||||||
|
this.random = random;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte randomByte(){
|
||||||
|
return (byte) random.nextInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String randomString(){
|
||||||
|
return new BigInteger(130, random).toString(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a complete instance of a BulletinBoardMessage
|
||||||
|
* @param signers contains the (possibly multiple) credentials required to sign the message
|
||||||
|
* @param timestamp contains the time used in the message
|
||||||
|
* @param dataSize is the length of the data contained in the message
|
||||||
|
* @param tagNumber is the number of tags to generate
|
||||||
|
* @return a random, signed Bulletin Board Message containing random data and tags and the given timestamp
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BulletinBoardMessage generateRandomMessage(DigitalSignature[] signers, Timestamp timestamp, int dataSize, int tagNumber)
|
||||||
|
throws SignatureException {
|
||||||
|
|
||||||
|
// Generate random data.
|
||||||
|
|
||||||
|
byte[] data = new byte[dataSize];
|
||||||
|
String[] tags = new String[tagNumber];
|
||||||
|
|
||||||
|
for (int i = 0; i < dataSize; i++) {
|
||||||
|
data[i] = randomByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < tagNumber; i++) {
|
||||||
|
tags[i] = randomString();
|
||||||
|
}
|
||||||
|
|
||||||
|
UnsignedBulletinBoardMessage unsignedMessage =
|
||||||
|
UnsignedBulletinBoardMessage.newBuilder()
|
||||||
|
.setData(ByteString.copyFrom(data))
|
||||||
|
.setTimestamp(timestamp)
|
||||||
|
.addAllTag(Arrays.asList(tags))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BulletinBoardMessage.Builder messageBuilder =
|
||||||
|
BulletinBoardMessage.newBuilder()
|
||||||
|
.setMsg(unsignedMessage);
|
||||||
|
|
||||||
|
for (int i = 0 ; i < signers.length ; i++) {
|
||||||
|
signers[i].updateContent(unsignedMessage);
|
||||||
|
messageBuilder.addSig(signers[i].sign());
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageBuilder.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a complete instance of a BulletinBoardMessage
|
||||||
|
* @param signers contains the (possibly multiple) credentials required to sign the message
|
||||||
|
* @param dataSize is the length of the data contained in the message
|
||||||
|
* @param tagNumber is the number of tags to generate
|
||||||
|
* @return a random, signed Bulletin Board Message containing random data, tags and timestamp
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BulletinBoardMessage generateRandomMessage(DigitalSignature[] signers, int dataSize, int tagNumber)
|
||||||
|
throws SignatureException {
|
||||||
|
|
||||||
|
Timestamp timestamp = Timestamp.newBuilder()
|
||||||
|
.setSeconds(random.nextLong())
|
||||||
|
.setNanos(random.nextInt())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return generateRandomMessage(signers, timestamp, dataSize, tagNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package meerkat.util;
|
||||||
|
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 16-Feb-16.
|
||||||
|
*/
|
||||||
|
public class BulletinBoardUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the tags in the message for one that begins with given prefix
|
||||||
|
* @param message is the message to search
|
||||||
|
* @param prefix is the given prefix
|
||||||
|
* @return the tag without the prefix, if found, or null if not found
|
||||||
|
*/
|
||||||
|
public static String findTagWithPrefix(BulletinBoardMessage message, String prefix) {
|
||||||
|
|
||||||
|
for (String tag : message.getMsg().getTagList()){
|
||||||
|
if (tag.startsWith(prefix)) {
|
||||||
|
return tag.substring(prefix.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the tags in a message for tags that do not contain a given list of prefixes
|
||||||
|
* @param message is the message to search
|
||||||
|
* @param prefixes is the list of prefixes
|
||||||
|
* @return a list of the tags that do *not* contain any of the given prefixes
|
||||||
|
*/
|
||||||
|
public static List<String> removePrefixTags(BulletinBoardMessage message, Iterable<String> prefixes) {
|
||||||
|
|
||||||
|
if (prefixes == null)
|
||||||
|
return message.getMsg().getTagList();
|
||||||
|
|
||||||
|
List<String> result = new LinkedList<>();
|
||||||
|
|
||||||
|
for (String tag : message.getMsg().getTagList()){
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
for (String prefix : prefixes){
|
||||||
|
if (tag.startsWith(prefix)){
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
result.add(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates a Timestamp Protobuf from a time specification
|
||||||
|
* @param timeInMillis is the time to encode since the Epoch time in milliseconds
|
||||||
|
* @return a Timestamp Protobuf encoding of the given time
|
||||||
|
*/
|
||||||
|
public static com.google.protobuf.Timestamp toTimestampProto(long timeInMillis) {
|
||||||
|
|
||||||
|
return com.google.protobuf.Timestamp.newBuilder()
|
||||||
|
.setSeconds(timeInMillis / 1000)
|
||||||
|
.setNanos((int) ((timeInMillis % 1000) * 1000000))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates a Timestamp Protobuf from the current system time
|
||||||
|
* @return a Timestamp Protobuf encoding of the current system time
|
||||||
|
*/
|
||||||
|
public static com.google.protobuf.Timestamp getCurrentTimestampProto() {
|
||||||
|
|
||||||
|
return toTimestampProto(System.currentTimeMillis());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method converts an SQL Timestamp object into a Protobuf Timestamp object
|
||||||
|
* @param sqlTimestamp is the SQL Timestamp
|
||||||
|
* @return an equivalent Protobuf Timestamp
|
||||||
|
*/
|
||||||
|
public static com.google.protobuf.Timestamp toTimestampProto(java.sql.Timestamp sqlTimestamp) {
|
||||||
|
return toTimestampProto(sqlTimestamp.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method converts a Protobuf Timestamp object into an SQL Timestamp object
|
||||||
|
* @param protoTimestamp is the Protobuf Timestamp
|
||||||
|
* @return an equivalent SQL Timestamp
|
||||||
|
*/
|
||||||
|
public static java.sql.Timestamp toSQLTimestamp(com.google.protobuf.Timestamp protoTimestamp) {
|
||||||
|
return new java.sql.Timestamp(protoTimestamp.getSeconds() * 1000 + protoTimestamp.getNanos() / 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package meerkat.util;
|
||||||
|
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 20-Feb-16.
|
||||||
|
*/
|
||||||
|
public class TimestampComparator implements Comparator<com.google.protobuf.Timestamp> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Timestamp o1, Timestamp o2) {
|
||||||
|
|
||||||
|
if (o1.getSeconds() != o2.getSeconds()){
|
||||||
|
|
||||||
|
return o1.getSeconds() > o2.getSeconds() ? 2 : -2;
|
||||||
|
|
||||||
|
} else if (o1.getNanos() != o2.getNanos()){
|
||||||
|
|
||||||
|
return o1.getNanos() > o2.getNanos() ? 1 : -1;
|
||||||
|
|
||||||
|
} else{
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ package meerkat;
|
||||||
option java_package = "meerkat.protobuf";
|
option java_package = "meerkat.protobuf";
|
||||||
|
|
||||||
import 'meerkat/crypto.proto';
|
import 'meerkat/crypto.proto';
|
||||||
|
import 'google/protobuf/timestamp.proto';
|
||||||
|
|
||||||
message BoolMsg {
|
message BoolMsg {
|
||||||
bool value = 1;
|
bool value = 1;
|
||||||
|
@ -21,11 +22,14 @@ message MessageID {
|
||||||
}
|
}
|
||||||
|
|
||||||
message UnsignedBulletinBoardMessage {
|
message UnsignedBulletinBoardMessage {
|
||||||
// Optional tags describing message
|
// Optional tags describing message; Used for message retrieval
|
||||||
repeated string tag = 1;
|
repeated string tag = 1;
|
||||||
|
|
||||||
|
// Timestamp of the message (as defined by client)
|
||||||
|
google.protobuf.Timestamp timestamp = 2;
|
||||||
|
|
||||||
// The actual content of the message
|
// The actual content of the message
|
||||||
bytes data = 2;
|
bytes data = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BulletinBoardMessage {
|
message BulletinBoardMessage {
|
||||||
|
@ -38,6 +42,7 @@ message BulletinBoardMessage {
|
||||||
|
|
||||||
// Signature of message (and tags), excluding the entry number.
|
// Signature of message (and tags), excluding the entry number.
|
||||||
repeated meerkat.Signature sig = 3;
|
repeated meerkat.Signature sig = 3;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message BulletinBoardMessageList {
|
message BulletinBoardMessageList {
|
||||||
|
@ -50,13 +55,16 @@ enum FilterType {
|
||||||
MSG_ID = 0; // Match exact message ID
|
MSG_ID = 0; // Match exact message ID
|
||||||
EXACT_ENTRY = 1; // Match exact entry number in database (chronological)
|
EXACT_ENTRY = 1; // Match exact entry number in database (chronological)
|
||||||
MAX_ENTRY = 2; // Find all entries in database up to specified entry number (chronological)
|
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)
|
MIN_ENTRY = 3; // Find all entries in database starting from specified entry number (chronological)
|
||||||
TAG = 4; // Find all entries in database that have a specific tag
|
SIGNER_ID = 4; // Find all entries in database that correspond to specific signature (signer)
|
||||||
|
TAG = 5; // Find all entries in database that have a specific tag
|
||||||
|
AFTER_TIME = 6; // Find all entries in database that occurred on or after a given timestamp
|
||||||
|
BEFORE_TIME = 7; // Find all entries in database that occurred on or before a given timestamp
|
||||||
|
|
||||||
// NOTE: The MAX_MESSAGES filter must remain the last filter type
|
// 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
|
// 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
|
// 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
|
MAX_MESSAGES = 8; // Return at most some specified number of messages
|
||||||
}
|
}
|
||||||
|
|
||||||
message MessageFilter {
|
message MessageFilter {
|
||||||
|
@ -68,6 +76,7 @@ message MessageFilter {
|
||||||
int64 entry = 3;
|
int64 entry = 3;
|
||||||
string tag = 4;
|
string tag = 4;
|
||||||
int64 maxMessages = 5;
|
int64 maxMessages = 5;
|
||||||
|
google.protobuf.Timestamp timestamp = 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,4 +86,74 @@ message MessageFilterList {
|
||||||
// To be implemented using intersection ("AND") operations.
|
// To be implemented using intersection ("AND") operations.
|
||||||
repeated MessageFilter filter = 1;
|
repeated MessageFilter filter = 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// This message is used to start a batch transfer to the Bulletin Board Server
|
||||||
|
message BeginBatchMessage {
|
||||||
|
bytes signerId = 1; // Unique signer identifier
|
||||||
|
int32 batchId = 2; // Unique identifier for the batch (unique per signer)
|
||||||
|
repeated string tag = 3; // Tags for the batch message
|
||||||
|
}
|
||||||
|
|
||||||
|
// This message is used to finalize and sign a batch transfer to the Bulletin Board Server
|
||||||
|
message CloseBatchMessage {
|
||||||
|
int32 batchId = 1; // Unique identifier for the batch (unique per signer)
|
||||||
|
int32 batchLength = 2; // Number of messages in the batch
|
||||||
|
google.protobuf.Timestamp timestamp = 3; // Timestamp of the batch (as defined by client)
|
||||||
|
meerkat.Signature sig = 4; // Signature on the (ordered) batch messages
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container for single batch message data
|
||||||
|
message BatchData {
|
||||||
|
bytes data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of BatchData; Only used for testing
|
||||||
|
message BatchDataList {
|
||||||
|
repeated BatchData data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These messages comprise a batch message
|
||||||
|
message BatchMessage {
|
||||||
|
bytes signerId = 1; // Unique signer identifier
|
||||||
|
int32 batchId = 2; // Unique identifier for the batch (unique per signer)
|
||||||
|
int32 serialNum = 3; // Location of the message in the batch: starting from 0
|
||||||
|
BatchData data = 4; // Actual data
|
||||||
|
}
|
||||||
|
|
||||||
|
// This message defines which batch to read and from which location to start reading
|
||||||
|
message BatchSpecificationMessage {
|
||||||
|
bytes signerId = 1; // Unique signer identifier
|
||||||
|
int32 batchId = 2; // Unique identifier for the batch (unique per signer)
|
||||||
|
int32 startPosition = 3; // Position in batch to start reading from
|
||||||
|
}
|
||||||
|
|
||||||
|
// This message is used to define a single query to the server to ascertain whether or not the server is synched with the client
|
||||||
|
// up till a specified timestamp
|
||||||
|
message SingleSyncQuery {
|
||||||
|
|
||||||
|
google.protobuf.Timestamp timeOfSync = 1;
|
||||||
|
int64 checksum = 2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// This message defines a complete server sync query
|
||||||
|
message SyncQuery {
|
||||||
|
|
||||||
|
MessageFilterList filterList = 1;
|
||||||
|
|
||||||
|
repeated SingleSyncQuery query = 2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// This message defines the server's response format to a sync query
|
||||||
|
message SyncQueryResponse {
|
||||||
|
|
||||||
|
// Serial entry number of current last entry in database
|
||||||
|
// Set to zero (0) in case no query checksums match
|
||||||
|
int64 lastEntryNum = 1;
|
||||||
|
|
||||||
|
// Largest value of timestamp for which the checksums match
|
||||||
|
google.protobuf.Timestamp lastTimeOfSync = 2;
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package meerkat.comm;
|
||||||
|
|
||||||
|
import com.google.protobuf.*;
|
||||||
|
import meerkat.comm.MessageInputStream.MessageInputStreamFactory;
|
||||||
|
import meerkat.protobuf.BulletinBoardAPI.*;
|
||||||
|
import meerkat.protobuf.Crypto;
|
||||||
|
import meerkat.util.BulletinBoardMessageComparator;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.core.Is.is;
|
||||||
|
import static org.hamcrest.core.IsEqual.equalTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 21-Feb-16.
|
||||||
|
* Tests for MessageInputStream and MessageOutputStream classes
|
||||||
|
*/
|
||||||
|
public class MessageStreamTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithBulletinBoardMessages() {
|
||||||
|
|
||||||
|
MessageOutputStream<BulletinBoardMessage> out;
|
||||||
|
MessageInputStream<BulletinBoardMessage> in;
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
BulletinBoardMessageComparator comparator = new BulletinBoardMessageComparator();
|
||||||
|
|
||||||
|
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};
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
out = new MessageOutputStream<>(stream);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
assertThat("Error creating streams: " + e.getMessage(), false);
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BulletinBoardMessage message = BulletinBoardMessage.newBuilder()
|
||||||
|
.setEntryNum(1)
|
||||||
|
.setMsg(UnsignedBulletinBoardMessage.newBuilder()
|
||||||
|
.setData(ByteString.copyFrom(b1))
|
||||||
|
.addTag("Test")
|
||||||
|
.addTag("1234")
|
||||||
|
.setTimestamp(com.google.protobuf.Timestamp.newBuilder()
|
||||||
|
.setSeconds(19823451)
|
||||||
|
.setNanos(2134)
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.addSig(Crypto.Signature.newBuilder()
|
||||||
|
.setSignerId(ByteString.copyFrom(b2))
|
||||||
|
.setData(ByteString.copyFrom(b3))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
out.writeMessage(message);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
assertThat("Error writing message: " + e.getMessage(), false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
in = MessageInputStreamFactory.createMessageInputStream(
|
||||||
|
new ByteArrayInputStream(stream.toByteArray()),
|
||||||
|
BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
assertThat("Retrieved message was not identical to send message", comparator.compare(message, in.readMessage()), is(equalTo(0)));
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
assertThat("Error reading message: " + e.getMessage(), false);
|
||||||
|
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
assertThat("Error creating input stream " + e.getMessage(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,9 @@ import java.security.spec.InvalidKeySpecException;
|
||||||
* utilities for ECElgamal
|
* utilities for ECElgamal
|
||||||
*/
|
*/
|
||||||
public class ECElGamalUtils {
|
public class ECElGamalUtils {
|
||||||
|
|
||||||
|
private static GlobalCryptoSetup globalCryptoSetup = GlobalCryptoSetup.getInstance();
|
||||||
|
|
||||||
final static Logger logger = LoggerFactory.getLogger(ECElGamalUtils.class);
|
final static Logger logger = LoggerFactory.getLogger(ECElGamalUtils.class);
|
||||||
|
|
||||||
public final static String ENCRYPTION_KEY_ALGORITHM = "ECDH";
|
public final static String ENCRYPTION_KEY_ALGORITHM = "ECDH";
|
||||||
|
@ -43,7 +46,7 @@ public class ECElGamalUtils {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KeyFactory fact = KeyFactory.getInstance(ENCRYPTION_KEY_ALGORITHM,
|
KeyFactory fact = KeyFactory.getInstance(ENCRYPTION_KEY_ALGORITHM,
|
||||||
GlobalCryptoSetup.getBouncyCastleProvider());
|
globalCryptoSetup.getBouncyCastleProvider());
|
||||||
PublicKey javaPk = fact.generatePublic(pubKeySpec);
|
PublicKey javaPk = fact.generatePublic(pubKeySpec);
|
||||||
ConcreteCrypto.ElGamalPublicKey serializedPk = ConcreteCrypto.ElGamalPublicKey.newBuilder()
|
ConcreteCrypto.ElGamalPublicKey serializedPk = ConcreteCrypto.ElGamalPublicKey.newBuilder()
|
||||||
.setSubjectPublicKeyInfo(ByteString.copyFrom(javaPk.getEncoded())).build();
|
.setSubjectPublicKeyInfo(ByteString.copyFrom(javaPk.getEncoded())).build();
|
||||||
|
|
|
@ -5,8 +5,4 @@ package meerkat.rest;
|
||||||
*/
|
*/
|
||||||
public interface Constants {
|
public interface Constants {
|
||||||
public static final String MEDIATYPE_PROTOBUF = "application/x-protobuf";
|
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";
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue