diff --git a/.gitignore b/.gitignore index 6339218..6c07527 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,16 @@ -.gradle -.idea -build -bin -.settings -.classpath -.project -out -*.iml -*.ipr -*.iws -**/*.swp -*.prefs -*.project -*.classpath -bulletin-board-server/local-instances/meerkat.db +.gradle +.idea +build +bin +.settings +.classpath +.project +out +*.iml +*.ipr +*.iws +**/*.swp +*.prefs +*.project +*.classpath +bulletin-board-server/local-instances/meerkat.db diff --git a/build.gradle b/build.gradle index 9f070e3..e3f1a0c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ - -subprojects { proj -> - proj.afterEvaluate { - // Used to generate initial maven-dir layout - task "create-dirs" { description = "Create default maven directory structure" } << { - sourceSets*.java.srcDirs*.each { it.mkdirs() } - sourceSets*.resources.srcDirs*.each { it.mkdirs() } - } - } -} + +subprojects { proj -> + proj.afterEvaluate { + // Used to generate initial maven-dir layout + task "create-dirs" { description = "Create default maven directory structure" } << { + sourceSets*.java.srcDirs*.each { it.mkdirs() } + sourceSets*.resources.srcDirs*.each { it.mkdirs() } + } + } +} diff --git a/build.gradle-template b/build.gradle-template index 60ec7c9..074667f 100644 --- a/build.gradle-template +++ b/build.gradle-template @@ -1,220 +1,220 @@ - -plugins { - id "us.kirchmeier.capsule" version "1.0.1" - id 'com.google.protobuf' version '0.7.0' -} - -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' - -apply plugin: 'maven-publish' - -// Uncomment the lines below to define an application -// (this will also allow you to build a "fatCapsule" which includes -// the entire application, including all dependencies in a single jar) -//apply plugin: 'application' -//mainClassName='your.main.ApplicationClass' - -// Is this a snapshot version? -ext { isSnapshot = false } - -ext { - groupId = 'org.factcenter.meerkat' - nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" - - // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) - // Should be set in ${HOME}/.gradle/gradle.properties - nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" - nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" -} - -description = "TODO: Add a description" - -// Your project version -version = "0.0" - -version += "${isSnapshot ? '-SNAPSHOT' : ''}" - - -dependencies { - // Meerkat common - compile project(':meerkat-common') - - // Logging - compile 'org.slf4j:slf4j-api:1.7.7' - runtime 'ch.qos.logback:logback-classic:1.1.2' - runtime 'ch.qos.logback:logback-core:1.1.2' - - // Google protobufs - compile 'com.google.protobuf:protobuf-java:3.+' - - testCompile 'junit:junit:4.+' - - runtime 'org.codehaus.groovy:groovy:2.4.+' -} - - -/*==== You probably don't have to edit below this line =======*/ - -// Setup test configuration that can appear as a dependency in -// other subprojects -configurations { - testOutput.extendsFrom (testCompile) -} - -task testJar(type: Jar, dependsOn: testClasses) { - classifier = 'tests' - from sourceSets.test.output -} - -artifacts { - testOutput testJar -} - -// The run task added by the application plugin -// is also of type JavaExec. -tasks.withType(JavaExec) { - // Assign all Java system properties from - // the command line to the JavaExec task. - systemProperties System.properties -} - - -protobuf { - // Configure the protoc executable - protoc { - // Download from repositories - artifact = 'com.google.protobuf:protoc:3.+' - } -} - - -idea { - module { - project.sourceSets.each { sourceSet -> - - def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" - - println "Adding $srcDir" - // add protobuf generated sources to generated source dir. - if ("test".equals(sourceSet.name)) { - testSourceDirs += file(srcDir) - } else { - sourceDirs += file(srcDir) - } - generatedSourceDirs += file(srcDir) - - } - - // Don't exclude build directory - excludeDirs -= file(buildDir) - } -} - - -/*=================================== - * "Fat" Build targets - *===================================*/ - - -if (project.hasProperty('mainClassName') && (mainClassName != null)) { - - task mavenCapsule(type: MavenCapsule) { - description = "Generate a capsule jar that automatically downloads and caches dependencies when run." - applicationClass mainClassName - destinationDir = buildDir - } - - task fatCapsule(type: FatCapsule) { - description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" - - destinationDir = buildDir - - def fatMain = hasProperty('fatmain') ? fatmain : mainClassName - - applicationClass fatMain - - def testJar = hasProperty('test') - - if (hasProperty('fatmain')) { - appendix = "fat-${fatMain}" - } else { - appendix = "fat" - } - - if (testJar) { - from sourceSets.test.output - } - } -} - - -/*=================================== - * Repositories - *===================================*/ - -repositories { - - // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) - maven { - url nexusRepository - - if (isSnapshot) { - credentials { username - password - - username nexusUser - password nexusPassword - } - } - } - - // Use local maven repository - mavenLocal() - - // Use 'maven central' for other dependencies. - mavenCentral() -} - -task "info" << { - println "Project: ${project.name}" -println "Description: ${project.description}" - println "--------------------------" - println "GroupId: $groupId" - println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" - println "" -} -info.description 'Print some information about project parameters' - - -/*=================================== - * Publishing - *===================================*/ - -publishing { - publications { - mavenJava(MavenPublication) { - groupId project.groupId - pom.withXml { - asNode().appendNode('description', project.description) - } - from project.components.java - - } - } - repositories { - maven { - url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" - credentials { username - password - - username nexusUser - password nexusPassword - } - } - } -} - - - + +plugins { + id "us.kirchmeier.capsule" version "1.0.1" + id 'com.google.protobuf' version '0.7.0' +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' + +apply plugin: 'maven-publish' + +// Uncomment the lines below to define an application +// (this will also allow you to build a "fatCapsule" which includes +// the entire application, including all dependencies in a single jar) +//apply plugin: 'application' +//mainClassName='your.main.ApplicationClass' + +// Is this a snapshot version? +ext { isSnapshot = false } + +ext { + groupId = 'org.factcenter.meerkat' + nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" + + // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) + // Should be set in ${HOME}/.gradle/gradle.properties + nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" + nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" +} + +description = "TODO: Add a description" + +// Your project version +version = "0.0" + +version += "${isSnapshot ? '-SNAPSHOT' : ''}" + + +dependencies { + // Meerkat common + compile project(':meerkat-common') + + // Logging + compile 'org.slf4j:slf4j-api:1.7.7' + runtime 'ch.qos.logback:logback-classic:1.1.2' + runtime 'ch.qos.logback:logback-core:1.1.2' + + // Google protobufs + compile 'com.google.protobuf:protobuf-java:3.+' + + testCompile 'junit:junit:4.+' + + runtime 'org.codehaus.groovy:groovy:2.4.+' +} + + +/*==== You probably don't have to edit below this line =======*/ + +// Setup test configuration that can appear as a dependency in +// other subprojects +configurations { + testOutput.extendsFrom (testCompile) +} + +task testJar(type: Jar, dependsOn: testClasses) { + classifier = 'tests' + from sourceSets.test.output +} + +artifacts { + testOutput testJar +} + +// The run task added by the application plugin +// is also of type JavaExec. +tasks.withType(JavaExec) { + // Assign all Java system properties from + // the command line to the JavaExec task. + systemProperties System.properties +} + + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.+' + } +} + + +idea { + module { + project.sourceSets.each { sourceSet -> + + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + println "Adding $srcDir" + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) + } +} + + +/*=================================== + * "Fat" Build targets + *===================================*/ + + +if (project.hasProperty('mainClassName') && (mainClassName != null)) { + + task mavenCapsule(type: MavenCapsule) { + description = "Generate a capsule jar that automatically downloads and caches dependencies when run." + applicationClass mainClassName + destinationDir = buildDir + } + + task fatCapsule(type: FatCapsule) { + description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" + + destinationDir = buildDir + + def fatMain = hasProperty('fatmain') ? fatmain : mainClassName + + applicationClass fatMain + + def testJar = hasProperty('test') + + if (hasProperty('fatmain')) { + appendix = "fat-${fatMain}" + } else { + appendix = "fat" + } + + if (testJar) { + from sourceSets.test.output + } + } +} + + +/*=================================== + * Repositories + *===================================*/ + +repositories { + + // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) + maven { + url nexusRepository + + if (isSnapshot) { + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } + + // Use local maven repository + mavenLocal() + + // Use 'maven central' for other dependencies. + mavenCentral() +} + +task "info" << { + println "Project: ${project.name}" +println "Description: ${project.description}" + println "--------------------------" + println "GroupId: $groupId" + println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" + println "" +} +info.description 'Print some information about project parameters' + + +/*=================================== + * Publishing + *===================================*/ + +publishing { + publications { + mavenJava(MavenPublication) { + groupId project.groupId + pom.withXml { + asNode().appendNode('description', project.description) + } + from project.components.java + + } + } + repositories { + maven { + url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } +} + + + diff --git a/bulletin-board-client/build.gradle b/bulletin-board-client/build.gradle new file mode 100644 index 0000000..8fdeba0 --- /dev/null +++ b/bulletin-board-client/build.gradle @@ -0,0 +1,242 @@ + +plugins { + id "us.kirchmeier.capsule" version "1.0.1" + id 'com.google.protobuf' version '0.7.0' +} + +apply plugin: 'java' +apply plugin: 'com.google.protobuf' +apply plugin: 'eclipse' +apply plugin: 'idea' + +apply plugin: 'maven-publish' + +// Is this a snapshot version? +ext { isSnapshot = false } + +ext { + groupId = 'org.factcenter.meerkat' + nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" + + // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) + // Should be set in ${HOME}/.gradle/gradle.properties + nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" + nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" +} + +description = "Meerkat Voting Common Library" + +// Your project version +version = "0.0" + +version += "${isSnapshot ? '-SNAPSHOT' : ''}" + + +dependencies { + + // Meerkat common + compile project(':meerkat-common') + compile project(':restful-api-common') + + // Jersey for RESTful API + compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.22.+' + compile 'org.xerial:sqlite-jdbc:3.7.+' + + // Logging + compile 'org.slf4j:slf4j-api:1.7.7' + runtime 'ch.qos.logback:logback-classic:1.1.2' + runtime 'ch.qos.logback:logback-core:1.1.2' + + // Google protobufs + compile 'com.google.protobuf:protobuf-java:3.+' + + // Crypto + compile 'org.factcenter.qilin:qilin:1.1+' + compile 'org.bouncycastle:bcprov-jdk15on:1.53' + compile 'org.bouncycastle:bcpkix-jdk15on:1.53' + + // Depend on test resources from meerkat-common + testCompile project(path: ':meerkat-common', configuration: 'testOutput') + + // Depend on server compilation for the non-integration tests + testCompile project(path: ':bulletin-board-server') + + testCompile 'junit:junit:4.+' + testCompile 'org.hamcrest:hamcrest-all:1.3' + + runtime 'org.codehaus.groovy:groovy:2.4.+' +} + +test { + exclude '**/*IntegrationTest*' + outputs.upToDateWhen { false } +} + +task integrationTest(type: Test) { + include '**/*IntegrationTest*' +// debug = true + outputs.upToDateWhen { false } + +} + +/*==== You probably don't have to edit below this line =======*/ + + +// Setup test configuration that can appear as a dependency in +// other subprojects +configurations { + testOutput.extendsFrom (testCompile) +} + +task testJar(type: Jar, dependsOn: testClasses) { + classifier = 'tests' + from sourceSets.test.output +} + +artifacts { + testOutput testJar +} + + + +// The run task added by the application plugin +// is also of type JavaExec. +tasks.withType(JavaExec) { + // Assign all Java system properties from + // the command line to the JavaExec task. + systemProperties System.properties +} + + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.+' + } +} + +idea { + module { + project.sourceSets.each { sourceSet -> + + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) + } +} + +/*=================================== + * "Fat" Build targets + *===================================*/ + +if (project.hasProperty('mainClassName') && (mainClassName != null)) { + + task mavenCapsule(type: MavenCapsule) { + description = "Generate a capsule jar that automatically downloads and caches dependencies when run." + applicationClass mainClassName + destinationDir = buildDir + } + + task fatCapsule(type: FatCapsule) { + description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" + + destinationDir = buildDir + + def fatMain = hasProperty('fatmain') ? fatmain : mainClassName + + applicationClass fatMain + + def testJar = hasProperty('test') + + if (hasProperty('fatmain')) { + appendix = "fat-${fatMain}" + } else { + appendix = "fat" + } + + if (testJar) { + from sourceSets.test.output + } + } + +} + +/*=================================== + * Repositories + *===================================*/ + +repositories { + + mavenLocal(); + + // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) + maven { + url nexusRepository + + if (isSnapshot) { + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } + + // Use 'maven central' for other dependencies. + mavenCentral() +} + +task "info" << { + println "Project: ${project.name}" + println "Description: ${project.description}" + println "--------------------------" + println "GroupId: $groupId" + println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" + println "" +} +info.description 'Print some information about project parameters' + + +/*=================================== + * Publishing + *===================================*/ + +publishing { + publications { + mavenJava(MavenPublication) { + groupId project.groupId + pom.withXml { + asNode().appendNode('description', project.description) + } + from project.components.java + + } + } + repositories { + maven { + url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } +} + + + diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BatchDataContainer.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BatchDataContainer.java new file mode 100644 index 0000000..53f4870 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BatchDataContainer.java @@ -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 batchDataList; + public final int startPosition; + + public BatchDataContainer(byte[] signerId, int batchId, List batchDataList, int startPosition) { + this.signerId = signerId; + this.batchId = batchId; + this.batchDataList = batchDataList; + this.startPosition = startPosition; + } +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJob.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJob.java deleted file mode 100644 index aca98d4..0000000 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJob.java +++ /dev/null @@ -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 serverAddresses; - - private int minServers; // The minimal number of servers the job must be successful on for the job to be completed - - private final JobType jobType; - - private final Message payload; // The information associated with the job type - - private int maxRetry; // Number of retries for this job; set to -1 for infinite retries - - public BulletinClientJob(List serverAddresses, int minServers, JobType jobType, Message payload, int maxRetry) { - this.serverAddresses = serverAddresses; - this.minServers = minServers; - this.jobType = jobType; - this.payload = payload; - this.maxRetry = maxRetry; - } - - public void updateServerAddresses(List newServerAdresses) { - this.serverAddresses = newServerAdresses; - } - - public List getServerAddresses() { - return serverAddresses; - } - - public int getMinServers() { - return minServers; - } - - public JobType getJobType() { - return jobType; - } - - public Message getPayload() { - return payload; - } - - public int getMaxRetry() { - return maxRetry; - } - - public void shuffleAddresses() { - Collections.shuffle(serverAddresses); - } - - public void decMinServers(){ - minServers--; - } - - public void decMaxRetry(){ - if (maxRetry > 0) { - maxRetry--; - } - } - - public boolean isRetry(){ - return (maxRetry != 0); - } - -} \ No newline at end of file diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJobResult.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJobResult.java deleted file mode 100644 index be0501b..0000000 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientJobResult.java +++ /dev/null @@ -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; - } - -} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientWorker.java index 51599d5..52ac7cb 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientWorker.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BulletinClientWorker.java @@ -1,217 +1,38 @@ -package meerkat.bulletinboard; - -import com.google.protobuf.Message; -import meerkat.comm.CommunicationException; -import meerkat.crypto.Digest; -import meerkat.crypto.concrete.SHA256Digest; -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.rest.Constants; -import meerkat.rest.ProtobufMessageBodyReader; -import meerkat.rest.ProtobufMessageBodyWriter; - -import javax.ws.rs.ProcessingException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Response; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.Callable; - -/** - * Created by Arbel Deutsch Peled on 09-Dec-15. - * - * This class implements the actual communication with the Bulletin Board Servers. - * It is meant to be used in a multi-threaded environment. - */ -//TODO: Maybe make this abstract and inherit from it. -public class BulletinClientWorker implements Callable { - - private final BulletinClientJob job; // The requested job to be handled - - public BulletinClientWorker(BulletinClientJob job){ - this.job = job; - } - - // This resource enabled creation of a single Client per thread. - private static final ThreadLocal clientLocal = - new ThreadLocal () { - @Override protected Client initialValue() { - Client client; - client = ClientBuilder.newClient(); - client.register(ProtobufMessageBodyReader.class); - client.register(ProtobufMessageBodyWriter.class); - - return client; - } - }; - - // This resource enables creation of a single Digest per thread. - private static final ThreadLocal digestLocal = - new ThreadLocal () { - @Override protected Digest initialValue() { - Digest digest; - digest = new SHA256Digest(); //TODO: Make this generic. - - return digest; - } - }; - - /** - * This method carries out the actual communication with the servers via HTTP Post - * It accesses the servers according to the job it received and updates said job as it goes - * The method will only iterate once through the server list, removing servers from the list when they are no longer required - * In a POST_MESSAGE job: successful post to a server results in removing the server from the list - * In a GET_REDUNDANCY job: no server is removed from the list and the (absolute) number of servers in which the message was found is returned - * In a READ_MESSAGES job: successful retrieval from any server terminates the method and returns the received values; The list is not changed - * @return The original job, modified to fit the current state and the required output (if any) of the operation - * @throws IllegalArgumentException - * @throws CommunicationException - */ - public BulletinClientJobResult call() throws IllegalArgumentException, CommunicationException{ - - Client client = clientLocal.get(); - Digest digest = digestLocal.get(); - - WebTarget webTarget; - Response response; - - String requestPath; - Message msg; - - List serverAddresses = new LinkedList(job.getServerAddresses()); - - Message payload = job.getPayload(); - - BulletinBoardMessageList msgList; - - int count = 0; // Used to count number of servers which contain the required message in a GET_REDUNDANCY request. - - job.shuffleAddresses(); // This is done to randomize the order of access to servers primarily for READ operations - - // Prepare the request. - switch(job.getJobType()) { - - case POST_MESSAGE: - // Make sure the payload is a BulletinBoardMessage - if (!(payload instanceof BulletinBoardMessage)) { - throw new IllegalArgumentException("Cannot post an object that is not an instance of BulletinBoardMessage"); - } - - msg = payload; - requestPath = Constants.POST_MESSAGE_PATH; - break; - - case READ_MESSAGES: - // Make sure the payload is a MessageFilterList - if (!(payload instanceof MessageFilterList)) { - throw new IllegalArgumentException("Read failed: an instance of MessageFilterList is required as payload for a READ_MESSAGES operation"); - } - - msg = payload; - requestPath = Constants.READ_MESSAGES_PATH; - break; - - case GET_REDUNDANCY: - // Make sure the payload is a MessageId - if (!(payload instanceof MessageID)) { - throw new IllegalArgumentException("Cannot search for an object that is not an instance of MessageID"); - } - - requestPath = Constants.READ_MESSAGES_PATH; - - msg = MessageFilterList.newBuilder() - .addFilter(MessageFilter.newBuilder() - .setType(FilterType.MSG_ID) - .setId(((MessageID) payload).getID()) - .build() - ).build(); - - break; - - default: - throw new IllegalArgumentException("Unsupported job type"); - - } - - // Iterate through servers - - Iterator addressIterator = serverAddresses.iterator(); - - while (addressIterator.hasNext()) { - - // Send request to Server - String address = addressIterator.next(); - webTarget = client.target(address).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(requestPath); - response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); - - // Retrieve answer - switch(job.getJobType()) { - - case POST_MESSAGE: - try { - - response.readEntity(BoolMsg.class); // If a BoolMsg entity is returned: the post was successful - addressIterator.remove(); // Post to this server succeeded: remove server from list - job.decMinServers(); - - } catch (ProcessingException | IllegalStateException e) {} // Post to this server failed: retry next time - finally { - response.close(); - } - break; - - case GET_REDUNDANCY: - try { - msgList = response.readEntity(BulletinBoardMessageList.class); // If a BulletinBoardMessageList is returned: the read was successful - - if (msgList.getMessageList().size() > 0){ // Message was found in the server. - count++; - } - } catch (ProcessingException | IllegalStateException e) {} // Read failed: try with next server - finally { - response.close(); - } - break; - - case READ_MESSAGES: - try { - msgList = response.readEntity(BulletinBoardMessageList.class); // If a BulletinBoardMessageList is returned: the read was successful - return new BulletinClientJobResult(job, msgList); // Return the result - } catch (ProcessingException | IllegalStateException e) {} // Read failed: try with next server - finally { - response.close(); - } - break; - - } - - } - - // Return result (if haven't done so yet) - switch(job.getJobType()) { - - case POST_MESSAGE: - // The job now contains the information required to ascertain whether enough server posts have succeeded - // It will also contain the list of servers in which the post was not successful - job.updateServerAddresses(serverAddresses); - return new BulletinClientJobResult(job, null); - - case GET_REDUNDANCY: - // Return the number of servers in which the message was found - // The job now contains the list of these servers - return new BulletinClientJobResult(job, IntMsg.newBuilder().setValue(count).build()); - - case READ_MESSAGES: - // A successful operation would have already returned an output - // Therefore: no server access was successful - throw new CommunicationException("Could not access any server"); - - default: // This is required for successful compilation - throw new IllegalArgumentException("Unsupported job type"); - - } - } -} +package meerkat.bulletinboard; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + * + * This class handles bulletin client work. + * It is meant to be used in a multi-threaded environment. + */ +public abstract class BulletinClientWorker { + + protected final IN payload; // Payload of the job + + private int maxRetry; // Number of retries for this job; set to -1 for infinite retries + + public BulletinClientWorker(IN payload, int maxRetry) { + this.payload = payload; + this.maxRetry = maxRetry; + } + + public IN getPayload() { + return payload; + } + + public int getMaxRetry() { + return maxRetry; + } + + public void decMaxRetry(){ + if (maxRetry > 0) { + maxRetry--; + } + } + + public boolean isRetry(){ + return (maxRetry != 0); + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/CachedBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/CachedBulletinBoardClient.java new file mode 100644 index 0000000..96ba76d --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/CachedBulletinBoardClient.java @@ -0,0 +1,168 @@ +package meerkat.bulletinboard; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.ByteString; +import meerkat.comm.CommunicationException; +import meerkat.protobuf.BulletinBoardAPI; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Voting.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executors; + +/** + * Created by Arbel Deutsch Peled on 03-Mar-16. + * This is a full-fledged implementation of a Bulletin Board Client + * It provides asynchronous access to several remote servers, as well as a local cache + * Read/write operations are performed on the local server + * After any read is carried out, a subscription is made for the specific query to make sure the local DB will be updated + * The database also employs a synchronizer which makes sure local data is sent to the remote servers + */ +public class CachedBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient { + + private final BulletinBoardClient localClient; + private AsyncBulletinBoardClient remoteClient; + private BulletinBoardSubscriber subscriber; + + private final int threadPoolSize; + private final long failDelayInMilliseconds; + private final long subscriptionIntervalInMilliseconds; + + public CachedBulletinBoardClient(BulletinBoardClient localClient, + int threadPoolSize, + long failDelayInMilliseconds, + long subscriptionIntervalInMilliseconds) + throws IllegalAccessException, InstantiationException { + + this.localClient = localClient; + this.threadPoolSize = threadPoolSize; + this.failDelayInMilliseconds = failDelayInMilliseconds; + this.subscriptionIntervalInMilliseconds = subscriptionIntervalInMilliseconds; + + remoteClient = new ThreadedBulletinBoardClient(); + + } + + @Override + public MessageID postMessage(BulletinBoardMessage msg, FutureCallback callback) { + return null; + } + + @Override + public MessageID postBatch(CompleteBatch completeBatch, FutureCallback callback) { + return null; + } + + @Override + public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback callback) { + + } + + @Override + public void postBatchData(byte[] signerId, int batchId, List batchDataList, int startPosition, FutureCallback callback) { + + } + + @Override + public void postBatchData(byte[] signerId, int batchId, List batchDataList, FutureCallback callback) { + + } + + @Override + public void postBatchData(ByteString signerId, int batchId, List batchDataList, int startPosition, FutureCallback callback) { + + } + + @Override + public void postBatchData(ByteString signerId, int batchId, List batchDataList, FutureCallback callback) { + + } + + @Override + public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback callback) { + + } + + @Override + public void getRedundancy(MessageID id, FutureCallback callback) { + + } + + @Override + public void readMessages(MessageFilterList filterList, FutureCallback> callback) { + + } + + @Override + public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback callback) { + + } + + @Override + public void querySync(SyncQuery syncQuery, FutureCallback callback) { + + } + + @Override + public void init(BulletinBoardClientParams clientParams) { + + remoteClient.init(clientParams); + + ListeningScheduledExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadPoolSize)); + + List subscriberClients = new ArrayList<>(clientParams.getBulletinBoardAddressCount()); + + for (String address : clientParams.getBulletinBoardAddressList()){ + + SubscriptionAsyncBulletinBoardClient newClient = + new SingleServerBulletinBoardClient(executorService, failDelayInMilliseconds, subscriptionIntervalInMilliseconds); + + newClient.init(clientParams.toBuilder().clearBulletinBoardAddress().addBulletinBoardAddress(address).build()); + + subscriberClients.add(newClient); + + } + + subscriber = new ThreadedBulletinBoardSubscriber(subscriberClients, localClient); + + } + + @Override + public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { + return null; + } + + @Override + public float getRedundancy(MessageID id) { + return 0; + } + + @Override + public List readMessages(MessageFilterList filterList) { + return null; + } + + @Override + public SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) throws CommunicationException { + return null; + } + + @Override + public void close() { + + } + + @Override + public void subscribe(MessageFilterList filterList, FutureCallback> callback) { + + } + + @Override + public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback> callback) { + + } +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/LocalBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/LocalBulletinBoardClient.java new file mode 100644 index 0000000..df3e196 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/LocalBulletinBoardClient.java @@ -0,0 +1,531 @@ +package meerkat.bulletinboard; + +import com.google.common.util.concurrent.*; +import com.google.protobuf.ByteString; +import meerkat.comm.CommunicationException; +import meerkat.comm.MessageInputStream; +import meerkat.comm.MessageInputStream.MessageInputStreamFactory; +import meerkat.comm.MessageOutputStream; +import meerkat.crypto.concrete.SHA256Digest; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Voting.*; +import meerkat.util.BulletinBoardUtils; + +import javax.ws.rs.NotFoundException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Created by Arbel Deutsch Peled on 15-Mar-16. + * This client is to be used mainly for testing. + * It wraps a BulletinBoardServer in an asynchronous client. + * This means the access to the server is direct (via method calls) instead of through a TCP connection. + * The client implements both synchronous and asynchronous method calls, but calls to the server itself are performed synchronously. + */ +public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient{ + + private final BulletinBoardServer server; + private final ListeningScheduledExecutorService executorService; + private final BatchDigest digest; + private final int subsrciptionDelay; + + /** + * Initializes an instance of the client + * @param server an initialized Bulletin Board Server instance which will perform the actual processing of the requests + * @param threadNum is the number of concurrent threads to allocate for the client + * @param subscriptionDelay is the required delay between subscription calls in milliseconds + */ + public LocalBulletinBoardClient(BulletinBoardServer server, int threadNum, int subscriptionDelay) { + this.server = server; + this.executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadNum)); + this.digest = new GenericBatchDigest(new SHA256Digest()); + this.subsrciptionDelay = subscriptionDelay; + } + + private class MessagePoster implements Callable { + + private final BulletinBoardMessage msg; + + public MessagePoster(BulletinBoardMessage msg) { + this.msg = msg; + } + + + @Override + public Boolean call() throws Exception { + return server.postMessage(msg).getValue(); + } + + } + + @Override + public MessageID postMessage(BulletinBoardMessage msg, FutureCallback callback) { + + Futures.addCallback(executorService.submit(new MessagePoster(msg)), callback); + + digest.update(msg.getMsg()); + return digest.digestAsMessageID(); + + } + + private class CompleteBatchPoster implements Callable { + + private final CompleteBatch completeBatch; + + public CompleteBatchPoster(CompleteBatch completeBatch) { + this.completeBatch = completeBatch; + } + + + @Override + public Boolean call() throws Exception { + + if (!server.beginBatch(completeBatch.getBeginBatchMessage()).getValue()) + return false; + + int i=0; + for (BatchData data : completeBatch.getBatchDataList()){ + + BatchMessage message = BatchMessage.newBuilder() + .setSignerId(completeBatch.getSignature().getSignerId()) + .setBatchId(completeBatch.getBeginBatchMessage().getBatchId()) + .setSerialNum(i) + .setData(data) + .build(); + + if (!server.postBatchMessage(message).getValue()) + return false; + + i++; + } + + return server.closeBatchMessage(completeBatch.getCloseBatchMessage()).getValue(); + } + + } + + @Override + public MessageID postBatch(CompleteBatch completeBatch, FutureCallback callback) { + + Futures.addCallback(executorService.schedule(new CompleteBatchPoster(completeBatch), subsrciptionDelay, TimeUnit.MILLISECONDS), callback); + + digest.update(completeBatch); + return digest.digestAsMessageID(); + + } + + private class BatchBeginner implements Callable { + + private final BeginBatchMessage msg; + + public BatchBeginner(BeginBatchMessage msg) { + this.msg = msg; + } + + + @Override + public Boolean call() throws Exception { + return server.beginBatch(msg).getValue(); + } + + } + + @Override + public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback callback) { + Futures.addCallback(executorService.submit(new BatchBeginner(beginBatchMessage)), callback); + } + + private class BatchDataPoster implements Callable { + + private final ByteString signerId; + private final int batchId; + private final List batchDataList; + private final int startPosition; + + public BatchDataPoster(ByteString signerId, int batchId, List batchDataList, int startPosition) { + this.signerId = signerId; + this.batchId = batchId; + this.batchDataList = batchDataList; + this.startPosition = startPosition; + } + + + @Override + public Boolean call() throws Exception { + + BatchMessage.Builder msgBuilder = BatchMessage.newBuilder() + .setSignerId(signerId) + .setBatchId(batchId); + + int i = startPosition; + for (BatchData data : batchDataList){ + + msgBuilder.setSerialNum(i) + .setData(data); + + if (!server.postBatchMessage(msgBuilder.build()).getValue()) + return false; + + i++; + + } + + return true; + + } + + } + + @Override + public void postBatchData(byte[] signerId, int batchId, List batchDataList, int startPosition, FutureCallback callback) { + postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback); + } + + @Override + public void postBatchData(byte[] signerId, int batchId, List batchDataList, FutureCallback callback) { + postBatchData(signerId, batchId, batchDataList, 0, callback); + } + + @Override + public void postBatchData(ByteString signerId, int batchId, List batchDataList, int startPosition, FutureCallback callback) { + Futures.addCallback(executorService.submit(new BatchDataPoster(signerId, batchId, batchDataList, startPosition)), callback); + } + + @Override + public void postBatchData(ByteString signerId, int batchId, List batchDataList, FutureCallback callback) { + postBatchData(signerId, batchId, batchDataList, 0, callback); + } + + private class BatchCloser implements Callable { + + private final CloseBatchMessage msg; + + public BatchCloser(CloseBatchMessage msg) { + this.msg = msg; + } + + + @Override + public Boolean call() throws Exception { + return server.closeBatchMessage(msg).getValue(); + } + + } + + @Override + public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback callback) { + Futures.addCallback(executorService.submit(new BatchCloser(closeBatchMessage)), callback); + } + + private class RedundancyGetter implements Callable { + + private final MessageID msgId; + + public RedundancyGetter(MessageID msgId) { + this.msgId = msgId; + } + + + @Override + public Float call() throws Exception { + + MessageFilterList filterList = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.MSG_ID) + .setId(msgId.getID()) + .build()) + .build(); + + ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + MessageOutputStream outputStream = new MessageOutputStream<>(byteOutputStream); + server.readMessages(filterList,outputStream); + + MessageInputStream inputStream = + MessageInputStreamFactory.createMessageInputStream( + new ByteArrayInputStream(byteOutputStream.toByteArray()), + BulletinBoardMessage.class); + + if (inputStream.isAvailable()) + return 1.0f; + else + return 0.0f; + + } + + } + + @Override + public void getRedundancy(MessageID id, FutureCallback callback) { + Futures.addCallback(executorService.submit(new RedundancyGetter(id)), callback); + } + + private class MessageReader implements Callable> { + + private final MessageFilterList filterList; + + public MessageReader(MessageFilterList filterList) { + this.filterList = filterList; + } + + + @Override + public List call() throws Exception { + + ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + MessageOutputStream outputStream = new MessageOutputStream<>(byteOutputStream); + server.readMessages(filterList, outputStream); + + MessageInputStream inputStream = + MessageInputStreamFactory.createMessageInputStream( + new ByteArrayInputStream(byteOutputStream.toByteArray()), + BulletinBoardMessage.class); + + return inputStream.asList(); + + } + + } + + @Override + public void readMessages(MessageFilterList filterList, FutureCallback> callback) { + Futures.addCallback(executorService.submit(new MessageReader(filterList)), callback); + } + + class SubscriptionCallback implements FutureCallback> { + + private MessageFilterList filterList; + private final FutureCallback> callback; + + public SubscriptionCallback(MessageFilterList filterList, FutureCallback> callback) { + this.filterList = filterList; + this.callback = callback; + } + + @Override + public void onSuccess(List result) { + + // Report new messages to user + callback.onSuccess(result); + + MessageFilterList.Builder filterBuilder = filterList.toBuilder(); + + // If any new messages arrived: update the MIN_ENTRY condition + if (result.size() > 0) { + + // 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()); + + } + + filterList = filterBuilder.build(); + + // Reschedule job + Futures.addCallback(executorService.submit(new MessageReader(filterList)), this); + + } + + @Override + public void onFailure(Throwable t) { + + // Notify caller about failure and terminate subscription + callback.onFailure(t); + + } + } + + @Override + public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback> callback) { + + MessageFilterList subscriptionFilterList = + filterList.toBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.MIN_ENTRY) + .setEntry(startEntry) + .build()) + .build(); + + Futures.addCallback(executorService.submit(new MessageReader(subscriptionFilterList)), new SubscriptionCallback(subscriptionFilterList, callback)); + + } + + @Override + public void subscribe(MessageFilterList filterList, FutureCallback> callback) { + subscribe(filterList, 0, callback); + } + + private class CompleteBatchReader implements Callable { + + private final BatchSpecificationMessage batchSpecificationMessage; + + public CompleteBatchReader(BatchSpecificationMessage batchSpecificationMessage) { + this.batchSpecificationMessage = batchSpecificationMessage; + } + + + @Override + public CompleteBatch call() throws Exception { + + final String[] TAGS_TO_REMOVE = {BulletinBoardConstants.BATCH_TAG, BulletinBoardConstants.BATCH_ID_TAG_PREFIX}; + + CompleteBatch completeBatch = new CompleteBatch(BeginBatchMessage.newBuilder() + .setSignerId(batchSpecificationMessage.getSignerId()) + .setBatchId(batchSpecificationMessage.getBatchId()) + .build()); + + ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + MessageOutputStream batchOutputStream = new MessageOutputStream<>(byteOutputStream); + server.readBatch(batchSpecificationMessage,batchOutputStream); + + MessageInputStream batchInputStream = + MessageInputStreamFactory.createMessageInputStream( + new ByteArrayInputStream(byteOutputStream.toByteArray()), + BatchData.class); + + completeBatch.appendBatchData(batchInputStream.asList()); + + 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 + completeBatch.getBeginBatchMessage().getBatchId()) + .build()) + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.SIGNER_ID) + .setId(completeBatch.getBeginBatchMessage().getSignerId()) + .build()) + .build(); + + byteOutputStream = new ByteArrayOutputStream(); + MessageOutputStream messageOutputStream = new MessageOutputStream<>(byteOutputStream); + server.readMessages(filterList,messageOutputStream); + + MessageInputStream messageInputStream = + MessageInputStreamFactory.createMessageInputStream( + new ByteArrayInputStream(byteOutputStream.toByteArray()), + BulletinBoardMessage.class); + + if (!messageInputStream.isAvailable()) + throw new NotFoundException("Batch does not exist"); + + BulletinBoardMessage message = messageInputStream.readMessage(); + + completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder() + .addAllTag(BulletinBoardUtils.removePrefixTags(message, Arrays.asList(TAGS_TO_REMOVE))) + .setSignerId(message.getSig(0).getSignerId()) + .setBatchId(Integer.parseInt(BulletinBoardUtils.findTagWithPrefix(message, BulletinBoardConstants.BATCH_ID_TAG_PREFIX))) + .build()); + + completeBatch.setSignature(message.getSig(0)); + completeBatch.setTimestamp(message.getMsg().getTimestamp()); + + return completeBatch; + + } + + } + + @Override + public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback callback) { + Futures.addCallback(executorService.submit(new CompleteBatchReader(batchSpecificationMessage)), callback); + } + + private class SyncQueryHandler implements Callable { + + private final SyncQuery syncQuery; + + public SyncQueryHandler(SyncQuery syncQuery) { + this.syncQuery = syncQuery; + } + + + @Override + public SyncQueryResponse call() throws Exception { + return server.querySync(syncQuery); + } + + } + + @Override + public void querySync(SyncQuery syncQuery, FutureCallback callback) { + Futures.addCallback(executorService.submit(new SyncQueryHandler(syncQuery)), callback); + } + + /** + * This method is a stub, since the implementation only considers one server, and that is given in the constructor + * @param ignored is ignored + */ + @Override + public void init(BulletinBoardClientParams ignored) {} + + @Override + public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { + + try { + + MessagePoster poster = new MessagePoster(msg); + poster.call(); + + digest.update(msg); + return digest.digestAsMessageID(); + + } catch (Exception e) { + return null; + } + + } + + @Override + public float getRedundancy(MessageID id) { + + try { + + RedundancyGetter getter = new RedundancyGetter(id); + return getter.call(); + + } catch (Exception e) { + return -1.0f; + } + + } + + @Override + public List readMessages(MessageFilterList filterList) { + + try { + + MessageReader reader = new MessageReader(filterList); + return reader.call(); + + } catch (Exception e){ + return null; + } + + } + + @Override + public SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) throws CommunicationException { + return server.generateSyncQuery(GenerateSyncQueryParams); + } + + @Override + public void close() { + try { + server.close(); + } catch (CommunicationException ignored) {} + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/MultiServerWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/MultiServerWorker.java new file mode 100644 index 0000000..7347f47 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/MultiServerWorker.java @@ -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 extends BulletinClientWorker implements Runnable, FutureCallback{ + + private final List 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 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 clients, boolean shuffleClients, + int minServers, IN payload, int maxRetry, + FutureCallback 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 clients, + int minServers, IN payload, int maxRetry, + FutureCallback 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 getClientIterator() { + return clients.iterator(); + } + + protected int getClientNumber() { + return clients.size(); + } + +} \ No newline at end of file diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java index f340cae..74eacae 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java @@ -1,161 +1,192 @@ -package meerkat.bulletinboard; - -import com.google.protobuf.ByteString; -import meerkat.comm.CommunicationException; -import meerkat.crypto.Digest; -import meerkat.crypto.concrete.SHA256Digest; -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.protobuf.Voting; -import meerkat.protobuf.Voting.BulletinBoardClientParams; -import meerkat.rest.*; - -import java.util.List; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Response; - -/** - * Created by Arbel Deutsch Peled on 05-Dec-15. - */ -public class SimpleBulletinBoardClient{ //implements BulletinBoardClient { - - private List meerkatDBs; - - private Client client; - - private Digest digest; - - /** - * Stores database locations and initializes the web Client - * @param clientParams contains the data needed to access the DBs - */ -// @Override - public void init(Voting.BulletinBoardClientParams clientParams) { - - meerkatDBs = clientParams.getBulletinBoardAddressList(); - - client = ClientBuilder.newClient(); - client.register(ProtobufMessageBodyReader.class); - client.register(ProtobufMessageBodyWriter.class); - - digest = new SHA256Digest(); - - } - - /** - * Post message to all DBs - * Make only one try per DB. - * @param msg is the message, - * @return the message ID for later retrieval - * @throws CommunicationException - */ -// @Override - public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { - - WebTarget webTarget; - Response response; - - // Post message to all databases - try { - for (String db : meerkatDBs) { - webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.POST_MESSAGE_PATH); - response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); - - // Only consider valid responses - if (response.getStatusInfo() == Response.Status.OK - || response.getStatusInfo() == Response.Status.CREATED) { - response.readEntity(BoolMsg.class).getValue(); - } - } - } catch (Exception e) { // Occurs only when server replies with valid status but invalid data - throw new CommunicationException("Error accessing database: " + e.getMessage()); - } - - // Calculate the correct message ID and return it - digest.reset(); - digest.update(msg.getMsg()); - return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build(); - } - - /** - * Access each database and search for a given message ID - * Return the number of databases in which the message was found - * Only try once per DB - * Ignore communication exceptions in specific databases - * @param id is the requested message ID - * @return the number of DBs in which retrieval was successful - */ -// @Override - public float getRedundancy(MessageID id) { - WebTarget webTarget; - Response response; - - MessageFilterList filterList = MessageFilterList.newBuilder() - .addFilter(MessageFilter.newBuilder() - .setType(FilterType.MSG_ID) - .setId(id.getID()) - .build()) - .build(); - - float count = 0; - - for (String db : meerkatDBs) { - try { - webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH); - - response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); - - if (response.readEntity(BulletinBoardMessageList.class).getMessageCount() > 0){ - count++; - } - - } catch (Exception e) {} - } - - return count / ((float) meerkatDBs.size()); - } - - /** - * Go through the DBs and try to retrieve messages according to the specified filter - * If at the operation is successful for some DB: return the results and stop iterating - * If no operation is successful: return null (NOT blank list) - * @param filterList return only messages that match the filters (null means no filtering). - * @return - */ -// @Override - public List readMessages(MessageFilterList filterList) { - WebTarget webTarget; - Response response; - BulletinBoardMessageList messageList; - - // Replace null filter list with blank one. - if (filterList == null){ - filterList = MessageFilterList.newBuilder().build(); - } - - for (String db : meerkatDBs) { - try { - webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH); - - response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); - - messageList = response.readEntity(BulletinBoardMessageList.class); - - if (messageList != null){ - return messageList.getMessageList(); - } - - } catch (Exception e) {} - } - - return null; - } - -// @Override -// public void registerNewMessageCallback(MessageCallback callback, MessageFilterList filterList) { -// callback.handleNewMessage(readMessages(filterList)); -// } -} +package meerkat.bulletinboard; + +import com.google.protobuf.BoolValue; +import com.google.protobuf.ByteString; +import meerkat.comm.CommunicationException; +import meerkat.crypto.Digest; +import meerkat.crypto.concrete.SHA256Digest; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Comm.*; +import meerkat.protobuf.Voting.*; +import meerkat.rest.*; + +import java.util.List; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +import static meerkat.bulletinboard.BulletinBoardConstants.*; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + * Implements BulletinBoardClient interface in a simple, straightforward manner + */ +public class SimpleBulletinBoardClient implements BulletinBoardClient{ + + protected List meerkatDBs; + + protected Client client; + + protected Digest digest; + + /** + * Stores database locations and initializes the web Client + * @param clientParams contains the data needed to access the DBs + */ + @Override + public void init(BulletinBoardClientParams clientParams) { + + this.meerkatDBs = clientParams.getBulletinBoardAddressList(); + + client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + + digest = new SHA256Digest(); + + } + + /** + * Post message to all DBs + * Make only one try per DB. + * @param msg is the message, + * @return the message ID for later retrieval + * @throws CommunicationException + */ + @Override + public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { + + WebTarget webTarget; + Response response; + + // Post message to all databases + try { + for (String db : meerkatDBs) { + webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH); + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); + + // Only consider valid responses + if (response.getStatusInfo() == Response.Status.OK + || response.getStatusInfo() == Response.Status.CREATED) { + response.readEntity(BoolValue.class).getValue(); + } + } + } catch (Exception e) { // Occurs only when server replies with valid status but invalid data + throw new CommunicationException("Error accessing database: " + e.getMessage()); + } + + // Calculate the correct message ID and return it + digest.reset(); + digest.update(msg.getMsg()); + return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build(); + } + + /** + * Access each database and search for a given message ID + * Return the number of databases in which the message was found + * Only try once per DB + * Ignore communication exceptions in specific databases + * @param id is the requested message ID + * @return the number of DBs in which retrieval was successful + */ + @Override + public float getRedundancy(MessageID id) { + WebTarget webTarget; + Response response; + + MessageFilterList filterList = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.MSG_ID) + .setId(id.getID()) + .build()) + .build(); + + float count = 0; + + for (String db : meerkatDBs) { + try { + webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH); + + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); + + if (response.readEntity(BulletinBoardMessageList.class).getMessageCount() > 0){ + count++; + } + + } catch (Exception e) {} + } + + return count / ((float) meerkatDBs.size()); + } + + /** + * Go through the DBs and try to retrieve messages according to the specified filter + * If at the operation is successful for some DB: return the results and stop iterating + * If no operation is successful: return null (NOT blank list) + * @param filterList return only messages that match the filters (null means no filtering). + * @return the list of Bulletin Board messages that are returned from a server + */ + @Override + public List readMessages(MessageFilterList filterList) { + + WebTarget webTarget; + Response response; + BulletinBoardMessageList messageList; + + // Replace null filter list with blank one. + if (filterList == null){ + filterList = MessageFilterList.newBuilder().build(); + } + + for (String db : meerkatDBs) { + + try { + webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH); + + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); + + messageList = response.readEntity(BulletinBoardMessageList.class); + + if (messageList != null){ + return messageList.getMessageList(); + } + + } catch (Exception e) {} + + } + + return null; + + } + + @Override + public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException { + + WebTarget webTarget; + Response response; + + for (String db : meerkatDBs) { + + try { + webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH); + + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(generateSyncQueryParams, Constants.MEDIATYPE_PROTOBUF)); + + return response.readEntity(SyncQuery.class); + + } catch (Exception e) {} + + } + + throw new CommunicationException("Could not contact any server"); + + } + + public void close() { + client.close(); + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerBulletinBoardClient.java new file mode 100644 index 0000000..5edd079 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerBulletinBoardClient.java @@ -0,0 +1,626 @@ +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 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 SubscriptionAsyncBulletinBoardClient { + + 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 implements FutureCallback { + + private final SingleServerWorker worker; + private final FutureCallback futureCallback; + + public RetryCallback(SingleServerWorker worker, FutureCallback 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 { + + private final FutureCallback callback; + + private AtomicInteger batchDataRemaining; + private AtomicBoolean aggregatedResult; + + public PostBatchDataListCallback(int batchDataLength, FutureCallback 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 callback; + + private List batchDataList; + private BulletinBoardMessage batchMessage; + + private AtomicInteger remainingQueries; + private AtomicBoolean failed; + + public CompleteBatchReadCallback(FutureCallback 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> asBatchDataListFutureCallback() { + return new FutureCallback>() { + + @Override + public void onSuccess(List 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> asBulletinBoardMessageListFutureCallback() { + return new FutureCallback>() { + + @Override + public void onSuccess(List 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> { + + private SingleServerReadMessagesWorker worker; + private final FutureCallback> callback; + + private MessageFilterList.Builder filterBuilder; + + public SubscriptionCallback(SingleServerReadMessagesWorker worker, FutureCallback> callback) { + this.worker = worker; + this.callback = callback; + filterBuilder = worker.getPayload().toBuilder(); + + } + + @Override + public void onSuccess(List result) { + + // Report new messages to user + callback.onSuccess(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(); + + // Notify caller about failure and terminate subscription + callback.onFailure(t); + } + } + + public SingleServerBulletinBoardClient(ListeningScheduledExecutorService executorService, + long failDelayInMilliseconds, + long subscriptionIntervalInMilliseconds) { + + this.executorService = executorService; + + 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; + + } + + public SingleServerBulletinBoardClient(int threadPoolSize, long failDelayInMilliseconds, long subscriptionIntervalInMilliseconds) { + + this(MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadPoolSize)), + failDelayInMilliseconds, + subscriptionIntervalInMilliseconds); + + } + + /** + * 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 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 { + + private final CompleteBatch completeBatch; + private final FutureCallback callback; + + public PostBatchDataCallback(CompleteBatch completeBatch, FutureCallback 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 { + + private final CompleteBatch completeBatch; + private final FutureCallback callback; + + public BeginBatchCallback(CompleteBatch completeBatch, FutureCallback 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 callback) { + + beginBatch( + completeBatch.getBeginBatchMessage(), + new BeginBatchCallback(completeBatch, callback) + ); + + batchDigest.update(completeBatch); + + return batchDigest.digestAsMessageID(); + + } + + @Override + public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback 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 batchDataList, + int startPosition, FutureCallback 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 batchDataList, FutureCallback callback) { + + postBatchData(signerId, batchId, batchDataList, 0, callback); + + } + + @Override + public void postBatchData(byte[] signerId, int batchId, List batchDataList, + int startPosition, FutureCallback callback) { + + postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback); + + } + + @Override + public void postBatchData(byte[] signerId, int batchId, List batchDataList, FutureCallback callback) { + + postBatchData(signerId, batchId, batchDataList, 0, callback); + + } + + @Override + public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback 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 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> 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 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 querySync(SyncQuery syncQuery, FutureCallback callback) { + + SingleServerQuerySyncWorker worker = new SingleServerQuerySyncWorker(meerkatDBs.get(0), syncQuery, MAX_RETRIES); + + scheduleWorker(worker, new RetryCallback<>(worker, callback)); + + } + + @Override + public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback> callback) { + // Remove all existing MIN_ENTRY filters and create new one that starts at 0 + + MessageFilterList.Builder filterListBuilder = filterList.toBuilder(); + + Iterator 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(startEntry) + .build()); + + // Create job with no retries + SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterListBuilder.build(), MAX_RETRIES); + + // Submit job and create callback that retries on failure and handles repeated subscription + scheduleWorker(worker, new RetryCallback<>(worker, new SubscriptionCallback(worker, callback))); + } + + @Override + public void subscribe(MessageFilterList filterList, FutureCallback> callback) { + subscribe(filterList, 0, callback); + } + + @Override + public void close() { + + super.close(); + + executorService.shutdown(); + + } +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerWorker.java new file mode 100644 index 0000000..ecebc12 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerWorker.java @@ -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 extends BulletinClientWorker implements Callable{ + + // This resource enabled creation of a single Client per thread. + protected static final ThreadLocal clientLocal = + new ThreadLocal () { + @Override protected Client initialValue() { + Client client; + client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + + return client; + } + }; + + protected final String serverAddress; + + public SingleServerWorker(String serverAddress, IN payload, int maxRetry) { + super(payload, maxRetry); + this.serverAddress = serverAddress; + } + + public String getServerAddress() { + return serverAddress; + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java index bb46c32..2069859 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java @@ -1,131 +1,255 @@ -package meerkat.bulletinboard; - -import com.google.common.util.concurrent.*; -import com.google.protobuf.ByteString; -import meerkat.bulletinboard.callbacks.GetRedundancyFutureCallback; -import meerkat.bulletinboard.callbacks.PostMessageFutureCallback; -import meerkat.bulletinboard.callbacks.ReadMessagesFutureCallback; -import meerkat.comm.CommunicationException; -import meerkat.crypto.Digest; -import meerkat.crypto.concrete.SHA256Digest; -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.protobuf.Voting; - -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -/** - * Created by Arbel Deutsch Peled on 05-Dec-15. - * Thread-based implementation of a Bulletin Board Client. - * Features: - * 1. Handles tasks concurrently. - * 2. Retries submitting - */ -public class ThreadedBulletinBoardClient implements BulletinBoardClient { - - private final static int THREAD_NUM = 10; - ListeningExecutorService listeningExecutor; - - private Digest digest; - - private List meerkatDBs; - private String postSubAddress; - private String readSubAddress; - - private final static int READ_MESSAGES_RETRY_NUM = 1; - - private int minAbsoluteRedundancy; - - /** - * Stores database locations and initializes the web Client - * Stores the required minimum redundancy. - * Starts the Thread Pool. - * @param clientParams contains the required information - */ - @Override - public void init(Voting.BulletinBoardClientParams clientParams) { - - meerkatDBs = clientParams.getBulletinBoardAddressList(); - - minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * meerkatDBs.size()); - - listeningExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(THREAD_NUM)); - - digest = new SHA256Digest(); - - } - - /** - * Post message to all DBs - * Retry failed DBs - * @param msg is the message, - * @return the message ID for later retrieval - * @throws CommunicationException - */ - @Override - public MessageID postMessage(BulletinBoardMessage msg, ClientCallback callback){ - - // Create job - BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.POST_MESSAGE, msg, -1); - - // Submit job and create callback - Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new PostMessageFutureCallback(listeningExecutor, callback)); - - // Calculate the correct message ID and return it - digest.reset(); - digest.update(msg.getMsg()); - return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build(); - } - - /** - * Access each database and search for a given message ID - * Return the number of databases in which the message was found - * Only try once per DB - * Ignore communication exceptions in specific databases - * @param id is the requested message ID - * @return the number of DBs in which retrieval was successful - */ - @Override - public void getRedundancy(MessageID id, ClientCallback callback) { - - // Create job - BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.GET_REDUNDANCY, id, 1); - - // Submit job and create callback - Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new GetRedundancyFutureCallback(listeningExecutor, callback)); - - } - - /** - * Go through the DBs and try to retrieve messages according to the specified filter - * If at the operation is successful for some DB: return the results and stop iterating - * If no operation is successful: return null (NOT blank list) - * @param filterList return only messages that match the filters (null means no filtering). - * @return - */ - @Override - public void readMessages(MessageFilterList filterList, ClientCallback> callback) { - - // Create job - BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.READ_MESSAGES, - filterList, READ_MESSAGES_RETRY_NUM); - - // Submit job and create callback - Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new ReadMessagesFutureCallback(listeningExecutor, callback)); - - } - - @Override - public void close() { - try { - listeningExecutor.shutdown(); - while (! listeningExecutor.isShutdown()) { - listeningExecutor.awaitTermination(10, TimeUnit.SECONDS); - } - } catch (InterruptedException e) { - System.err.println(e.getCause() + " " + e.getMessage()); - } - } - -} +package meerkat.bulletinboard; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.ByteString; + +import meerkat.bulletinboard.workers.multiserver.*; +import meerkat.comm.CommunicationException; +import meerkat.protobuf.BulletinBoardAPI; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Voting.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + * Thread-based implementation of a Async Bulletin Board Client. + * Features: + * 1. Handles tasks concurrently. + * 2. Retries submitting + */ +public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient implements AsyncBulletinBoardClient { + + // Executor service for handling jobs + private final static int JOBS_THREAD_NUM = 5; + private ExecutorService executorService; + + // Per-server clients + private List clients; + + private BatchDigest batchDigest; + + private final static int POST_MESSAGE_RETRY_NUM = 3; + 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; + + /** + * Stores database locations and initializes the web Client + * Stores the required minimum redundancy. + * Starts the Thread Pool. + * @param clientParams contains the required information + */ + @Override + public void init(BulletinBoardClientParams clientParams) { + + super.init(clientParams); + + batchDigest = new GenericBatchDigest(digest); + + minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * (float) clientParams.getBulletinBoardAddressCount()); + + 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); + + } + + } + + /** + * Post message to all DBs + * Retry failed DBs + * @param msg is the message, + * @return the message ID for later retrieval + */ + @Override + public MessageID postMessage(BulletinBoardMessage msg, FutureCallback callback){ + + // Create job + MultiServerPostMessageWorker worker = + new MultiServerPostMessageWorker(clients, minAbsoluteRedundancy, msg, POST_MESSAGE_RETRY_NUM, callback); + + // Submit job + executorService.submit(worker); + + // Calculate the correct message ID and return it + batchDigest.reset(); + batchDigest.update(msg.getMsg()); + return batchDigest.digestAsMessageID(); + + } + + @Override + public MessageID postBatch(CompleteBatch completeBatch, FutureCallback 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 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 batchDataList, + int startPosition, FutureCallback 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 batchDataList, FutureCallback callback) { + + postBatchData(signerId, batchId, batchDataList, 0, callback); + + } + + @Override + public void postBatchData(ByteString signerId, int batchId, List batchDataList, + int startPosition, FutureCallback callback) { + + postBatchData(signerId.toByteArray(), batchId, batchDataList, startPosition, callback); + + } + + @Override + public void postBatchData(ByteString signerId, int batchId, List batchDataList, FutureCallback callback) { + + postBatchData(signerId, batchId, batchDataList, 0, callback); + + } + + @Override + public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback callback) { + + // Create job + MultiServerCloseBatchWorker worker = + new MultiServerCloseBatchWorker(clients, minAbsoluteRedundancy, closeBatchMessage, POST_MESSAGE_RETRY_NUM, callback); + + // Submit job + executorService.submit(worker); + + } + + /** + * Access each database and search for a given message ID + * Return the number of databases in which the message was found + * Only try once per DB + * Ignore communication exceptions in specific databases + */ + @Override + public void getRedundancy(MessageID id, FutureCallback callback) { + + // Create job + MultiServerGetRedundancyWorker worker = + new MultiServerGetRedundancyWorker(clients, minAbsoluteRedundancy, id, GET_REDUNDANCY_RETRY_NUM, callback); + + // Submit job + executorService.submit(worker); + + } + + /** + * Go through the DBs and try to retrieve messages according to the specified filter + * If at the operation is successful for some DB: return the results and stop iterating + * If no operation is successful: return null (NOT blank list) + */ + @Override + public void readMessages(MessageFilterList filterList, FutureCallback> callback) { + + // Create job + MultiServerReadMessagesWorker worker = + new MultiServerReadMessagesWorker(clients, minAbsoluteRedundancy, filterList, READ_MESSAGES_RETRY_NUM, callback); + + // Submit job + executorService.submit(worker); + + } + + @Override + public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback callback) { + + // Create job + MultiServerReadBatchWorker worker = + new MultiServerReadBatchWorker(clients, minAbsoluteRedundancy, batchSpecificationMessage, READ_MESSAGES_RETRY_NUM, callback); + + // Submit job + executorService.submit(worker); + + } + + /** + * This method is not supported by this class! + * This is because it has no meaning when considering more than one server without knowing which server will be contacted + */ + @Override + public void querySync(SyncQuery syncQuery, FutureCallback callback) { + callback.onFailure(new IllegalAccessError("querySync is not supported by this class")); + } + + @Override + public void close() { + super.close(); + + try { + + for (SingleServerBulletinBoardClient client : clients){ + client.close(); + } + + executorService.shutdown(); + while (! executorService.isShutdown()) { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } + } catch (InterruptedException e) { + System.err.println(e.getCause() + " " + e.getMessage()); + } + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardSubscriber.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardSubscriber.java new file mode 100644 index 0000000..cf8d47d --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardSubscriber.java @@ -0,0 +1,272 @@ +package meerkat.bulletinboard; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.Timestamp; +import meerkat.comm.CommunicationException; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.util.BulletinBoardUtils; + +import static meerkat.protobuf.BulletinBoardAPI.FilterType.*; + +import java.sql.Time; +import java.util.*; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Created by Arbel Deutsch Peled on 03-Mar-16. + * A multi-server implementation of the {@link BulletinBoardSubscriber} + */ +public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber { + + protected final Collection clients; + protected final BulletinBoardClient localClient; + + protected Iterator clientIterator; + protected SubscriptionAsyncBulletinBoardClient currentClient; + + private long lastServerSwitchTime; + + private AtomicBoolean isSyncInProgress; + private Semaphore rescheduleSemaphore; + + private static final Float[] BREAKPOINTS = {0.5f, 0.75f, 0.9f, 0.95f, 0.99f, 0.999f}; + + public ThreadedBulletinBoardSubscriber(Collection clients, BulletinBoardClient localClient) { + + this.clients = clients; + this.localClient = localClient; + + lastServerSwitchTime = System.currentTimeMillis(); + + clientIterator = clients.iterator(); + currentClient = clientIterator.next(); + + isSyncInProgress = new AtomicBoolean(false); + rescheduleSemaphore = new Semaphore(1); + + } + + /** + * Moves to next client and performs resync with it + */ + private void nextClient() { + + try { + + rescheduleSemaphore.acquire(); + + if (!clientIterator.hasNext()){ + clientIterator = clients.iterator(); + } + + currentClient = clientIterator.next(); + + lastServerSwitchTime = System.currentTimeMillis(); + + isSyncInProgress.set(false); + + rescheduleSemaphore.release(); + + } catch (InterruptedException e) { + // TODO: log + // Do not change client + } + + } + + private abstract class SubscriberCallback implements FutureCallback { + + protected final MessageFilterList filterList; + protected final FutureCallback> callback; + private final long invocationTime; + + public SubscriberCallback(MessageFilterList filterList, FutureCallback> callback) { + + this.filterList = filterList; + this.callback = callback; + this.invocationTime = System.currentTimeMillis(); + + } + + /** + * Handles resyncing process for the given subscription after a server is switched + * Specifically: generates a sync query from the local database and uses it to query the current server + */ + private void reSync() { + + SyncQuery syncQuery = null; + try { + + syncQuery = localClient.generateSyncQuery(GenerateSyncQueryParams.newBuilder() + .setFilterList(filterList) + .addAllBreakpointList(Arrays.asList(BREAKPOINTS)) + .build()); + + } catch (CommunicationException e) { + + // Handle failure in standard way + onFailure(e); + + } + + currentClient.querySync(syncQuery, new SyncQueryCallback(filterList, callback)); + + } + + /** + * Reschedules the subscription + */ + private void reschedule() { + + try { + + rescheduleSemaphore.acquire(); + + reSync(); + + rescheduleSemaphore.release(); + + + } catch (InterruptedException e) { + + //TODO: log + + callback.onFailure(e); // Hard error: Cannot guarantee subscription safety + + } + + } + + @Override + public void onFailure(Throwable t) { + + // If server failure is not already known: switch to next client and resync + if (invocationTime > lastServerSwitchTime){ + + // Make sure only what thread switches the client + if (isSyncInProgress.compareAndSet(false, true)){ + nextClient(); + } + + } + + reschedule(); + + } + + } + + /** + * Provides handling logic for resync query callback operation + * Receives a SyncQueryResponse and reads the missing data (starting from the received timestamp) if needed + */ + protected class SyncQueryCallback extends SubscriberCallback { + + public SyncQueryCallback (MessageFilterList filterList, FutureCallback> callback) { + + super(filterList, callback); + + } + + @Override + public void onSuccess(SyncQueryResponse result) { + + final Timestamp DEFAULT_TIME = BulletinBoardUtils.toTimestampProto(946728000); // Year 2000 + + // Read required messages according to received Timestamp + + Timestamp syncTimestamp; + + if (result.hasLastTimeOfSync()) { + syncTimestamp = result.getLastTimeOfSync(); // Use returned time of sync + } else { + syncTimestamp = DEFAULT_TIME; // Get all messages + } + + MessageFilterList timestampedFilterList = filterList.toBuilder() + .removeFilter(filterList.getFilterCount()-1) // Remove MIN_ENTRY filter + .addFilter(MessageFilter.newBuilder() // Add timestamp filter + .setType(AFTER_TIME) + .setTimestamp(syncTimestamp) + .build()) + .build(); + + currentClient.readMessages(timestampedFilterList, new ReSyncCallback(filterList, callback, result.getLastEntryNum())); + + } + + } + + /** + * Provides handling logic for callback of resyncing process + * Receives the missing messages, handles them and resubscribes + */ + protected class ReSyncCallback extends SubscriberCallback> { + + private long minEntry; + + public ReSyncCallback (MessageFilterList filterList, FutureCallback> callback, long minEntry) { + + super(filterList, callback); + + this.minEntry = minEntry; + + } + + @Override + public void onSuccess(List result) { + + // Propagate result to caller + callback.onSuccess(result); + + // Renew subscription + + MessageFilterList newFilterList = filterList.toBuilder() + .removeFilter(filterList.getFilterCount()-1) // Remove current MIN_ENTRY filter + .addFilter(MessageFilter.newBuilder() // Add new MIN_ENTRY filter for current server + .setType(MIN_ENTRY) + .setEntry(minEntry) + .build()) + .build(); + + currentClient.subscribe(newFilterList, callback); + + } + + } + + /** + * Provides the handling logic for results and failures of main subscription (while there are no errors) + */ + protected class SubscriptionCallback extends SubscriberCallback> { + + public SubscriptionCallback(MessageFilterList filterList, FutureCallback> callback){ + super(filterList, callback); + } + + + @Override + public void onSuccess(List result) { + + // Propagate result to caller + callback.onSuccess(result); + + } + + } + + @Override + public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback> callback) { + + currentClient.subscribe(filterList, startEntry, new SubscriptionCallback(filterList, callback)); + + } + + @Override + public void subscribe(MessageFilterList filterList, FutureCallback> callback) { + subscribe(filterList, 0, callback); + } + + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ClientFutureCallback.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ClientFutureCallback.java deleted file mode 100644 index 7c5b7b0..0000000 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ClientFutureCallback.java +++ /dev/null @@ -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 { - - protected ListeningExecutorService listeningExecutor; - - ClientFutureCallback(ListeningExecutorService listeningExecutor) { - this.listeningExecutor = listeningExecutor; - } - -} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/GetRedundancyFutureCallback.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/GetRedundancyFutureCallback.java deleted file mode 100644 index 518ed77..0000000 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/GetRedundancyFutureCallback.java +++ /dev/null @@ -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 callback; - - public GetRedundancyFutureCallback(ListeningExecutorService listeningExecutor, - BulletinBoardClient.ClientCallback callback) { - super(listeningExecutor); - this.callback = callback; - } - - @Override - public void onSuccess(BulletinClientJobResult result) { - - int absoluteRedundancy = ((IntMsg) result.getResult()).getValue(); - int totalServers = result.getJob().getServerAddresses().size(); - - callback.handleCallback( ((float) absoluteRedundancy) / ((float) totalServers) ); - - } - - @Override - public void onFailure(Throwable t) { - callback.handleFailure(t); - } -} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/PostMessageFutureCallback.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/PostMessageFutureCallback.java deleted file mode 100644 index 221ae1a..0000000 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/PostMessageFutureCallback.java +++ /dev/null @@ -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); - } -} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ReadMessagesFutureCallback.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ReadMessagesFutureCallback.java deleted file mode 100644 index 4c43ba2..0000000 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/callbacks/ReadMessagesFutureCallback.java +++ /dev/null @@ -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> callback; - - public ReadMessagesFutureCallback(ListeningExecutorService listeningExecutor, - BulletinBoardClient.ClientCallback> callback) { - super(listeningExecutor); - this.callback = callback; - } - - @Override - public void onSuccess(BulletinClientJobResult result) { - - callback.handleCallback(((BulletinBoardAPI.BulletinBoardMessageList) result.getResult()).getMessageList()); - - } - - @Override - public void onFailure(Throwable t) { - callback.handleFailure(t); - } -} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerBeginBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerBeginBatchWorker.java new file mode 100644 index 0000000..e0e92bb --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerBeginBatchWorker.java @@ -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 { + + public MultiServerBeginBatchWorker(List clients, + int minServers, BeginBatchMessage payload, int maxRetry, + FutureCallback futureCallback) { + + super(clients, minServers, payload, maxRetry, futureCallback); + + } + + @Override + protected void doPost(SingleServerBulletinBoardClient client, BeginBatchMessage payload) { + client.beginBatch(payload, this); + } + + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerCloseBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerCloseBatchWorker.java new file mode 100644 index 0000000..300440f --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerCloseBatchWorker.java @@ -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 { + + public MultiServerCloseBatchWorker(List clients, + int minServers, CloseBatchMessage payload, int maxRetry, + FutureCallback futureCallback) { + + super(clients, minServers, payload, maxRetry, futureCallback); + + } + + @Override + protected void doPost(SingleServerBulletinBoardClient client, CloseBatchMessage payload) { + client.closeBatch(payload, this); + } + + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerGenericPostWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerGenericPostWorker.java new file mode 100644 index 0000000..8172e14 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerGenericPostWorker.java @@ -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 extends MultiServerWorker { + + public MultiServerGenericPostWorker(List clients, + int minServers, T payload, int maxRetry, + FutureCallback 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 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); + } + } +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerGenericReadWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerGenericReadWorker.java new file mode 100644 index 0000000..88b4ac1 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerGenericReadWorker.java @@ -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 extends MultiServerWorker{ + + private final Iterator clientIterator; + + public MultiServerGenericReadWorker(List clients, + int minServers, IN payload, int maxRetry, + FutureCallback 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 + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerGetRedundancyWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerGetRedundancyWorker.java new file mode 100644 index 0000000..748916b --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerGetRedundancyWorker.java @@ -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 { + + private AtomicInteger serversContainingMessage; + private AtomicInteger totalContactedServers; + + public MultiServerGetRedundancyWorker(List clients, + int minServers, MessageID payload, int maxRetry, + FutureCallback 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 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); + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchDataWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchDataWorker.java new file mode 100644 index 0000000..ef1c4fa --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchDataWorker.java @@ -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 { + + public MultiServerPostBatchDataWorker(List clients, + int minServers, BatchDataContainer payload, int maxRetry, + FutureCallback 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); + } + + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchWorker.java new file mode 100644 index 0000000..1b1f3df --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchWorker.java @@ -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 { + + public MultiServerPostBatchWorker(List clients, + int minServers, CompleteBatch payload, int maxRetry, + FutureCallback futureCallback) { + + super(clients, minServers, payload, maxRetry, futureCallback); + + } + + @Override + protected void doPost(SingleServerBulletinBoardClient client, CompleteBatch payload) { + client.postBatch(payload, this); + } + + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostMessageWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostMessageWorker.java new file mode 100644 index 0000000..6d3d702 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostMessageWorker.java @@ -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 { + + public MultiServerPostMessageWorker(List clients, + int minServers, BulletinBoardMessage payload, int maxRetry, + FutureCallback futureCallback) { + + super(clients, minServers, payload, maxRetry, futureCallback); + + } + + @Override + protected void doPost(SingleServerBulletinBoardClient client, BulletinBoardMessage payload) { + client.postMessage(payload, this); + } + + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerReadBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerReadBatchWorker.java new file mode 100644 index 0000000..3d40c8a --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerReadBatchWorker.java @@ -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 { + + public MultiServerReadBatchWorker(List clients, + int minServers, BatchSpecificationMessage payload, int maxRetry, + FutureCallback futureCallback) { + + super(clients, minServers, payload, maxRetry, futureCallback); + + } + + @Override + protected void doRead(BatchSpecificationMessage payload, SingleServerBulletinBoardClient client) { + client.readBatch(payload, this); + } + + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerReadMessagesWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerReadMessagesWorker.java new file mode 100644 index 0000000..980d869 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerReadMessagesWorker.java @@ -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>{ + + public MultiServerReadMessagesWorker(List clients, + int minServers, MessageFilterList payload, int maxRetry, + FutureCallback> futureCallback) { + + super(clients, minServers, payload, maxRetry, futureCallback); + + } + + @Override + protected void doRead(MessageFilterList payload, SingleServerBulletinBoardClient client) { + client.readMessages(payload, this); + } + + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerBeginBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerBeginBatchWorker.java new file mode 100644 index 0000000..0c1a1a3 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerBeginBatchWorker.java @@ -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 { + + public SingleServerBeginBatchWorker(String serverAddress, BeginBatchMessage payload, int maxRetry) { + super(serverAddress, BEGIN_BATCH_PATH, payload, maxRetry); + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerCloseBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerCloseBatchWorker.java new file mode 100644 index 0000000..83e27c2 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerCloseBatchWorker.java @@ -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 stop batch operation + */ +public class SingleServerCloseBatchWorker extends SingleServerGenericPostWorker { + + public SingleServerCloseBatchWorker(String serverAddress, CloseBatchMessage payload, int maxRetry) { + super(serverAddress, CLOSE_BATCH_PATH, payload, maxRetry); + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGenericPostWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGenericPostWorker.java new file mode 100644 index 0000000..08a66d1 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGenericPostWorker.java @@ -0,0 +1,63 @@ +package meerkat.bulletinboard.workers.singleserver; + +import com.google.protobuf.BoolValue; +import meerkat.bulletinboard.SingleServerWorker; +import meerkat.comm.CommunicationException; +import meerkat.protobuf.Comm.*; +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 extends SingleServerWorker { + + 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 BoolValue entity is returned: the post was successful + response.readEntity(BoolValue.class); + return Boolean.TRUE; + + } catch (ProcessingException | IllegalStateException e) { + + // Post to this server failed + throw new CommunicationException("Could not contact the server"); + + } + finally { + response.close(); + } + + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGetRedundancyWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGetRedundancyWorker.java new file mode 100644 index 0000000..0401a76 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGetRedundancyWorker.java @@ -0,0 +1,87 @@ +package meerkat.bulletinboard.workers.singleserver; + +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.Response; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; + +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 { + + 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 + + // 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(msgFilterList, Constants.MEDIATYPE_PROTOBUF), InputStream.class); + + MessageInputStream inputStream = null; + + // Retrieve answer + + try { + + inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BulletinBoardMessage.class); + + if (inputStream.asList().size() > 0){ + // Message exists in the server + return 1.0f; + } + else { + // Message does not exist in the server + return 0.0f; + } + + } catch (Exception e) { + + // Read failed + throw new CommunicationException("Server access failed"); + + } finally { + try { + inputStream.close(); + } catch (IOException ignored) {} + } + + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerPostBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerPostBatchWorker.java new file mode 100644 index 0000000..dfb42a7 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerPostBatchWorker.java @@ -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 { + + public SingleServerPostBatchWorker(String serverAddress, BatchMessage payload, int maxRetry) { + super(serverAddress, POST_BATCH_PATH, payload, maxRetry); + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerPostMessageWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerPostMessageWorker.java new file mode 100644 index 0000000..454d720 --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerPostMessageWorker.java @@ -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 { + + public SingleServerPostMessageWorker(String serverAddress, BulletinBoardMessage payload, int maxRetry) { + super(serverAddress, POST_MESSAGE_PATH, payload, maxRetry); + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerQuerySyncWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerQuerySyncWorker.java new file mode 100644 index 0000000..3a9873d --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerQuerySyncWorker.java @@ -0,0 +1,59 @@ +package meerkat.bulletinboard.workers.singleserver; + +import meerkat.bulletinboard.SingleServerWorker; +import meerkat.comm.CommunicationException; +import meerkat.protobuf.BulletinBoardAPI.SyncQuery; +import meerkat.protobuf.BulletinBoardAPI.SyncQueryResponse; +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.SYNC_QUERY_PATH; + +/** + * Created by Arbel Deutsch Peled on 27-Dec-15. + * Tries to contact server once and perform a post operation + */ +public class SingleServerQuerySyncWorker extends SingleServerWorker { + + public SingleServerQuerySyncWorker(String serverAddress, SyncQuery payload, int maxRetry) { + super(serverAddress, payload, maxRetry); + } + + @Override + public SyncQueryResponse call() throws Exception { + + Client client = clientLocal.get(); + + WebTarget webTarget; + Response response; + + // Send request to Server + + webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(SYNC_QUERY_PATH); + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF)); + + // Retrieve answer + + try { + + // If a BulletinBoardMessageList is returned: the read was successful + return response.readEntity(SyncQueryResponse.class); + + } catch (ProcessingException | IllegalStateException e) { + + // Read failed + throw new CommunicationException("Server access failed"); + + } + finally { + response.close(); + } + + } +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerReadBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerReadBatchWorker.java new file mode 100644 index 0000000..57b336e --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerReadBatchWorker.java @@ -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> { + + 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 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 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) {} + } + + } + +} diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerReadMessagesWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerReadMessagesWorker.java new file mode 100644 index 0000000..d8525ab --- /dev/null +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerReadMessagesWorker.java @@ -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> { + + 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 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 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) {} + } + + } + +} diff --git a/bulletin-board-client/src/test/java/BulletinBoardClientIntegrationTest.java b/bulletin-board-client/src/test/java/BulletinBoardClientIntegrationTest.java deleted file mode 100644 index dda76c7..0000000 --- a/bulletin-board-client/src/test/java/BulletinBoardClientIntegrationTest.java +++ /dev/null @@ -1,214 +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.*; -import meerkat.util.BulletinBoardMessageComparator; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.number.OrderingComparison.*; - -import java.util.*; -import java.util.concurrent.Semaphore; - -/** - * Created by Arbel Deutsch Peled on 05-Dec-15. - */ -public class BulletinBoardClientIntegrationTest { - - Semaphore jobSemaphore; - Vector thrown; - - private class PostCallback implements ClientCallback{ - - @Override - public void handleCallback(Object msg) { - System.err.println("Post operation completed"); - jobSemaphore.release(); - } - - @Override - public void handleFailure(Throwable t) { - thrown.add(t); - jobSemaphore.release(); - } - } - - private class RedundancyCallback implements ClientCallback{ - - private float minRedundancy; - - public RedundancyCallback(float minRedundancy) { - this.minRedundancy = minRedundancy; - } - - @Override - public void handleCallback(Float redundancy) { - System.err.println("Redundancy found is: " + redundancy); - jobSemaphore.release(); - assertThat(redundancy, greaterThanOrEqualTo(minRedundancy)); - } - - @Override - public void handleFailure(Throwable t) { - thrown.add(t); - jobSemaphore.release(); - } - } - - private class ReadCallback implements ClientCallback>{ - - private List expectedMsgList; - - public ReadCallback(List expectedMsgList) { - this.expectedMsgList = expectedMsgList; - } - - @Override - public void handleCallback(List messages) { - - System.err.println(messages); - jobSemaphore.release(); - - BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator(); - - assertThat(messages.size(), is(expectedMsgList.size())); - - Iterator expectedMessageIterator = expectedMsgList.iterator(); - Iterator receivedMessageIterator = messages.iterator(); - - while (expectedMessageIterator.hasNext()) { - assertThat(msgComparator.compare(expectedMessageIterator.next(), receivedMessageIterator.next()), is(0)); - } - - } - - @Override - public void handleFailure(Throwable t) { - thrown.add(t); - jobSemaphore.release(); - } - } - - private BulletinBoardClient bulletinBoardClient; - - private PostCallback postCallback; - private RedundancyCallback redundancyCallback; - private ReadCallback readCallback; - - private static String PROP_GETTY_URL = "gretty.httpBaseURI"; - private static String DEFAULT_BASE_URL = "http://localhost:8081"; - private static String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL); - - @Before - public void init(){ - - bulletinBoardClient = new ThreadedBulletinBoardClient(); - - List testDB = new LinkedList(); - testDB.add(BASE_URL); - - bulletinBoardClient.init(BulletinBoardClientParams.newBuilder() - .addBulletinBoardAddress("http://localhost:8081") - .setMinRedundancy((float) 1.0) - .build()); - - postCallback = new PostCallback(); - redundancyCallback = new RedundancyCallback((float) 1.0); - - thrown = new Vector<>(); - jobSemaphore = new Semaphore(0); - - } - - @Test - public void postTest() { - - byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4}; - byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14}; - byte[] b3 = {(byte) 21, (byte) 22, (byte) 23, (byte) 24}; - byte[] b4 = {(byte) 4, (byte) 5, (byte) 100, (byte) -50, (byte) 0}; - - BulletinBoardMessage msg; - - MessageFilterList filterList; - List msgList; - - MessageID messageID; - - Comparator msgComparator = new BulletinBoardMessageComparator(); - - msg = BulletinBoardMessage.newBuilder() - .setMsg(UnsignedBulletinBoardMessage.newBuilder() - .addTag("Signature") - .addTag("Trustee") - .setData(ByteString.copyFrom(b1)) - .build()) - .addSig(Crypto.Signature.newBuilder() - .setType(Crypto.SignatureType.DSA) - .setData(ByteString.copyFrom(b2)) - .setSignerId(ByteString.copyFrom(b3)) - .build()) - .addSig(Crypto.Signature.newBuilder() - .setType(Crypto.SignatureType.ECDSA) - .setData(ByteString.copyFrom(b3)) - .setSignerId(ByteString.copyFrom(b2)) - .build()) - .build(); - - messageID = bulletinBoardClient.postMessage(msg,postCallback); - - try { - jobSemaphore.acquire(); - } catch (InterruptedException e) { - System.err.println(e.getCause() + " " + e.getMessage()); - } - - bulletinBoardClient.getRedundancy(messageID,redundancyCallback); - - filterList = MessageFilterList.newBuilder() - .addFilter( - MessageFilter.newBuilder() - .setType(FilterType.TAG) - .setTag("Signature") - .build() - ) - .addFilter( - MessageFilter.newBuilder() - .setType(FilterType.TAG) - .setTag("Trustee") - .build() - ) - .build(); - - msgList = new LinkedList(); - msgList.add(msg); - - readCallback = new ReadCallback(msgList); - - bulletinBoardClient.readMessages(filterList, readCallback); - try { - jobSemaphore.acquire(2); - } catch (InterruptedException e) { - System.err.println(e.getCause() + " " + e.getMessage()); - } - - bulletinBoardClient.close(); - - for (Throwable t : thrown) { - System.err.println(t.getMessage()); - } - if (thrown.size() > 0) { - assert false; - } - - } - -} diff --git a/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericBulletinBoardClientTester.java b/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericBulletinBoardClientTester.java new file mode 100644 index 0000000..d4bd3ec --- /dev/null +++ b/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericBulletinBoardClientTester.java @@ -0,0 +1,519 @@ +package meerkat.bulletinboard; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.ByteString; +import com.google.protobuf.Timestamp; +import meerkat.comm.CommunicationException; +import meerkat.crypto.concrete.ECDSASignature; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Crypto; +import meerkat.util.BulletinBoardMessageComparator; +import meerkat.util.BulletinBoardMessageGenerator; + +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.*; +import java.util.concurrent.Semaphore; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; +import static org.junit.Assert.*; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + */ +public class GenericBulletinBoardClientTester { + + // 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"; + + // 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 thrown; + private Random random; + + // Constructor + + public GenericBulletinBoardClientTester(AsyncBulletinBoardClient bulletinBoardClient){ + + this.bulletinBoardClient = bulletinBoardClient; + + 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{ + + 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{ + + 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>{ + + private List expectedMsgList; + + public ReadCallback(List expectedMsgList) { + this.expectedMsgList = expectedMsgList; + } + + @Override + public void onSuccess(List messages) { + + System.err.println(messages); + jobSemaphore.release(); + + BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator(); + + assertThat(messages.size(), is(expectedMsgList.size())); + + Iterator expectedMessageIterator = expectedMsgList.iterator(); + Iterator receivedMessageIterator = messages.iterator(); + + while (expectedMessageIterator.hasNext()) { + assertThat(msgComparator.compare(expectedMessageIterator.next(), receivedMessageIterator.next()), is(0)); + } + + } + + @Override + public void onFailure(Throwable t) { + genericHandleFailure(t); + } + } + + private class ReadBatchCallback implements FutureCallback { + + 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 + */ + public void init(){ + + random = new Random(0); // We use insecure randomness in tests for repeatability + + 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 + */ + + public void close() { + + if (thrown.size() > 0) { + assert false; + } + + } + + /** + * Tests the standard post, redundancy and read methods + */ + 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 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 + */ + 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 = completeBatch.getCloseBatchMessage(); + + 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 of the message + * @throws CommunicationException, SignatureException, InterruptedException + */ + 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 + */ + 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 stop the (unopened) batch; + + bulletinBoardClient.closeBatch(closeBatchMessage, failPostCallback); + + jobSemaphore.acquire(); + + } + +} diff --git a/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericSubscriptionClientTester.java b/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericSubscriptionClientTester.java new file mode 100644 index 0000000..a91f8d6 --- /dev/null +++ b/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericSubscriptionClientTester.java @@ -0,0 +1,231 @@ +package meerkat.bulletinboard; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.ByteString; +import com.google.protobuf.Timestamp; +import meerkat.comm.CommunicationException; +import meerkat.crypto.concrete.ECDSASignature; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.util.BulletinBoardMessageComparator; +import meerkat.util.BulletinBoardMessageGenerator; + +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.*; +import java.util.concurrent.Semaphore; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 22-Mar-16. + */ +public class GenericSubscriptionClientTester { + + 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"; + + private SubscriptionAsyncBulletinBoardClient bulletinBoardClient; + + private Random random; + private BulletinBoardMessageGenerator generator; + + private Semaphore jobSemaphore; + private Vector thrown; + + public GenericSubscriptionClientTester(SubscriptionAsyncBulletinBoardClient bulletinBoardClient){ + + this.bulletinBoardClient = bulletinBoardClient; + + 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()); + } + + } + + /** + * Takes care of initializing the client and the test resources + */ + public void init(){ + + random = new Random(0); // We use insecure randomness in tests for repeatability + generator = new BulletinBoardMessageGenerator(random); + + thrown = new Vector<>(); + jobSemaphore = new Semaphore(0); + + } + + /** + * Closes the client and makes sure the test fails when an exception occurred in a separate thread + */ + + public void close() { + + if (thrown.size() > 0) { + assert false; + } + + } + + private class SubscriptionCallback implements FutureCallback>{ + + private int stage; + private final List> expectedMessages; + private final List messagesToPost; + private final BulletinBoardMessageComparator comparator; + + public SubscriptionCallback(List> expectedMessages, List messagesToPost) { + + this.expectedMessages = expectedMessages; + this.messagesToPost = messagesToPost; + this.stage = 0; + this.comparator = new BulletinBoardMessageComparator(); + + } + + @Override + public void onSuccess(List result) { + + if (stage >= expectedMessages.size()) + return; + + // Check for consistency + + List expectedMsgList = expectedMessages.get(stage); + + if (expectedMsgList.size() != result.size()){ + onFailure(new AssertionError("Received wrong number of messages")); + return; + } + + Iterator expectedMessageIterator = expectedMsgList.iterator(); + Iterator receivedMessageIterator = result.iterator(); + + while (expectedMessageIterator.hasNext()) { + if(comparator.compare(expectedMessageIterator.next(), receivedMessageIterator.next()) != 0){ + onFailure(new AssertionError("Received unexpected message")); + return; + } + } + + // Post new message + try { + if (stage < messagesToPost.size()) { + bulletinBoardClient.postMessage(messagesToPost.get(stage)); + } + } catch (CommunicationException e) { + onFailure(e); + return; + } + + stage++; + jobSemaphore.release(); + } + + @Override + public void onFailure(Throwable t) { + System.err.println(t.getCause() + " " + t.getMessage()); + thrown.add(t); + jobSemaphore.release(expectedMessages.size()); + stage = expectedMessages.size(); + } + } + + public void subscriptionTest() throws SignatureException, CommunicationException { + + final int FIRST_POST_ID = 201; + final int SECOND_POST_ID = 202; + final String COMMON_TAG = "SUBSCRIPTION_TEST"; + + List tags = new LinkedList<>(); + tags.add(COMMON_TAG); + + BulletinBoardMessage msg1 = generator.generateRandomMessage(signers, Timestamp.newBuilder().setSeconds(1000).setNanos(900).build(), 10, 4, tags); + BulletinBoardMessage msg2 = generator.generateRandomMessage(signers, Timestamp.newBuilder().setSeconds(800).setNanos(300).build(), 10, 4); + BulletinBoardMessage msg3 = generator.generateRandomMessage(signers, Timestamp.newBuilder().setSeconds(2000).setNanos(0).build(), 10, 4, tags); + + MessageFilterList filterList = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag(COMMON_TAG) + .build()) + .build(); + + List> expectedMessages = new ArrayList<>(3); + expectedMessages.add(new LinkedList()); + expectedMessages.add(new LinkedList()); + expectedMessages.add(new LinkedList()); + expectedMessages.get(0).add(msg1); + expectedMessages.get(2).add(msg3); + + List messagesToPost = new ArrayList<>(2); + messagesToPost.add(msg2); + messagesToPost.add(msg3); + + bulletinBoardClient.postMessage(msg1); + bulletinBoardClient.subscribe(filterList, new SubscriptionCallback(expectedMessages, messagesToPost)); + + try { + jobSemaphore.acquire(3); + } catch (InterruptedException e) { + System.err.println(e.getCause() + " " + e.getMessage()); + } + + } + +} diff --git a/bulletin-board-client/src/test/java/meerkat/bulletinboard/LocalBulletinBoardClientTest.java b/bulletin-board-client/src/test/java/meerkat/bulletinboard/LocalBulletinBoardClientTest.java new file mode 100644 index 0000000..d2039ae --- /dev/null +++ b/bulletin-board-client/src/test/java/meerkat/bulletinboard/LocalBulletinBoardClientTest.java @@ -0,0 +1,118 @@ +package meerkat.bulletinboard; + +import meerkat.bulletinboard.sqlserver.*; +import meerkat.comm.CommunicationException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.security.SignatureException; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + */ +public class LocalBulletinBoardClientTest { + + private static final int THREAD_NUM = 3; + private static final String DB_NAME = "TestDB"; + + private static final int SUBSRCIPTION_DELAY = 3000; + + // Testers + private GenericBulletinBoardClientTester clientTest; + private GenericSubscriptionClientTester subscriptionTester; + + public LocalBulletinBoardClientTest() throws CommunicationException { + + H2QueryProvider queryProvider = new H2QueryProvider(DB_NAME) ; + + try { + + Connection conn = queryProvider.getDataSource().getConnection(); + Statement stmt = conn.createStatement(); + + List deletionQueries = queryProvider.getSchemaDeletionCommands(); + + for (String deletionQuery : deletionQueries) { + stmt.execute(deletionQuery); + } + + } catch (SQLException e) { + System.err.println(e.getMessage()); + throw new CommunicationException(e.getCause() + " " + e.getMessage()); + } + + BulletinBoardServer server = new BulletinBoardSQLServer(queryProvider); + server.init(DB_NAME); + + LocalBulletinBoardClient client = new LocalBulletinBoardClient(server, THREAD_NUM, SUBSRCIPTION_DELAY); + subscriptionTester = new GenericSubscriptionClientTester(client); + clientTest = new GenericBulletinBoardClientTester(client); + + } + + // Test methods + + /** + * Takes care of initializing the client and the test resources + */ + @Before + public void init(){ + + clientTest.init(); + + } + + /** + * Closes the client and makes sure the test fails when an exception occurred in a separate thread + */ + + @After + public void close() { + + clientTest.close(); + + } + + @Test + public void postTest() { + + clientTest.postTest(); + + } + + @Test + public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException { + + clientTest.testBatchPost(); + } + + @Test + public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException { + + clientTest.testCompleteBatchPost(); + + } + + @Test + public void testInvalidBatchClose() throws CommunicationException, InterruptedException { + + clientTest.testInvalidBatchClose(); + + } + + @Test + public void testSubscription() throws SignatureException, CommunicationException { + subscriptionTester.init(); + subscriptionTester.subscriptionTest(); + subscriptionTester.close(); + + } + +} diff --git a/bulletin-board-client/src/test/java/meerkat/bulletinboard/ThreadedBulletinBoardClientIntegrationTest.java b/bulletin-board-client/src/test/java/meerkat/bulletinboard/ThreadedBulletinBoardClientIntegrationTest.java new file mode 100644 index 0000000..b69cf72 --- /dev/null +++ b/bulletin-board-client/src/test/java/meerkat/bulletinboard/ThreadedBulletinBoardClientIntegrationTest.java @@ -0,0 +1,95 @@ +package meerkat.bulletinboard; + +import meerkat.comm.CommunicationException; + +import meerkat.protobuf.Voting.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.security.SignatureException; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + */ +public class ThreadedBulletinBoardClientIntegrationTest { + + // 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); + + // Tester + private GenericBulletinBoardClientTester clientTest; + + public ThreadedBulletinBoardClientIntegrationTest(){ + + ThreadedBulletinBoardClient client = new ThreadedBulletinBoardClient(); + + List testDB = new LinkedList<>(); + testDB.add(BASE_URL); + + client.init(BulletinBoardClientParams.newBuilder() + .addAllBulletinBoardAddress(testDB) + .setMinRedundancy((float) 1.0) + .build()); + + clientTest = new GenericBulletinBoardClientTester(client); + + } + + // Test methods + + /** + * Takes care of initializing the client and the test resources + */ + @Before + public void init(){ + + clientTest.init(); + + } + + /** + * Closes the client and makes sure the test fails when an exception occurred in a separate thread + */ + + @After + public void close() { + + clientTest.close(); + + } + + @Test + public void postTest() { + + clientTest.postTest(); + + } + + @Test + public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException { + + clientTest.testBatchPost(); + } + + @Test + public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException { + + clientTest.testCompleteBatchPost(); + + } + + @Test + public void testInvalidBatchClose() throws CommunicationException, InterruptedException { + + clientTest.testInvalidBatchClose(); + + } + +} diff --git a/bulletin-board-server/.gitignore b/bulletin-board-server/.gitignore index ae3c172..3e2fcc7 100644 --- a/bulletin-board-server/.gitignore +++ b/bulletin-board-server/.gitignore @@ -1 +1 @@ -/bin/ +/bin/ diff --git a/bulletin-board-server/build.gradle b/bulletin-board-server/build.gradle index eb197c9..766ed56 100644 --- a/bulletin-board-server/build.gradle +++ b/bulletin-board-server/build.gradle @@ -48,9 +48,10 @@ dependencies { // JDBC connections 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 'com.h2database:h2:1.0.+' + compile 'org.apache.commons:commons-dbcp2:2.0.+' // Servlets compile 'javax.servlet:javax.servlet-api:3.0.+' @@ -75,13 +76,34 @@ dependencies { test { exclude '**/*SQLite*Test*' exclude '**/*H2*Test*' - exclude '**/*MySql*Test' + exclude '**/*MySQL*Test*' 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) { 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) { @@ -238,4 +260,3 @@ publishing { } - diff --git a/bulletin-board-server/gradle-app.setting b/bulletin-board-server/gradle-app.setting index b0bad25..21226bc 100644 --- a/bulletin-board-server/gradle-app.setting +++ b/bulletin-board-server/gradle-app.setting @@ -1,29 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/service/HelloProtoBuf.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/service/HelloProtoBuf.java index 772e7dd..c1f5de8 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/service/HelloProtoBuf.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/service/HelloProtoBuf.java @@ -1,30 +1,30 @@ -package meerkat.bulletinboard.service; - -import com.google.protobuf.ByteString; -import com.google.protobuf.Message; -import meerkat.protobuf.Crypto; -import meerkat.protobuf.BulletinBoardAPI.*; - -import java.util.Arrays; -import java.util.List; - -/** - * Created by talm on 10/11/15. - */ -public class HelloProtoBuf { - public Message sayHello() { - BulletinBoardMessage.Builder msg = BulletinBoardMessage.newBuilder(); - - UnsignedBulletinBoardMessage.Builder unsigned = UnsignedBulletinBoardMessage.newBuilder(); - unsigned.setData(ByteString.copyFromUtf8("Hello World!")); - List tags = Arrays.asList("Greetings", "FirstPrograms"); - unsigned.addAllTag(tags); - msg.setMsg(unsigned); - - Crypto.Signature.Builder sig = Crypto.Signature.newBuilder(); - sig.setData(ByteString.copyFromUtf8("deadbeef")); - msg.addSig(sig); - - return msg.build(); - } -} +package meerkat.bulletinboard.service; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Message; +import meerkat.protobuf.Crypto; +import meerkat.protobuf.BulletinBoardAPI.*; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by talm on 10/11/15. + */ +public class HelloProtoBuf { + public Message sayHello() { + BulletinBoardMessage.Builder msg = BulletinBoardMessage.newBuilder(); + + UnsignedBulletinBoardMessage.Builder unsigned = UnsignedBulletinBoardMessage.newBuilder(); + unsigned.setData(ByteString.copyFromUtf8("Hello World!")); + List tags = Arrays.asList("Greetings", "FirstPrograms"); + unsigned.addAllTag(tags); + msg.setMsg(unsigned); + + Crypto.Signature.Builder sig = Crypto.Signature.newBuilder(); + sig.setData(ByteString.copyFromUtf8("deadbeef")); + msg.addSig(sig); + + return msg.build(); + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java index 52bf42b..02b50c9 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java @@ -1,484 +1,1044 @@ -package meerkat.bulletinboard.sqlserver; - -import java.sql.*; -import java.util.*; - -import com.google.protobuf.ProtocolStringList; - -import meerkat.bulletinboard.BulletinBoardServer; -import meerkat.bulletinboard.sqlserver.mappers.EntryNumMapper; -import meerkat.bulletinboard.sqlserver.mappers.MessageMapper; -import meerkat.bulletinboard.sqlserver.mappers.SignatureMapper; -import meerkat.comm.CommunicationException; -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.protobuf.Crypto.Signature; -import meerkat.protobuf.Crypto.SignatureVerificationKey; -import meerkat.crypto.Digest; -import meerkat.crypto.concrete.SHA256Digest; - -import javax.sql.DataSource; - -import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; - -/** - * This is a generic SQL implementation of the BulletinBoardServer API. - */ -public class BulletinBoardSQLServer implements BulletinBoardServer{ - - /** - * This interface provides the required implementation-specific data to enable an access to an actual SQL server. - * It accounts for differences in languages between SQL DB types and for the different addresses needed to access them. - */ - public interface SQLQueryProvider { - - /** - * Allowed query types. - * Note that each query returned has to comply with the parameter names specified ny getParamNames - */ - public static enum QueryType { - - FIND_MSG_ID(new String[] {"MsgId"}), - INSERT_MSG(new String[] {"MsgId","Msg"}), - INSERT_NEW_TAG(new String[] {"Tag"}), - CONNECT_TAG(new String[] {"EntryNum","Tag"}), - ADD_SIGNATURE(new String[] {"EntryNum","SignerId","Signature"}), - GET_SIGNATURES(new String[] {"EntryNum"}), - GET_MESSAGES(new String[] {}); - - private String[] paramNames; - - private QueryType(String[] paramNames) { - this.paramNames = paramNames; - } - - public String[] getParamNames() { - return paramNames; - } - - } - - /** - * This enum provides the standard translation between a filter type and the corresponding parameter name in the SQL query - */ - public static enum FilterTypeParam { - - ENTRY_NUM("EntryNum", Types.INTEGER), - MSG_ID("MsgId", Types.BLOB), - SIGNER_ID("SignerId", Types.BLOB), - TAG("Tag", Types.VARCHAR), - LIMIT("Limit", Types.INTEGER); - - private FilterTypeParam(String paramName, int paramType) { - this.paramName = paramName; - this.paramType = paramType; - } - - private String paramName; - private int paramType; - - public static FilterTypeParam getFilterTypeParamName(FilterType filterType) { - switch (filterType) { - - case MSG_ID: - return MSG_ID; - - case EXACT_ENTRY: // Go through - case MAX_ENTRY: - return ENTRY_NUM; - - case SIGNER_ID: - return SIGNER_ID; - - case TAG: - return TAG; - - case MAX_MESSAGES: - return LIMIT; - - default: - return null; - } - } - - public String getParamName() { - return paramName; - } - - public int getParamType() { - return paramType; - } - } - - /** - * This function translates a QueryType into an actual SQL query. - * @param queryType is the type of query requested - * @return a string representation of the query for the specific type of SQL database implemented. - */ - public String getSQLString(QueryType queryType) throws IllegalArgumentException; - - /** - * Used to retrieve a condition to add to an SQL statement that will make the result comply with the filter type - * @param filterType is the filter type - * @param serialNum is a unique number used to identify the condition variables from other condition instances - * @return The SQL string for the condition - * @throws IllegalArgumentException if the filter type used is not supported - */ - public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException; - - public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException; - - /** - * @return the string needed in order to connect to the DB. - */ - public DataSource getDataSource(); - - /** - * This is used to get a list of queries that together create the schema needed for the DB. - * Note that these queries should not assume anything about the current state of the DB. - * In particular: they should not erase any existing tables and/or entries. - * @return The list of queries. - */ - public List getSchemaCreationCommands(); - - /** - * This is used to get a list of queries that together delete the schema needed for the DB. - * This is useful primarily for tests, in which we want to make sure we start with a clean DB. - * @return The list of queries. - */ - public List getSchemaDeletionCommands(); - - } - - private Object getParam(MessageFilter messageFilter) { - - switch (messageFilter.getType()) { - - case MSG_ID: // Go through - case SIGNER_ID: - return messageFilter.getId().toByteArray(); - - case EXACT_ENTRY: // Go through - case MAX_ENTRY: - return messageFilter.getEntry(); - - case TAG: - return messageFilter.getTag(); - - case MAX_MESSAGES: - return messageFilter.getMaxMessages(); - - default: - return null; - } - - } - - /** - * This class implements a comparator for the MessageFilter class - * The comparison is done solely by comparing the type of the filter - * This is used to sort the filters by type - */ - public class FilterTypeComparator implements Comparator { - - @Override - public int compare(MessageFilter filter1, MessageFilter filter2) { - return filter1.getTypeValue() - filter2.getTypeValue(); - } - } - - protected SQLQueryProvider sqlQueryProvider; - - protected NamedParameterJdbcTemplate jdbcTemplate; - - protected Digest digest; - - protected List trusteeSignatureVerificationArray; - protected int minTrusteeSignatures; - - protected List> pollingCommitteeSignatureVerificationKeyArray; - protected int minCommiteeSignatures; - - /** - * This constructor sets the type of SQL language in use. - * @param sqlQueryProvider is the provider of the SQL query strings required for actual operation of the server. - */ - public BulletinBoardSQLServer(SQLQueryProvider sqlQueryProvider) { - this.sqlQueryProvider = sqlQueryProvider; - } - - /** - * This method creates the schema in the given DB to prepare for future transactions - * It does not assume anything about the current state of the database - * @throws SQLException - */ - private void createSchema() throws SQLException { - - final int TIMEOUT = 20; - - for (String command : sqlQueryProvider.getSchemaCreationCommands()) { - jdbcTemplate.update(command,(Map) null); - } - - } - - /** - * This method initializes the signatures, connects to the DB and creates the schema (if required). - */ - @Override - public void init(String meerkatDB) throws CommunicationException { - // TODO write signature reading part. - - digest = new SHA256Digest(); - - jdbcTemplate = new NamedParameterJdbcTemplate(sqlQueryProvider.getDataSource()); - - try { - createSchema(); - } catch (SQLException e) { - throw new CommunicationException("Couldn't create schema " + e.getMessage()); - } - - } - - /** - * This method verifies the authenticity of the received message based on - * the stored signatures. - * - * @param msg - * is the message to authenticate (containing the signature). - * @return TRUE if the message is authenticated and FALSE otherwise. - */ - private boolean verifyMessage(BulletinBoardMessage msg) { - //TODO: Replace with actual verification. - return true; - } - - /** - * This procedure makes sure that all tags in the given list have an entry in the tags table. - * @param tags - */ - protected void insertNewTags(String[] tags) throws SQLException { - - String sql; - - sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.INSERT_NEW_TAG); - Map namedParameters[] = new HashMap[tags.length]; - - for (int i = 0 ; i < tags.length ; i++){ - namedParameters[i] = new HashMap(); - namedParameters[i].put("Tag", tags[i]); - } - - jdbcTemplate.batchUpdate(sql, namedParameters); - - } - - /** - * This procedure is used to convert a boolean to a BoolMsg. - * @param b is the boolean to convert. - * @return a ProtoBuf message with boolean payload. - */ - private BoolMsg boolToBoolMsg(boolean b){ - return BoolMsg.newBuilder() - .setValue(b) - .build(); - } - - @Override - public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException { - - if (!verifyMessage(msg)) { - return boolToBoolMsg(false); - } - - String sql; - Map[] namedParameterArray; - - byte[] msgID; - long entryNum; - - ProtocolStringList tagList; - String[] tags; - - List signatureList; - Signature[] signatures; - - // Calculate message ID (depending only on the the unsigned message) - - digest.reset(); - digest.update(msg.getMsg()); - - msgID = digest.digest(); - - // Add message to table if needed and store entry number of message. - - - sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.FIND_MSG_ID); - Map namedParameters = new HashMap(); - namedParameters.put("MsgId",msgID); - - List entryNums = jdbcTemplate.query(sql, new MapSqlParameterSource(namedParameters), new EntryNumMapper()); - - if (entryNums.size() > 0){ - - entryNum = entryNums.get(0); - - } else{ - - sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.INSERT_MSG); - namedParameters.put("Msg", msg.getMsg().toByteArray()); - - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(sql,new MapSqlParameterSource(namedParameters),keyHolder); - - entryNum = keyHolder.getKey().longValue(); - - } - - // Retrieve tags and store new ones in tag table. - - try { - - tagList = msg.getMsg().getTagList(); - tags = new String[tagList.size()]; - tags = tagList.toArray(tags); - - insertNewTags(tags); - - } catch (SQLException e) { - throw new CommunicationException(e.getMessage()); - } - - // Connect message to tags. - - sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.CONNECT_TAG); - - namedParameterArray = new HashMap[tags.length]; - - for (int i = 0 ; i < tags.length ; i++) { - namedParameterArray[i] = new HashMap(); - namedParameterArray[i].put("EntryNum", entryNum); - namedParameterArray[i].put("Tag", tags[i]); - } - - jdbcTemplate.batchUpdate(sql, namedParameterArray); - - // Retrieve signatures. - - signatureList = msg.getSigList(); - signatures = new Signature[signatureList.size()]; - signatures = signatureList.toArray(signatures); - - // Connect message to signatures. - - sql = sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.ADD_SIGNATURE); - - namedParameterArray = new HashMap[signatures.length]; - - for (int i = 0 ; i < signatures.length ; i++) { - namedParameterArray[i] = new HashMap(); - namedParameterArray[i].put("EntryNum", entryNum); - namedParameterArray[i].put("SignerId", signatures[i].getSignerId().toByteArray()); - namedParameterArray[i].put("Signature", signatures[i].toByteArray()); - } - - jdbcTemplate.batchUpdate(sql,namedParameterArray); - - return boolToBoolMsg(true); - } - - @Override - public BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException { - - BulletinBoardMessageList.Builder resultListBuilder = BulletinBoardMessageList.newBuilder(); - - // SQL length is roughly 50 characters per filter + 50 for the query itself - StringBuilder sqlBuilder = new StringBuilder(50 * (filterList.getFilterCount() + 1)); - - MapSqlParameterSource namedParameters; - int paramNum; - - MessageMapper messageMapper = new MessageMapper(); - SignatureMapper signatureMapper = new SignatureMapper(); - - List filters = new ArrayList(filterList.getFilterList()); - - boolean isFirstFilter = true; - - Collections.sort(filters, new FilterTypeComparator()); - - // Check if Tag/Signature tables are required for filtering purposes - - sqlBuilder.append(sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.GET_MESSAGES)); - // Add conditions - - namedParameters = new MapSqlParameterSource(); - - if (!filters.isEmpty()) { - sqlBuilder.append(" WHERE "); - - for (paramNum = 0 ; paramNum < filters.size() ; paramNum++) { - - MessageFilter filter = filters.get(paramNum); - - if (filter.getType().getNumber() != FilterType.MAX_MESSAGES_VALUE) { - if (isFirstFilter) { - isFirstFilter = false; - } else { - sqlBuilder.append(" AND "); - } - } - - sqlBuilder.append(sqlQueryProvider.getCondition(filter.getType(), paramNum)); - - SQLQueryProvider.FilterTypeParam filterTypeParam = SQLQueryProvider.FilterTypeParam.getFilterTypeParamName(filter.getType()); - - namedParameters.addValue( - filterTypeParam.getParamName() + Integer.toString(paramNum), - getParam(filter), - filterTypeParam.getParamType(), - sqlQueryProvider.getConditionParamTypeName(filter.getType())); - - } - - } - - // Run query - - List msgBuilders = jdbcTemplate.query(sqlBuilder.toString(), namedParameters, messageMapper); - - // Compile list of messages - - for (BulletinBoardMessage.Builder msgBuilder : msgBuilders) { - - // Retrieve signatures - - namedParameters = new MapSqlParameterSource(); - namedParameters.addValue("EntryNum", msgBuilder.getEntryNum()); - - List signatures = jdbcTemplate.query( - sqlQueryProvider.getSQLString(SQLQueryProvider.QueryType.GET_SIGNATURES), - namedParameters, - signatureMapper); - - // Append signatures - msgBuilder.addAllSig(signatures); - - // Finalize message and add to message list. - - resultListBuilder.addMessage(msgBuilder.build()); - - } - - //Combine results and return. - return resultListBuilder.build(); - - } - - @Override - public void close() {} - -} +package meerkat.bulletinboard.sqlserver; + +import java.sql.*; +import java.util.*; + +import com.google.protobuf.*; + +import com.google.protobuf.Timestamp; +import meerkat.bulletinboard.*; +import meerkat.bulletinboard.sqlserver.mappers.*; +import static meerkat.bulletinboard.BulletinBoardConstants.*; + +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.Comm.*; +import meerkat.protobuf.Crypto.Signature; +import meerkat.protobuf.Crypto.SignatureVerificationKey; + + +import static meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider.*; + +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.NamedParameterJdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; + + + +/** + * This is a generic SQL implementation of the BulletinBoardServer API. + */ +public class BulletinBoardSQLServer implements BulletinBoardServer{ + + /** + * This interface provides the required implementation-specific data to enable an access to an actual SQL server. + * It accounts for differences in languages between SQL DB types and for the different addresses needed to access them. + */ + public interface SQLQueryProvider { + + /** + * Allowed query types. + * Note that each query returned has to comply with the parameter names specified ny getParamNames + */ + public static enum QueryType { + + FIND_MSG_ID( + new String[] {"MsgId"}, + new int[] {Types.BLOB} + ), + + FIND_TAG_ID( + 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 int[] paramTypes; + + private QueryType(String[] paramNames, int[] paramTypes) { + this.paramNames = paramNames; + this.paramTypes = paramTypes; + } + + public String[] getParamNames() { + return paramNames; + } + + public String getParamName(int num) { + return paramNames[num]; + } + + public int[] getParamTypes() { + return paramTypes; + } + + public int getParamType(int num) { + return paramTypes[num]; + } + + } + + /** + * This enum provides the standard translation between a filter type and the corresponding parameter name in the SQL query + */ + public static enum FilterTypeParam { + + ENTRY_NUM("EntryNum", Types.INTEGER), + MSG_ID("MsgId", Types.BLOB), + SIGNER_ID("SignerId", Types.BLOB), + TAG("Tag", Types.VARCHAR), + LIMIT("Limit", Types.INTEGER), + TIMESTAMP("TimeStamp", Types.TIMESTAMP); + + private FilterTypeParam(String paramName, int paramType) { + this.paramName = paramName; + this.paramType = paramType; + } + + private String paramName; + private int paramType; + + public static FilterTypeParam getFilterTypeParamName(FilterType filterType) { + switch (filterType) { + + case MSG_ID: + return MSG_ID; + + case EXACT_ENTRY: // Go through + case MAX_ENTRY: // Go through + case MIN_ENTRY: + return ENTRY_NUM; + + case SIGNER_ID: + return SIGNER_ID; + + case TAG: + return TAG; + + case MAX_MESSAGES: + return LIMIT; + + case BEFORE_TIME: // Go through + case AFTER_TIME: + return TIMESTAMP; + + default: + return null; + } + } + + public String getParamName() { + return paramName; + } + + public int getParamType() { + return paramType; + } + } + + /** + * This function translates a QueryType into an actual SQL query. + * @param queryType is the type of query requested + * @return a string representation of the query for the specific type of SQL database implemented. + */ + public String getSQLString(QueryType queryType) throws IllegalArgumentException; + + /** + * Used to retrieve a condition to add to an SQL statement that will make the result comply with the filter type + * @param filterType is the filter type + * @param serialNum is a unique number used to identify the condition variables from other condition instances + * @return The SQL string for the condition + * @throws IllegalArgumentException if the filter type used is not supported + */ + public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException; + + public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException; + + /** + * @return the string needed in order to connect to the DB. + */ + public DataSource getDataSource(); + + /** + * This is used to get a list of queries that together create the schema needed for the DB. + * Note that these queries should not assume anything about the current state of the DB. + * In particular: they should not erase any existing tables and/or entries. + * @return The list of queries. + */ + public List getSchemaCreationCommands(); + + /** + * This is used to get a list of queries that together delete the schema needed for the DB. + * This is useful primarily for tests, in which we want to make sure we start with a clean DB. + * @return The list of queries. + */ + public List getSchemaDeletionCommands(); + + } + + /** + * 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) { + + switch (messageFilter.getType()) { + + case MSG_ID: // Go through + case SIGNER_ID: + return messageFilter.getId().toByteArray(); + + case EXACT_ENTRY: // Go through + case MAX_ENTRY: // Go through + case MIN_ENTRY: + return messageFilter.getEntry(); + + case TAG: + return messageFilter.getTag(); + + case MAX_MESSAGES: + return messageFilter.getMaxMessages(); + + case BEFORE_TIME: // Go through + case AFTER_TIME: + return BulletinBoardUtils.toSQLTimestamp(messageFilter.getTimestamp()); + + default: // Unsupported filter type + return null; + } + + } + + /** + * This class implements a comparator for the MessageFilter class + * The comparison is done solely by comparing the type of the filter + * This is used to sort the filters by type + */ + public class FilterTypeComparator implements Comparator { + + @Override + public int compare(MessageFilter filter1, MessageFilter filter2) { + return filter1.getTypeValue() - filter2.getTypeValue(); + } + } + + protected SQLQueryProvider sqlQueryProvider; + + protected NamedParameterJdbcTemplate jdbcTemplate; + + protected BatchDigest digest; + protected BatchDigitalSignature signer; + + protected List trusteeSignatureVerificationArray; + protected int minTrusteeSignatures; + + protected List> pollingCommitteeSignatureVerificationKeyArray; + protected int minCommiteeSignatures; + + /** + * This constructor sets the type of SQL language in use. + * @param sqlQueryProvider is the provider of the SQL query strings required for actual operation of the server. + */ + public BulletinBoardSQLServer(SQLQueryProvider sqlQueryProvider) { + this.sqlQueryProvider = sqlQueryProvider; + } + + /** + * This method creates the schema in the given DB to prepare for future transactions + * It does not assume anything about the current state of the database + * @throws SQLException + */ + private void createSchema() throws SQLException { + + for (String command : sqlQueryProvider.getSchemaCreationCommands()) { + jdbcTemplate.update(command,(Map) null); + } + + } + + /** + * This method initializes the signatures, connects to the DB and creates the schema (if required). + */ + @Override + public void init(String meerkatDB) throws CommunicationException { + // TODO write signature reading part. + + digest = new GenericBatchDigest(new SHA256Digest()); + signer = new GenericBatchDigitalSignature(new ECDSASignature()); + + jdbcTemplate = new NamedParameterJdbcTemplate(sqlQueryProvider.getDataSource()); + + try { + createSchema(); + } catch (SQLException e) { + throw new CommunicationException("Couldn't create schema " + e.getMessage()); + } + + } + + /** + * This method verifies the authenticity of the received message based on + * the stored signatures. + * + * @param msg + * is the message to authenticate (containing the signature). + * @return TRUE if the message is authenticated and FALSE otherwise. + */ + private boolean verifyMessage(BulletinBoardMessage msg) { + //TODO: Replace with actual verification. + return true; + } + + /** + * This procedure makes sure that all tags in the given list have an entry in the tags table. + * @param tags + */ + protected void insertNewTags(String[] tags) throws SQLException { + + String sql; + + sql = sqlQueryProvider.getSQLString(QueryType.INSERT_NEW_TAG); + Map namedParameters[] = new HashMap[tags.length]; + + for (int i = 0 ; i < tags.length ; i++){ + namedParameters[i] = new HashMap(); + namedParameters[i].put(QueryType.INSERT_NEW_TAG.getParamName(0), tags[i]); + } + + jdbcTemplate.batchUpdate(sql, namedParameters); + + } + + /** + * This procedure is used to convert a boolean to a BoolValue. + * @param b is the boolean to convert. + * @return a ProtoBuf message with boolean payload. + */ + private BoolValue boolToBoolValue(boolean b){ + return BoolValue.newBuilder() + .setValue(b) + .build(); + } + + + /** + * This method posts a messages to the server + * @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 BoolValue postMessage(BulletinBoardMessage msg, boolean checkSignature) throws CommunicationException{ + + if (checkSignature && !verifyMessage(msg)) { + return boolToBoolValue(false); + } + + String sql; + Map[] namedParameterArray; + + byte[] msgID; + long entryNum; + + ProtocolStringList tagList; + String[] tags; + + List signatureList; + Signature[] signatures; + + // Calculate message ID (depending only on the the unsigned message) + + digest.reset(); + digest.update(msg.getMsg()); + + msgID = digest.digest(); + + // Add message to table if needed and store entry number of message. + + sql = sqlQueryProvider.getSQLString(QueryType.FIND_MSG_ID); + Map namedParameters = new HashMap(); + + namedParameters.put(QueryType.FIND_MSG_ID.getParamName(0),msgID); + + List entryNums = jdbcTemplate.query(sql, namedParameters, new LongMapper()); + + if (entryNums.size() > 0){ + + entryNum = entryNums.get(0); + + } else{ + + sql = sqlQueryProvider.getSQLString(QueryType.INSERT_MSG); + + 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(); + jdbcTemplate.update(sql, new MapSqlParameterSource(namedParameters), keyHolder); + + entryNum = keyHolder.getKey().longValue(); + + } + + // Retrieve tags and store new ones in tag table. + + try { + + tagList = msg.getMsg().getTagList(); + tags = new String[tagList.size()]; + tags = tagList.toArray(tags); + + insertNewTags(tags); + + } catch (SQLException e) { + throw new CommunicationException(e.getMessage()); + } + + // Connect message to tags. + + sql = sqlQueryProvider.getSQLString(QueryType.CONNECT_TAG); + + namedParameterArray = new HashMap[tags.length]; + + for (int i = 0 ; i < tags.length ; i++) { + namedParameterArray[i] = new HashMap(); + namedParameterArray[i].put(QueryType.CONNECT_TAG.getParamName(0), entryNum); + namedParameterArray[i].put(QueryType.CONNECT_TAG.getParamName(1), tags[i]); + } + + jdbcTemplate.batchUpdate(sql, namedParameterArray); + + // Retrieve signatures. + + signatureList = msg.getSigList(); + signatures = new Signature[signatureList.size()]; + signatures = signatureList.toArray(signatures); + + // Connect message to signatures. + + sql = sqlQueryProvider.getSQLString(QueryType.ADD_SIGNATURE); + + namedParameterArray = new HashMap[signatures.length]; + + for (int i = 0 ; i < signatures.length ; i++) { + namedParameterArray[i] = new HashMap(); + namedParameterArray[i].put(QueryType.ADD_SIGNATURE.getParamName(0), entryNum); + namedParameterArray[i].put(QueryType.ADD_SIGNATURE.getParamName(1), signatures[i].getSignerId().toByteArray()); + namedParameterArray[i].put(QueryType.ADD_SIGNATURE.getParamName(2), signatures[i].toByteArray()); + } + + jdbcTemplate.batchUpdate(sql,namedParameterArray); + + return boolToBoolValue(true); + + } + + @Override + public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException { + return postMessage(msg, true); // Perform a post and check the signature for authenticity + } + + + /** + * This is a container class for and SQL string builder and a MapSqlParameterSource to be used with it + */ + class SQLAndParameters { + + public StringBuilder sql; + public MapSqlParameterSource parameters; + + public SQLAndParameters(int numOfFilters) { + sql = new StringBuilder(50 * numOfFilters); + parameters = new MapSqlParameterSource(); + } + + } + + SQLAndParameters getSQLFromFilters(MessageFilterList filterList) { + + SQLAndParameters result = new SQLAndParameters(filterList.getFilterCount()); + + List filters = new ArrayList(filterList.getFilterList()); + + Collections.sort(filters, new FilterTypeComparator()); + + boolean isFirstFilter = true; + + if (!filters.isEmpty()) { + result.sql.append(" WHERE "); + + for (int paramNum = 0 ; paramNum < filters.size() ; paramNum++) { + + MessageFilter filter = filters.get(paramNum); + + if (filter.getType().getNumber() != FilterType.MAX_MESSAGES_VALUE) { + if (isFirstFilter) { + isFirstFilter = false; + } else { + result.sql.append(" AND "); + } + } + + result.sql.append(sqlQueryProvider.getCondition(filter.getType(), paramNum)); + + FilterTypeParam filterTypeParam = FilterTypeParam.getFilterTypeParamName(filter.getType()); + + result.parameters.addValue( + filterTypeParam.getParamName() + Integer.toString(paramNum), + getParam(filter), + filterTypeParam.getParamType(), + sqlQueryProvider.getConditionParamTypeName(filter.getType())); + + } + + } + + return result; + + } + + /** + * Private implementation of the message stub reader for returning result as a list + * @param filterList is a filter list that defines which messages the client is interested in + * @return the requested list of message stubs + */ + private List 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 + + return jdbcTemplate.query(sqlBuilder.toString(), sqlAndParameters.parameters, new MessageStubMapper()); + + } + + + @Override + public void readMessages(MessageFilterList filterList, MessageOutputStream out) throws CommunicationException { + + BulletinBoardMessageList.Builder resultListBuilder = BulletinBoardMessageList.newBuilder(); + + // SQL length is roughly 50 characters per filter + 50 for the query itself + StringBuilder sqlBuilder = new StringBuilder(50 * (filterList.getFilterCount() + 1)); + + // Check if Tag/Signature tables are required for filtering purposes + + sqlBuilder.append(sqlQueryProvider.getSQLString(QueryType.GET_MESSAGES)); + + // 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 count = jdbcTemplate.query(sqlBuilder.toString(), sqlAndParameters.parameters, new LongMapper()); + + return (count.size() > 0) && (count.get(0) > 0); + + } + + + @Override + public BoolValue beginBatch(BeginBatchMessage message) throws CommunicationException { + + // Check if batch is closed + if (isBatchClosed(message.getSignerId(), message.getBatchId())) { + return BoolValue.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 BoolValue.newBuilder().setValue(true).build(); + + } + + + @Override + public BoolValue postBatchMessage(BatchMessage batchMessage) throws CommunicationException{ + + // Check if batch is closed + if (isBatchClosed(batchMessage.getSignerId(), batchMessage.getBatchId())) { + return BoolValue.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 BoolValue.newBuilder().setValue(true).build(); + + } + + + @Override + public BoolValue 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 lengthResult = jdbcTemplate.query(sql, namedParameters, new LongMapper()); + + if (lengthResult.get(0) != message.getBatchLength()) { + return BoolValue.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.toByteArray()); + namedParameters.addValue(QueryType.GET_BATCH_TAGS.getParamName(1),batchId); + + List 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 BoolValue.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 BoolValue.newBuilder().setValue(true).build(); + } + + + @Override + public void readBatch(BatchSpecificationMessage message, MessageOutputStream 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 resultList = jdbcTemplate.query(sql, new LongMapper()); + + if (resultList.size() <= 0){ + return -1; + } + + return resultList.get(0); + + } + + @Override + public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) { + + if (generateSyncQueryParams == null + || !generateSyncQueryParams.hasFilterList() + || generateSyncQueryParams.getFilterList().getFilterCount() <= 0 + || generateSyncQueryParams.getBreakpointListCount() <= 0){ + + return SyncQuery.getDefaultInstance(); + + } + + List messages = readMessageStubs(generateSyncQueryParams.getFilterList()); + + if (messages.size() <= 0){ + return SyncQuery.newBuilder().build(); + } + + SyncQuery.Builder resultBuilder = SyncQuery.newBuilder(); + + Iterator messageIterator = messages.iterator(); + Iterator breakpointIterator = generateSyncQueryParams.getBreakpointListList().iterator(); + + Checksum checksum = new SimpleChecksum(); + checksum.setDigest(new SHA256Digest()); + + Timestamp lastTimestamp = Timestamp.getDefaultInstance(); + BulletinBoardMessage message = messageIterator.next(); + long currentMessageNum = 1; + + boolean checksumChanged = true; + + while (breakpointIterator.hasNext()){ + + Float breakpoint = breakpointIterator.next(); + + // Continue while breakpoint not reached, or it has been reached but no new timestamp has been encountered since + while ( messageIterator.hasNext() + && ((float) currentMessageNum / (float) messages.size() <= breakpoint) + || ((float) currentMessageNum / (float) messages.size() > breakpoint + && lastTimestamp.equals(message.getMsg().getTimestamp()))){ + + checksumChanged = true; + + checksum.update(message.getMsg().getData()); + + lastTimestamp = message.getMsg().getTimestamp(); + message = messageIterator.next(); + + } + + if (checksumChanged) { + + checksum.update(message.getMsg().getData()); + resultBuilder.addQuery(SingleSyncQuery.newBuilder() + .setTimeOfSync(message.getMsg().getTimestamp()) + .setChecksum(checksum.getChecksum()) + .build()); + + } + + checksumChanged = false; + + } + + return resultBuilder.build(); + + } + + + /** + * 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 an instance of SimpleChecksum + * @param syncQuery contains a succinct representation of states to compare against + * @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 queryIterator = syncQuery.getQueryList().iterator(); + + SingleSyncQuery currentQuery = queryIterator.next(); + + List 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); + + } + + if (checksum.getChecksum() == currentQuery.getChecksum()){ + 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(); + } + + } + + + @Override + public void close() {} + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java index fa2b146..74b509b 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java @@ -1,164 +1,266 @@ -package meerkat.bulletinboard.sqlserver; - -import meerkat.protobuf.BulletinBoardAPI.FilterType; -import org.h2.jdbcx.JdbcDataSource; -import javax.naming.Context; -import javax.naming.InitialContext; - -import javax.naming.NamingException; -import javax.sql.DataSource; -import java.util.LinkedList; -import java.util.List; - -/** - * Created by Arbel Deutsch Peled on 09-Dec-15. - */ - -public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider { - - private String dbName; - - public H2QueryProvider(String dbName) { - this.dbName = dbName; - } - - - @Override - public String getSQLString(QueryType queryType) throws IllegalArgumentException{ - - switch(queryType) { - case ADD_SIGNATURE: - return "INSERT INTO SignatureTable (EntryNum, SignerId, Signature)" - + " SELECT DISTINCT :EntryNum AS Entry, :SignerId AS Id, :Signature AS Sig FROM UtilityTable AS Temp" - + " WHERE NOT EXISTS" - + " (SELECT 1 FROM SignatureTable AS SubTable WHERE SubTable.SignerId = :SignerId AND SubTable.EntryNum = :EntryNum)"; - - case CONNECT_TAG: - return "INSERT INTO MsgTagTable (TagId, EntryNum)" - + " SELECT DISTINCT TagTable.TagId, :EntryNum AS NewEntry FROM TagTable WHERE Tag = :Tag" - + " AND NOT EXISTS (SELECT 1 FROM MsgTagTable AS SubTable WHERE SubTable.TagId = TagTable.TagId" - + " AND SubTable.EntryNum = :EntryNum)"; - - case FIND_MSG_ID: - return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; - - case GET_MESSAGES: - return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; - - case GET_SIGNATURES: - return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum"; - - case INSERT_MSG: - return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId,:Msg)"; - - case INSERT_NEW_TAG: - return "INSERT INTO TagTable(Tag) SELECT DISTINCT :Tag AS NewTag FROM UtilityTable WHERE" - + " NOT EXISTS (SELECT 1 FROM TagTable AS SubTable WHERE SubTable.Tag = :Tag)"; - - default: - throw new IllegalArgumentException("Cannot serve a query of type " + queryType); - } - - } - - @Override - public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { - - String serialString = Integer.toString(serialNum); - - switch(filterType) { - case EXACT_ENTRY: - return "MsgTable.EntryNum = :EntryNum" + serialString; - case MAX_ENTRY: - return "MsgTable.EntryNum <= :EntryNum" + serialString; - case MAX_MESSAGES: - return "LIMIT :Limit" + serialString; - case MSG_ID: - return "MsgTable.MsgId = MsgId" + serialString; - case SIGNER_ID: - return "EXISTS (SELECT 1 FROM SignatureTable" - + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; - case TAG: - return "EXISTS (SELECT 1 FROM TagTable" - + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" - + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; - default: - throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); - } - - } - - @Override - public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { - - switch(filterType) { - case EXACT_ENTRY: // Go through - case MAX_ENTRY: // Go through - case MAX_MESSAGES: - return "INT"; - - case MSG_ID: // Go through - case SIGNER_ID: - return "TINYBLOB"; - - case TAG: - return "VARCHAR"; - - default: - throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); - } - - } - - @Override - public DataSource getDataSource() { - - JdbcDataSource dataSource = new JdbcDataSource(); - dataSource.setURL("jdbc:h2:~/" + dbName); - - return dataSource; - } - - - @Override - public List getSchemaCreationCommands() { - List list = new LinkedList(); - - list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY, MsgId TINYBLOB UNIQUE, Msg BLOB)"); - - list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50) UNIQUE)"); - - list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum INT, TagId INT," - + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum)," - + " FOREIGN KEY (TagId) REFERENCES TagTable(TagId)," - + " UNIQUE (EntryNum, TagID))"); - - list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB UNIQUE," - + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); - - list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); - list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId, EntryNum)"); - - // This is used to create a simple table with one entry. - // It is used for implementing a workaround for the missing INSERT IGNORE syntax - list.add("CREATE TABLE IF NOT EXISTS UtilityTable (Entry INT)"); - list.add("INSERT INTO UtilityTable (Entry) VALUES (1)"); - - return list; - } - - @Override - public List getSchemaDeletionCommands() { - List list = new LinkedList(); - - list.add("DROP TABLE IF EXISTS UtilityTable"); - list.add("DROP INDEX IF EXISTS SignerIdIndex"); - list.add("DROP TABLE IF EXISTS MsgTagTable"); - list.add("DROP TABLE IF EXISTS SignatureTable"); - list.add("DROP TABLE IF EXISTS TagTable"); - list.add("DROP TABLE IF EXISTS MsgTable"); - - return list; - } - -} +package meerkat.bulletinboard.sqlserver; + +import meerkat.protobuf.BulletinBoardAPI.FilterType; +import org.apache.commons.dbcp2.BasicDataSource; +import org.h2.jdbcx.JdbcDataSource; +import javax.naming.Context; +import javax.naming.InitialContext; + +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.text.MessageFormat; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + */ + +public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider { + + private String dbName; + + public H2QueryProvider(String dbName) { + this.dbName = dbName; + } + + + @Override + public String getSQLString(QueryType queryType) throws IllegalArgumentException{ + + switch(queryType) { + case ADD_SIGNATURE: + return "INSERT INTO SignatureTable (EntryNum, SignerId, Signature)" + + " SELECT DISTINCT :EntryNum AS Entry, :SignerId AS Id, :Signature AS Sig FROM UtilityTable AS Temp" + + " WHERE NOT EXISTS" + + " (SELECT 1 FROM SignatureTable AS SubTable WHERE SubTable.SignerId = :SignerId AND SubTable.EntryNum = :EntryNum)"; + + case CONNECT_TAG: + return "INSERT INTO MsgTagTable (TagId, EntryNum)" + + " SELECT DISTINCT TagTable.TagId, :EntryNum AS NewEntry FROM TagTable WHERE Tag = :Tag" + + " AND NOT EXISTS (SELECT 1 FROM MsgTagTable AS SubTable WHERE SubTable.TagId = TagTable.TagId" + + " AND SubTable.EntryNum = :EntryNum)"; + + case FIND_MSG_ID: + return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; + + case FIND_TAG_ID: + return MessageFormat.format( + "SELECT TagId FROM TagTable WHERE Tag = :{0}", + QueryType.FIND_TAG_ID.getParamName(0)); + + case GET_MESSAGES: + 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: + return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum"; + + case INSERT_MSG: + return "INSERT INTO MsgTable (MsgId, Msg, ExactTime) VALUES(:MsgId,:Msg,:TimeStamp)"; + + case INSERT_NEW_TAG: + return "INSERT INTO TagTable(Tag) SELECT DISTINCT :Tag AS NewTag FROM UtilityTable WHERE" + + " NOT EXISTS (SELECT 1 FROM TagTable AS SubTable WHERE SubTable.Tag = :Tag)"; + + 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: + throw new IllegalArgumentException("Cannot serve a query of type " + queryType); + } + + } + + @Override + public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { + + String serialString = Integer.toString(serialNum); + + switch(filterType) { + case EXACT_ENTRY: + return "MsgTable.EntryNum = :EntryNum" + serialString; + case MAX_ENTRY: + return "MsgTable.EntryNum <= :EntryNum" + serialString; + case MIN_ENTRY: + return "MsgTable.EntryNum >= :EntryNum" + serialString; + case MAX_MESSAGES: + return "LIMIT :Limit" + serialString; + case MSG_ID: + return "MsgTable.MsgId = :MsgId" + serialString; + case SIGNER_ID: + return "EXISTS (SELECT 1 FROM SignatureTable" + + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; + case TAG: + return "EXISTS (SELECT 1 FROM TagTable" + + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" + + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; + + case BEFORE_TIME: + return "MsgTable.ExactTime <= :TimeStamp" + serialString; + + case AFTER_TIME: + return "MsgTable.ExactTime >= :TimeStamp" + serialString; + + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { + + switch(filterType) { + case EXACT_ENTRY: // Go through + case MAX_ENTRY: // Go through + case MIN_ENTRY: // Go through + case MAX_MESSAGES: + return "INT"; + + case MSG_ID: // Go through + case SIGNER_ID: + return "TINYBLOB"; + + case TAG: + return "VARCHAR"; + + case AFTER_TIME: // Go through + case BEFORE_TIME: + return "TIMESTAMP"; + + + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public DataSource getDataSource() { + + BasicDataSource dataSource = new BasicDataSource(); + + dataSource.setDriverClassName("org.h2.Driver"); + dataSource.setUrl("jdbc:h2:~/" + dbName); + + return dataSource; + + } + + + @Override + public List getSchemaCreationCommands() { + List list = new LinkedList(); + + list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY, MsgId TINYBLOB UNIQUE, 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 MsgTagTable (EntryNum INT, TagId INT," + + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum)," + + " FOREIGN KEY (TagId) REFERENCES TagTable(TagId)," + + " UNIQUE (EntryNum, TagID))"); + + list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB UNIQUE," + + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); + + list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); + list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId, EntryNum)"); + + 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. + // It is used for implementing a workaround for the missing INSERT IGNORE syntax + list.add("CREATE TABLE IF NOT EXISTS UtilityTable (Entry INT)"); + list.add("INSERT INTO UtilityTable (Entry) VALUES (1)"); + + return list; + } + + @Override + public List getSchemaDeletionCommands() { + List list = new LinkedList(); + + list.add("DROP TABLE IF EXISTS UtilityTable"); + list.add("DROP INDEX IF EXISTS 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 TABLE IF EXISTS MsgTagTable"); + list.add("DROP TABLE IF EXISTS SignatureTable"); + list.add("DROP TABLE IF EXISTS TagTable"); + list.add("DROP TABLE IF EXISTS MsgTable"); + + return list; + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java index c00c044..c8e6b67 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java @@ -1,148 +1,273 @@ -package meerkat.bulletinboard.sqlserver; - -import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; -import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; -import meerkat.protobuf.BulletinBoardAPI.FilterType; - -import javax.sql.DataSource; -import java.util.LinkedList; -import java.util.List; - -/** - * Created by Arbel Deutsch Peled on 09-Dec-15. - */ - -public class MySQLQueryProvider implements SQLQueryProvider { - - private String dbAddress; - private int dbPort; - private String dbName; - private String username; - private String password; - - public MySQLQueryProvider(String dbAddress, int dbPort, String dbName, String username, String password) { - this.dbAddress = dbAddress; - this.dbPort = dbPort; - this.dbName = dbName; - this.username = username; - this.password = password; - } - - @Override - public String getSQLString(QueryType queryType) throws IllegalArgumentException{ - - switch(queryType) { - case ADD_SIGNATURE: - return "INSERT IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum, :SignerId, :Signature)"; - case CONNECT_TAG: - return "INSERT IGNORE INTO MsgTagTable (TagId, EntryNum)" - + " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag"; - case FIND_MSG_ID: - return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; - case GET_MESSAGES: - return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; - case GET_SIGNATURES: - return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum"; - case INSERT_MSG: - return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId, :Msg)"; - case INSERT_NEW_TAG: - return "INSERT IGNORE INTO TagTable(Tag) VALUES (:Tag)"; - default: - throw new IllegalArgumentException("Cannot serve a query of type " + queryType); - } - - } - - @Override - public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { - - String serialString = Integer.toString(serialNum); - - switch(filterType) { - case EXACT_ENTRY: - return "MsgTable.EntryNum = :EntryNum" + serialString; - case MAX_ENTRY: - return "MsgTable.EntryNum <= :EntryNum" + serialString; - case MAX_MESSAGES: - return "LIMIT :Limit" + serialString; - case MSG_ID: - return "MsgTable.MsgId = :MsgId" + serialString; - case SIGNER_ID: - return "EXISTS (SELECT 1 FROM SignatureTable" - + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; - case TAG: - return "EXISTS (SELECT 1 FROM TagTable" - + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" - + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; - default: - throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); - } - - } - - @Override - public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { - - switch(filterType) { - case EXACT_ENTRY: // Go through - case MAX_ENTRY: // Go through - case MAX_MESSAGES: - return "INT"; - - case MSG_ID: // Go through - case SIGNER_ID: - return "TINYBLOB"; - - case TAG: - return "VARCHAR"; - - default: - throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); - } - - } - - @Override - public DataSource getDataSource() { - MysqlDataSource dataSource = new MysqlDataSource(); - - dataSource.setServerName(dbAddress); - dataSource.setPort(dbPort); - dataSource.setDatabaseName(dbName); - dataSource.setUser(username); - dataSource.setPassword(password); - - return dataSource; - } - - @Override - public List getSchemaCreationCommands() { - List list = new LinkedList(); - - list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY, MsgId TINYBLOB, Msg BLOB, UNIQUE(MsgId(50)))"); - - list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50), UNIQUE(Tag))"); - - list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum INT, TagId INT," - + " CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum)," - + " CONSTRAINT FOREIGN KEY (TagId) REFERENCES TagTable(TagId)," - + " CONSTRAINT UNIQUE (EntryNum, TagID))"); - - list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB," - + " INDEX(SignerId(32)), CONSTRAINT Uni UNIQUE(SignerId(32), EntryNum), CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); - - return list; - } - - @Override - public List getSchemaDeletionCommands() { - List list = new LinkedList(); - - list.add("DROP TABLE IF EXISTS MsgTagTable"); - list.add("DROP TABLE IF EXISTS SignatureTable"); - list.add("DROP TABLE IF EXISTS TagTable"); - list.add("DROP TABLE IF EXISTS MsgTable"); - - return list; - } -} +package meerkat.bulletinboard.sqlserver; + +import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; +import meerkat.bulletinboard.BulletinBoardConstants; +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; +import meerkat.protobuf.BulletinBoardAPI.FilterType; +import org.apache.commons.dbcp2.BasicDataSource; + +import javax.sql.DataSource; +import java.text.MessageFormat; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + */ + +public class MySQLQueryProvider implements SQLQueryProvider { + + private String dbAddress; + private int dbPort; + private String dbName; + private String username; + private String password; + + public MySQLQueryProvider(String dbAddress, int dbPort, String dbName, String username, String password) { + this.dbAddress = dbAddress; + this.dbPort = dbPort; + this.dbName = dbName; + this.username = username; + this.password = password; + } + + @Override + public String getSQLString(QueryType queryType) throws IllegalArgumentException{ + + switch(queryType) { + + case ADD_SIGNATURE: + return 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: + return MessageFormat.format( + "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: + 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: + 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: + return MessageFormat.format( + "SELECT Signature FROM SignatureTable WHERE EntryNum = :{0}", + QueryType.GET_SIGNATURES.getParamName(0)); + + case INSERT_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: + 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: + throw new IllegalArgumentException("Cannot serve a query of type " + queryType); + } + + } + + @Override + public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { + + String serialString = Integer.toString(serialNum); + + switch(filterType) { + case EXACT_ENTRY: + return "MsgTable.EntryNum = :EntryNum" + serialString; + case MAX_ENTRY: + return "MsgTable.EntryNum <= :EntryNum" + serialString; + case MIN_ENTRY: + return "MsgTable.EntryNum >= :EntryNum" + serialString; + case MAX_MESSAGES: + return "LIMIT :Limit" + serialString; + case MSG_ID: + return "MsgTable.MsgId = :MsgId" + serialString; + case SIGNER_ID: + return "EXISTS (SELECT 1 FROM SignatureTable" + + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; + case TAG: + return "EXISTS (SELECT 1 FROM TagTable" + + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" + + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; + + case BEFORE_TIME: + return "MsgTable.ExactTime <= :TimeStamp"; + + case AFTER_TIME: + return "MsgTable.ExactTime >= :TimeStamp"; + + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { + + switch(filterType) { + case EXACT_ENTRY: // Go through + case MAX_ENTRY: // Go through + case MIN_ENTRY: // Go through + case MAX_MESSAGES: + return "INT"; + + case MSG_ID: // Go through + case SIGNER_ID: + return "TINYBLOB"; + + case TAG: + return "VARCHAR"; + + case AFTER_TIME: // Go through + case BEFORE_TIME: + return "TIMESTAMP"; + + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public DataSource getDataSource() { + + BasicDataSource dataSource = new BasicDataSource(); + + dataSource.setDriverClassName("com.mysql.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql://" + dbAddress + ":" + dbPort + "/" + dbName); + + dataSource.setUsername(username); + dataSource.setPassword(password); + + return dataSource; + + } + + @Override + public List getSchemaCreationCommands() { + List list = new LinkedList(); + + list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY," + + " MsgId TINYBLOB, 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 MsgTagTable (EntryNum INT, TagId INT," + + " CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum)," + + " CONSTRAINT FOREIGN KEY (TagId) REFERENCES TagTable(TagId)," + + " CONSTRAINT UNIQUE (EntryNum, TagID))"); + + list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB," + + " INDEX(SignerId(32)), CONSTRAINT 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; + } + + @Override + public List getSchemaDeletionCommands() { + List list = new LinkedList(); + + 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 SignatureTable"); + list.add("DROP TABLE IF EXISTS TagTable"); + list.add("DROP TABLE IF EXISTS MsgTable"); + + return list; + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java index 945ae47..00f75a0 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java @@ -1,123 +1,233 @@ -package meerkat.bulletinboard.sqlserver; - -import meerkat.protobuf.BulletinBoardAPI.*; -import org.sqlite.SQLiteDataSource; - -import javax.sql.DataSource; -import java.util.LinkedList; -import java.util.List; - -/** - * Created by Arbel Deutsch Peled on 09-Dec-15. - */ - -public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvider { - - String dbName; - - public SQLiteQueryProvider(String dbName) { - this.dbName = dbName; - } - - @Override - public String getSQLString(QueryType queryType) throws IllegalArgumentException{ - - switch(queryType) { - case ADD_SIGNATURE: - return "INSERT OR IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum,:SignerId,:Signature)"; - case CONNECT_TAG: - return "INSERT OR IGNORE INTO MsgTagTable (TagId, EntryNum)" - + " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag"; - case FIND_MSG_ID: - return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; - case GET_MESSAGES: - return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; - case GET_SIGNATURES: - return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum"; - case INSERT_MSG: - return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId,:Msg)"; - case INSERT_NEW_TAG: - return "INSERT OR IGNORE INTO TagTable(Tag) VALUES (:Tag)"; - default: - throw new IllegalArgumentException("Cannot serve a query of type " + queryType); - } - - } - - @Override - public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { - - String serialString = Integer.toString(serialNum); - - switch(filterType) { - case EXACT_ENTRY: - return "MsgTable.EntryNum = :EntryNum" + serialString; - case MAX_ENTRY: - return "MsgTable.EntryNum <= :EntryNum" + serialString; - case MAX_MESSAGES: - return "LIMIT = :Limit" + serialString; - case MSG_ID: - return "MsgTable.MsgId = :MsgId" + serialString; - case SIGNER_ID: - return "EXISTS (SELECT 1 FROM SignatureTable" - + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; - case TAG: - return "EXISTS (SELECT 1 FROM TagTable" - + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" - + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; - default: - throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); - } - - } - - @Override - public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { - return null; //TODO: write this. - } - - @Override - public DataSource getDataSource() { - // TODO: Fix this - SQLiteDataSource dataSource = new SQLiteDataSource(); - dataSource.setUrl("jdbc:sqlite:" + dbName); - dataSource.setDatabaseName("meerkat"); //TODO: Make generic - - return dataSource; - } - - - @Override - public List getSchemaCreationCommands() { - List list = new LinkedList(); - - list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INTEGER PRIMARY KEY, MsgId BLOB UNIQUE, Msg BLOB)"); - - list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INTEGER PRIMARY KEY, Tag varchar(50) UNIQUE)"); - list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum BLOB, TagId INTEGER, FOREIGN KEY (EntryNum)" - + " REFERENCES MsgTable(EntryNum), FOREIGN KEY (TagId) REFERENCES TagTable(TagId), UNIQUE (EntryNum, TagID))"); - - list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INTEGER, SignerId BLOB, Signature BLOB," - + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); - - list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); - list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId, EntryNum)"); - - return list; - } - - @Override - public List getSchemaDeletionCommands() { - List list = new LinkedList(); - - list.add("DROP TABLE IF EXISTS MsgTagTable"); - - list.add("DROP INDEX IF EXISTS SignerIndex"); - list.add("DROP TABLE IF EXISTS SignatureTable"); - - list.add("DROP TABLE IF EXISTS TagTable"); - list.add("DROP TABLE IF EXISTS MsgTable"); - - return list; - } -} +package meerkat.bulletinboard.sqlserver; + +import meerkat.protobuf.BulletinBoardAPI.*; +import org.sqlite.SQLiteDataSource; + +import javax.sql.DataSource; +import java.text.MessageFormat; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 09-Dec-15. + */ + +public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvider { + + String dbName; + + public SQLiteQueryProvider(String dbName) { + this.dbName = dbName; + } + + @Override + public String getSQLString(QueryType queryType) throws IllegalArgumentException{ + + switch(queryType) { + case ADD_SIGNATURE: + return "INSERT OR IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum,:SignerId,:Signature)"; + + case CONNECT_TAG: + return "INSERT OR IGNORE INTO MsgTagTable (TagId, EntryNum)" + + " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag"; + + case FIND_MSG_ID: + return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; + + case FIND_TAG_ID: + return MessageFormat.format( + "SELECT TagId FROM TagTable WHERE Tag = :{0}", + QueryType.FIND_TAG_ID.getParamName(0)); + + case GET_MESSAGES: + 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: + return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum"; + + case INSERT_MSG: + return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId,:Msg)"; + + case INSERT_NEW_TAG: + return "INSERT OR IGNORE INTO TagTable(Tag) VALUES (:Tag)"; + + 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: + throw new IllegalArgumentException("Cannot serve a query of type " + queryType); + } + + } + + @Override + public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { + + String serialString = Integer.toString(serialNum); + + switch(filterType) { + case EXACT_ENTRY: + return "MsgTable.EntryNum = :EntryNum" + serialString; + + case MAX_ENTRY: + return "MsgTable.EntryNum <= :EntryNum" + serialString; + + case MIN_ENTRY: + return "MsgTable.EntryNum <= :EntryNum" + serialString; + + case MAX_MESSAGES: + return "LIMIT = :Limit" + serialString; + + case MSG_ID: + return "MsgTable.MsgId = :MsgId" + serialString; + + case SIGNER_ID: + return "EXISTS (SELECT 1 FROM SignatureTable" + + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; + + case TAG: + return "EXISTS (SELECT 1 FROM TagTable" + + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" + + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; + + case BEFORE_TIME: + return "MsgTable.ExactTime <= :TimeStamp"; + + case AFTER_TIME: + return "MsgTable.ExactTime >= :TimeStamp"; + + default: + throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); + } + + } + + @Override + public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { + + switch(filterType) { + case EXACT_ENTRY: // Go through + case MAX_ENTRY: // Go through + case 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 + public DataSource getDataSource() { + + SQLiteDataSource dataSource = new SQLiteDataSource(); + dataSource.setUrl("jdbc:sqlite:" + dbName); + + return dataSource; + } + + + @Override + public List getSchemaCreationCommands() { + List list = new LinkedList(); + + list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INTEGER PRIMARY KEY, MsgId BLOB UNIQUE, Msg BLOB)"); + + list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INTEGER PRIMARY KEY, Tag varchar(50) UNIQUE)"); + list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum BLOB, TagId INTEGER, FOREIGN KEY (EntryNum)" + + " REFERENCES MsgTable(EntryNum), FOREIGN KEY (TagId) REFERENCES TagTable(TagId), UNIQUE (EntryNum, TagID))"); + + list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INTEGER, SignerId BLOB, Signature BLOB," + + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum), UNIQUE(SignerId, EntryNum))"); + + list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); + + return list; + } + + @Override + public List getSchemaDeletionCommands() { + List list = new LinkedList(); + + list.add("DROP TABLE IF EXISTS MsgTagTable"); + + list.add("DROP INDEX IF EXISTS SignerIndex"); + list.add("DROP TABLE IF EXISTS SignatureTable"); + + list.add("DROP TABLE IF EXISTS TagTable"); + list.add("DROP TABLE IF EXISTS MsgTable"); + + return list; + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataCallbackHandler.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataCallbackHandler.java new file mode 100644 index 0000000..9ad0dc7 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataCallbackHandler.java @@ -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 out; + + public BatchDataCallbackHandler(MessageOutputStream 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 + } + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataMapper.java new file mode 100644 index 0000000..bc4ea26 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataMapper.java @@ -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 { + + @Override + public BatchData mapRow(ResultSet rs, int rowNum) throws SQLException { + + try { + return BatchData.parseFrom(rs.getBytes(1)); + } catch (InvalidProtocolBufferException e) { + return BatchData.getDefaultInstance(); + } + + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/EntryNumMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/LongMapper.java similarity index 84% rename from bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/EntryNumMapper.java rename to bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/LongMapper.java index 478c39e..ba5a378 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/EntryNumMapper.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/LongMapper.java @@ -1,18 +1,18 @@ -package meerkat.bulletinboard.sqlserver.mappers; - -import meerkat.protobuf.BulletinBoardAPI.MessageID; -import org.springframework.jdbc.core.RowMapper; - -import java.sql.ResultSet; -import java.sql.SQLException; - -/** - * Created by Arbel Deutsch Peled on 11-Dec-15. - */ -public class EntryNumMapper implements RowMapper { - - @Override - public Long mapRow(ResultSet rs, int rowNum) throws SQLException { - return rs.getLong(1); - } -} +package meerkat.bulletinboard.sqlserver.mappers; + +import meerkat.protobuf.BulletinBoardAPI.MessageID; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Arbel Deutsch Peled on 11-Dec-15. + */ +public class LongMapper implements RowMapper { + + @Override + public Long mapRow(ResultSet rs, int rowNum) throws SQLException { + return rs.getLong(1); + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageCallbackHandler.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageCallbackHandler.java new file mode 100644 index 0000000..71ba742 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageCallbackHandler.java @@ -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 { + + private final NamedParameterJdbcTemplate jdbcTemplate; + private final SQLQueryProvider sqlQueryProvider; + private final MessageOutputStream out; + + public MessageCallbackHandler(NamedParameterJdbcTemplate jdbcTemplate, SQLQueryProvider sqlQueryProvider, MessageOutputStream 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 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(); + + } + + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageMapper.java index fdc1fa8..d1584aa 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageMapper.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageMapper.java @@ -1,32 +1,32 @@ -package meerkat.bulletinboard.sqlserver.mappers; - -import com.google.protobuf.InvalidProtocolBufferException; -import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; -import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; -import org.springframework.jdbc.core.RowMapper; - -import java.sql.ResultSet; -import java.sql.SQLException; - -/** - * Created by Arbel Deutsch Peled on 11-Dec-15. - */ -public class MessageMapper implements RowMapper { - - @Override - public BulletinBoardMessage.Builder mapRow(ResultSet rs, int rowNum) throws SQLException { - - BulletinBoardMessage.Builder builder = BulletinBoardMessage.newBuilder(); - - try { - builder.setEntryNum(rs.getLong(1)) - .setMsg(UnsignedBulletinBoardMessage.parseFrom(rs.getBytes(2))); - - } catch (InvalidProtocolBufferException e) { - throw new SQLException(e.getMessage(), e); - } - - return builder; - } - -} +package meerkat.bulletinboard.sqlserver.mappers; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; +import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Arbel Deutsch Peled on 11-Dec-15. + */ +public class MessageMapper implements RowMapper { + + @Override + public BulletinBoardMessage.Builder mapRow(ResultSet rs, int rowNum) throws SQLException { + + BulletinBoardMessage.Builder builder = BulletinBoardMessage.newBuilder(); + + try { + builder.setEntryNum(rs.getLong(1)) + .setMsg(UnsignedBulletinBoardMessage.parseFrom(rs.getBytes(2))); + + } catch (InvalidProtocolBufferException e) { + throw new SQLException(e.getMessage(), e); + } + + return builder; + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageStubCallbackHandler.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageStubCallbackHandler.java new file mode 100644 index 0000000..f81cc76 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageStubCallbackHandler.java @@ -0,0 +1,59 @@ +package meerkat.bulletinboard.sqlserver.mappers; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider.QueryType; +import meerkat.comm.MessageOutputStream; +import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; +import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; +import meerkat.protobuf.Crypto; +import meerkat.util.BulletinBoardUtils; +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 MessageStubCallbackHandler implements RowCallbackHandler { + + private final MessageOutputStream out; + + public MessageStubCallbackHandler(MessageOutputStream out) { + + this.out = out; + + } + + @Override + public void processRow(ResultSet rs) throws SQLException { + + BulletinBoardMessage result; + + result = BulletinBoardMessage.newBuilder() + .setEntryNum(rs.getLong(1)) + .setMsg(UnsignedBulletinBoardMessage.newBuilder() + .setData(ByteString.copyFrom(rs.getBytes(2))) + .setTimestamp(BulletinBoardUtils.toTimestampProto(rs.getTimestamp(3))) + .build()) + .build(); + + try { + + out.writeMessage(result); + + } catch (IOException e) { + + //TODO: log + e.printStackTrace(); + + } + + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageStubMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageStubMapper.java new file mode 100644 index 0000000..1f9c459 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/MessageStubMapper.java @@ -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 { + + @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(); + + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/SignatureMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/SignatureMapper.java index 60015c1..95ff087 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/SignatureMapper.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/SignatureMapper.java @@ -1,28 +1,28 @@ -package meerkat.bulletinboard.sqlserver.mappers; - -import com.google.protobuf.InvalidProtocolBufferException; -import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; -import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; -import meerkat.protobuf.Crypto.Signature; -import org.springframework.jdbc.core.RowMapper; - -import java.sql.ResultSet; -import java.sql.SQLException; - -/** - * Created by Arbel Deutsch Peled on 11-Dec-15. - */ -public class SignatureMapper implements RowMapper { - - @Override - public Signature mapRow(ResultSet rs, int rowNum) throws SQLException { - - try { - return Signature.parseFrom(rs.getBytes(1)); - } catch (InvalidProtocolBufferException e) { - throw new SQLException(e.getMessage(), e); - } - - } - -} +package meerkat.bulletinboard.sqlserver.mappers; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; +import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; +import meerkat.protobuf.Crypto.Signature; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Arbel Deutsch Peled on 11-Dec-15. + */ +public class SignatureMapper implements RowMapper { + + @Override + public Signature mapRow(ResultSet rs, int rowNum) throws SQLException { + + try { + return Signature.parseFrom(rs.getBytes(1)); + } catch (InvalidProtocolBufferException e) { + throw new SQLException(e.getMessage(), e); + } + + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/StringMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/StringMapper.java new file mode 100644 index 0000000..c5b1d85 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/StringMapper.java @@ -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 { + + @Override + public String mapRow(ResultSet rs, int rowNum) throws SQLException { + return rs.getString(1); + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java index b3fc03c..aef820e 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java @@ -1,124 +1,266 @@ -package meerkat.bulletinboard.webapp; - -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; - -import meerkat.bulletinboard.BulletinBoardServer; -import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; -import meerkat.bulletinboard.sqlserver.H2QueryProvider; -import meerkat.bulletinboard.sqlserver.MySQLQueryProvider; -import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider; -import meerkat.comm.CommunicationException; -import meerkat.protobuf.BulletinBoardAPI.BoolMsg; -import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; -import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessageList; -import meerkat.protobuf.BulletinBoardAPI.MessageFilterList; -import meerkat.rest.Constants; - -@Path(Constants.BULLETIN_BOARD_SERVER_PATH) -public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextListener{ - - private static final String BULLETIN_BOARD_ATTRIBUTE_NAME = "bulletinBoard"; - - @Context ServletContext servletContext; - - BulletinBoardServer bulletinBoard; - - /** - * This is the servlet init method. - */ - public void init(){ - bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME); - } - - /** - * This is the BulletinBoard init method. - */ - @Override - public void init(String meerkatDB) throws CommunicationException { - bulletinBoard.init(meerkatDB); - } - - @Override - public void contextInitialized(ServletContextEvent servletContextEvent) { - ServletContext servletContext = servletContextEvent.getServletContext(); - String dbType = servletContext.getInitParameter("dbType"); - String dbName = servletContext.getInitParameter("dbName"); - - if ("SQLite".equals(dbType)){ - - bulletinBoard = new BulletinBoardSQLServer(new SQLiteQueryProvider(dbName)); - - } else if ("H2".equals(dbType)) { - - bulletinBoard = new BulletinBoardSQLServer(new H2QueryProvider(dbName)); - - } else if ("MySQL".equals(dbType)) { - - String dbAddress = servletContext.getInitParameter("dbAddress"); - int dbPort = Integer.parseInt(servletContext.getInitParameter("dbPort")); - String username = servletContext.getInitParameter("username"); - String password = servletContext.getInitParameter("password"); - - bulletinBoard = new BulletinBoardSQLServer(new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password)); - } - - try { - init(dbName); - servletContext.setAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME, bulletinBoard); - } catch (CommunicationException e) { - System.err.println(e.getMessage()); - } - } - - @Path(Constants.POST_MESSAGE_PATH) - @POST - @Consumes(Constants.MEDIATYPE_PROTOBUF) - @Produces(Constants.MEDIATYPE_PROTOBUF) - @Override - public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException { - init(); - return bulletinBoard.postMessage(msg); - } - - @Path(Constants.READ_MESSAGES_PATH) - @POST - @Consumes(Constants.MEDIATYPE_PROTOBUF) - @Produces(Constants.MEDIATYPE_PROTOBUF) - @Override - public BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException { - init(); - return bulletinBoard.readMessages(filterList); - } - - @Override - public void close(){ - try { - bulletinBoard.close(); - } catch (CommunicationException e) { - System.err.println(e.getMessage()); - } - } - - @GET - @Produces(MediaType.TEXT_PLAIN) - public String test() { - return "This BulletinBoard is up and running!\n Please consult the API documents to perform queries."; - } - - @Override - public void contextDestroyed(ServletContextEvent servletContextEvent) { - ServletContext servletContext = servletContextEvent.getServletContext(); - bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME); - close(); - } - -} +package meerkat.bulletinboard.webapp; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.StreamingOutput; + +import com.google.protobuf.BoolValue; +import meerkat.bulletinboard.BulletinBoardServer; +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; +import meerkat.bulletinboard.sqlserver.H2QueryProvider; +import meerkat.bulletinboard.sqlserver.MySQLQueryProvider; +import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider; +import meerkat.comm.CommunicationException; +import meerkat.comm.MessageOutputStream; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Comm.*; +import static meerkat.bulletinboard.BulletinBoardConstants.*; +import static meerkat.rest.Constants.*; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; + +/** + * An implementation of the BulletinBoardServer which functions as a WebApp + */ +@Path(BULLETIN_BOARD_SERVER_PATH) +public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextListener{ + + private static final String BULLETIN_BOARD_ATTRIBUTE_NAME = "bulletinBoard"; + + @Context ServletContext servletContext; + + BulletinBoardServer bulletinBoard; + + /** + * This is the servlet init method. + */ + public void init(){ + bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME); + } + + /** + * This is the BulletinBoard init method. + */ + @Override + public void init(String meerkatDB) throws CommunicationException { + bulletinBoard.init(meerkatDB); + } + + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) { + ServletContext servletContext = servletContextEvent.getServletContext(); + String dbType = servletContext.getInitParameter("dbType"); + String dbName = servletContext.getInitParameter("dbName"); + + if ("SQLite".equals(dbType)){ + + bulletinBoard = new BulletinBoardSQLServer(new SQLiteQueryProvider(dbName)); + + } else if ("H2".equals(dbType)) { + + bulletinBoard = new BulletinBoardSQLServer(new H2QueryProvider(dbName)); + + } else if ("MySQL".equals(dbType)) { + + String dbAddress = servletContext.getInitParameter("dbAddress"); + int dbPort = Integer.parseInt(servletContext.getInitParameter("dbPort")); + String username = servletContext.getInitParameter("username"); + String password = servletContext.getInitParameter("password"); + + bulletinBoard = new BulletinBoardSQLServer(new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password)); + } + + try { + init(dbName); + servletContext.setAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME, bulletinBoard); + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + } + } + + @Path(POST_MESSAGE_PATH) + @POST + @Consumes(MEDIATYPE_PROTOBUF) + @Produces(MEDIATYPE_PROTOBUF) + @Override + public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException { + init(); + return bulletinBoard.postMessage(msg); + } + + @Override + public void readMessages(MessageFilterList filterList, MessageOutputStream out) throws CommunicationException { + init(); + 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 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 BoolValue 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 BoolValue 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 BoolValue 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 out) { + try { + init(); + bulletinBoard.readBatch(message, out); + } catch (CommunicationException | IllegalArgumentException e) { + System.err.println(e.getMessage()); + } + } + + @Path(GENERATE_SYNC_QUERY_PATH) + @POST + @Consumes(MEDIATYPE_PROTOBUF) + @Produces(MEDIATYPE_PROTOBUF) + @Override + public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException { + try { + init(); + return bulletinBoard.generateSyncQuery(generateSyncQueryParams); + } catch (CommunicationException | IllegalArgumentException e) { + System.err.println(e.getMessage()); + return null; + } + } + + @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 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 + public void close(){ + try { + bulletinBoard.close(); + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + } + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String test() { + return "This BulletinBoard is up and running!\n Please consult the API documents to perform queries."; + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + ServletContext servletContext = servletContextEvent.getServletContext(); + bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME); + close(); + } + +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/HelloProtoWebApp.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/HelloProtoWebApp.java index bf748c5..469fba9 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/HelloProtoWebApp.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/HelloProtoWebApp.java @@ -1,50 +1,50 @@ -package meerkat.bulletinboard.webapp; - -import com.google.protobuf.ByteString; -import com.google.protobuf.Message; -import meerkat.bulletinboard.service.HelloProtoBuf; -import meerkat.protobuf.Crypto.*; -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.rest.Constants; - -import javax.annotation.PostConstruct; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; - -@Path("/proto") -public class HelloProtoWebApp { - private HelloProtoBuf helloProtoBuf; - - @PostConstruct - public void init() { - helloProtoBuf = new HelloProtoBuf(); - } - - @GET - @Produces(Constants.MEDIATYPE_PROTOBUF) - public Message hello() { - 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}; - - Message msg; - - if (helloProtoBuf != null) { - msg = helloProtoBuf.sayHello(); - } else { - msg = BulletinBoardMessage.newBuilder() - .setMsg(UnsignedBulletinBoardMessage.newBuilder() - .addTag("Signature") - .addTag("Trustee") - .setData(ByteString.copyFrom(b1)).build()) - .addSig(Signature.newBuilder() - .setType(SignatureType.DSA) - .setData(ByteString.copyFrom(b2)) - .setSignerId(ByteString.copyFrom(b3)).build()) - .build(); - } - - return msg; - } -} +package meerkat.bulletinboard.webapp; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Message; +import meerkat.bulletinboard.service.HelloProtoBuf; +import meerkat.protobuf.Crypto.*; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.rest.Constants; + +import javax.annotation.PostConstruct; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +@Path("/proto") +public class HelloProtoWebApp { + private HelloProtoBuf helloProtoBuf; + + @PostConstruct + public void init() { + helloProtoBuf = new HelloProtoBuf(); + } + + @GET + @Produces(Constants.MEDIATYPE_PROTOBUF) + public Message hello() { + 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}; + + Message msg; + + if (helloProtoBuf != null) { + msg = helloProtoBuf.sayHello(); + } else { + msg = BulletinBoardMessage.newBuilder() + .setMsg(UnsignedBulletinBoardMessage.newBuilder() + .addTag("Signature") + .addTag("Trustee") + .setData(ByteString.copyFrom(b1)).build()) + .addSig(Signature.newBuilder() + .setType(SignatureType.DSA) + .setData(ByteString.copyFrom(b2)) + .setSignerId(ByteString.copyFrom(b3)).build()) + .build(); + } + + return msg; + } +} diff --git a/bulletin-board-server/src/main/proto/meerkat/bulletin_board_server.proto b/bulletin-board-server/src/main/proto/meerkat/bulletin_board_server.proto index e31485b..c284302 100644 --- a/bulletin-board-server/src/main/proto/meerkat/bulletin_board_server.proto +++ b/bulletin-board-server/src/main/proto/meerkat/bulletin_board_server.proto @@ -1,9 +1,9 @@ -syntax = "proto3"; - -package meerkat; - -option java_package = "meerkat.protobuf"; - -message Boolean { - bool value = 1; +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +message Boolean { + bool value = 1; } \ No newline at end of file diff --git a/bulletin-board-server/src/main/webapp/META-INF/jetty-env.xml b/bulletin-board-server/src/main/webapp/META-INF/jetty-env.xml index c4d368f..07c1470 100644 --- a/bulletin-board-server/src/main/webapp/META-INF/jetty-env.xml +++ b/bulletin-board-server/src/main/webapp/META-INF/jetty-env.xml @@ -1,12 +1,12 @@ - - - - - org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern - none - - - org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern - none - + + + + + org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern + none + + + org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern + none + \ No newline at end of file diff --git a/bulletin-board-server/src/main/webapp/WEB-INF/web.xml b/bulletin-board-server/src/main/webapp/WEB-INF/web.xml index 2198c07..387ed62 100644 --- a/bulletin-board-server/src/main/webapp/WEB-INF/web.xml +++ b/bulletin-board-server/src/main/webapp/WEB-INF/web.xml @@ -1,38 +1,38 @@ - - - Jersey Hello World - - org.glassfish.jersey.servlet.ServletContainer - - - jersey.config.server.provider.packages - meerkat - - 1 - - - Jersey Hello World - /* - - - dbAddress - localhost - - dbPort - 3306 - - dbName - meerkat - - username - arbel - - password - mypass - - dbType - SQLite - - meerkat.bulletinboard.webapp.BulletinBoardWebApp - - + + + Jersey Hello World + + org.glassfish.jersey.servlet.ServletContainer + + + jersey.config.server.provider.packages + meerkat + + 1 + + + Jersey Hello World + /* + + + dbAddress + localhost + + dbPort + 3306 + + dbName + meerkat + + username + arbel + + password + mypass + + dbType + H2 + + meerkat.bulletinboard.webapp.BulletinBoardWebApp + + diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java index 838adcc..844a050 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java @@ -1,124 +1,150 @@ -package meerkat.bulletinboard; - - -import com.google.protobuf.ByteString; -import com.google.protobuf.TextFormat; - -import meerkat.protobuf.Crypto.*; -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.rest.Constants; -import meerkat.rest.ProtobufMessageBodyReader; -import meerkat.rest.ProtobufMessageBodyWriter; - -import org.junit.Before; -import org.junit.Test; - -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; - -public class BulletinBoardSQLServerIntegrationTest { - - 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 client; - - @Before - public void setup() throws Exception { - System.err.println("Registering client"); - client = ClientBuilder.newClient(); - client.register(ProtobufMessageBodyReader.class); - client.register(ProtobufMessageBodyWriter.class); - - } - - @Test - public void testPost() throws Exception { - 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}; - - WebTarget webTarget; - Response response; - BoolMsg bool; - - BulletinBoardMessage msg; - - MessageFilterList filterList; - BulletinBoardMessageList msgList; - - // Test writing mechanism - - System.err.println("******** Testing: " + Constants.POST_MESSAGE_PATH); - webTarget = client.target(BASE_URL).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.POST_MESSAGE_PATH); - System.err.println(webTarget.getUri()); - - msg = BulletinBoardMessage.newBuilder() - .setMsg(UnsignedBulletinBoardMessage.newBuilder() - .addTag("Signature") - .addTag("Trustee") - .setData(ByteString.copyFrom(b1)) - .build()) - .addSig(Signature.newBuilder() - .setType(SignatureType.DSA) - .setData(ByteString.copyFrom(b2)) - .setSignerId(ByteString.copyFrom(b3)) - .build()) - .addSig(Signature.newBuilder() - .setType(SignatureType.ECDSA) - .setData(ByteString.copyFrom(b3)) - .setSignerId(ByteString.copyFrom(b2)) - .build()) - .build(); - - response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); - System.err.println(response); - bool = response.readEntity(BoolMsg.class); - assert bool.getValue(); - - msg = BulletinBoardMessage.newBuilder() - .setMsg(UnsignedBulletinBoardMessage.newBuilder() - .addTag("Vote") - .addTag("Trustee") - .setData(ByteString.copyFrom(b4)) - .build()) - .addSig(Signature.newBuilder() - .setType(SignatureType.ECDSA) - .setData(ByteString.copyFrom(b4)) - .setSignerId(ByteString.copyFrom(b2)) - .build()) - .build(); - - response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); - System.err.println(response); - bool = response.readEntity(BoolMsg.class); - assert bool.getValue(); - - // Test reading mechanism - - System.err.println("******** Testing: " + Constants.READ_MESSAGES_PATH); - webTarget = client.target(BASE_URL).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH); - filterList = MessageFilterList.newBuilder() - .addFilter( - MessageFilter.newBuilder() - .setType(FilterType.TAG) - .setTag("Vote") - .build() - ) - .build(); - - response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); - System.err.println(response); - msgList = response.readEntity(BulletinBoardMessageList.class); - System.err.println("List size: " + msgList.getMessageCount()); - System.err.println("This is the list:"); - System.err.println(TextFormat.printToString(msgList)); - assert msgList.getMessageCount() == 1; - } - -} +package meerkat.bulletinboard; + + +import com.google.protobuf.BoolValue; +import com.google.protobuf.ByteString; +import com.google.protobuf.TextFormat; + +import com.google.protobuf.Timestamp; +import meerkat.comm.MessageInputStream; +import meerkat.protobuf.Crypto.*; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Comm.*; +import static meerkat.bulletinboard.BulletinBoardConstants.*; +import meerkat.rest.Constants; +import meerkat.rest.ProtobufMessageBodyReader; +import meerkat.rest.ProtobufMessageBodyWriter; + +import org.junit.Before; +import org.junit.Test; + +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.io.InputStream; +import java.util.List; + +public class BulletinBoardSQLServerIntegrationTest { + + 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 client; + + @Before + public void setup() throws Exception { + System.err.println("Registering client"); + client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + + } + + @Test + public void testPost() throws Exception { + 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}; + + Timestamp t1 = Timestamp.newBuilder() + .setSeconds(8276482) + .setNanos(4314) + .build(); + + Timestamp t2 = Timestamp.newBuilder() + .setSeconds(987591) + .setNanos(1513) + .build(); + + WebTarget webTarget; + Response response; + BoolValue bool; + + BulletinBoardMessage msg; + + MessageFilterList filterList; + List msgList; + + // Test writing mechanism + + System.err.println("******** Testing: " + POST_MESSAGE_PATH); + webTarget = client.target(BASE_URL).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH); + System.err.println(webTarget.getUri()); + + msg = BulletinBoardMessage.newBuilder() + .setMsg(UnsignedBulletinBoardMessage.newBuilder() + .addTag("Signature") + .addTag("Trustee") + .setData(ByteString.copyFrom(b1)) + .setTimestamp(t1) + .build()) + .addSig(Signature.newBuilder() + .setType(SignatureType.DSA) + .setData(ByteString.copyFrom(b2)) + .setSignerId(ByteString.copyFrom(b3)) + .build()) + .addSig(Signature.newBuilder() + .setType(SignatureType.ECDSA) + .setData(ByteString.copyFrom(b3)) + .setSignerId(ByteString.copyFrom(b2)) + .build()) + .build(); + + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); + System.err.println(response); + bool = response.readEntity(BoolValue.class); + assert bool.getValue(); + + msg = BulletinBoardMessage.newBuilder() + .setMsg(UnsignedBulletinBoardMessage.newBuilder() + .addTag("Vote") + .addTag("Trustee") + .setData(ByteString.copyFrom(b4)) + .setTimestamp(t2) + .build()) + .addSig(Signature.newBuilder() + .setType(SignatureType.ECDSA) + .setData(ByteString.copyFrom(b4)) + .setSignerId(ByteString.copyFrom(b2)) + .build()) + .build(); + + response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); + System.err.println(response); + bool = response.readEntity(BoolValue.class); + assert bool.getValue(); + + // Test reading mechanism + + System.err.println("******** Testing: " + READ_MESSAGES_PATH); + webTarget = client.target(BASE_URL).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH); + filterList = MessageFilterList.newBuilder() + .addFilter( + MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag("Vote") + .build() + ) + .build(); + + InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF), InputStream.class); + + MessageInputStream inputStream = + MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BulletinBoardMessage.class); + + msgList = inputStream.asList(); + System.err.println("List size: " + msgList.size()); + System.err.println("This is the list:"); + + for (BulletinBoardMessage message : msgList) { + System.err.println(TextFormat.printToString(message)); + } + + assert msgList.size() == 1; + } + +} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java index 4799e0d..49c3050 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java @@ -1,391 +1,716 @@ -package meerkat.bulletinboard; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.util.List; -import java.util.Random; - -import com.google.protobuf.ByteString; - -import meerkat.comm.CommunicationException; -import meerkat.crypto.concrete.ECDSASignature; -import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; -import meerkat.protobuf.BulletinBoardAPI.FilterType; -import meerkat.protobuf.BulletinBoardAPI.MessageFilter; -import meerkat.protobuf.BulletinBoardAPI.MessageFilterList; -import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; - -import static org.junit.Assert.*; -import static org.hamcrest.CoreMatchers.*; - -public class GenericBulletinBoardServerTest { - - protected BulletinBoardServer bulletinBoardServer; - private ECDSASignature signers[]; - private ByteString[] signerIDs; - - private Random random; - - private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; - private static String KEYFILE_EXAMPLE3 = "/certs/enduser-certs/user3-key-with-password-shh.p12"; - - private static String KEYFILE_PASSWORD1 = "secret"; - private static String KEYFILE_PASSWORD3 = "shh"; - - public static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt"; - public static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt"; - - private final int TAG_NUM = 5; // Number of tags. - private final int MESSAGE_NUM = 32; // Number of messages (2^TAG_NUM). - - private String[] tags; - private byte[][] data; - - private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests - - /** - * @param bulletinBoardServer is an initialized server. - * @throws InstantiationException - * @throws IllegalAccessException - * @throws CertificateException - * @throws KeyStoreException - * @throws NoSuchAlgorithmException - * @throws IOException - * @throws UnrecoverableKeyException - * @throws CommunicationException - */ - public void init(BulletinBoardServer bulletinBoardServer) { - - System.err.println("Starting to initialize GenericBulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - this.bulletinBoardServer = bulletinBoardServer; - - signers = new ECDSASignature[2]; - signerIDs = new ByteString[signers.length]; - signers[0] = new ECDSASignature(); - signers[1] = new ECDSASignature(); - - InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); - char[] password = KEYFILE_PASSWORD1.toCharArray(); - - KeyStore.Builder keyStoreBuilder = null; - try { - keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); - - signers[0].loadSigningCertificate(keyStoreBuilder); - - signers[0].loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE)); - - keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE3); - password = KEYFILE_PASSWORD3.toCharArray(); - - keyStoreBuilder = signers[1].getPKCS12KeyStoreBuilder(keyStream, password); - signers[1].loadSigningCertificate(keyStoreBuilder); - - signers[1].loadVerificationCertificates(getClass().getResourceAsStream(CERT3_PEM_EXAMPLE)); - - for (int i = 0 ; i < signers.length ; i++) { - signerIDs[i] = signers[i].getSignerID(); - } - - } catch (IOException e) { - System.err.println("Failed reading from signature file " + e.getMessage()); - fail("Failed reading from signature file " + e.getMessage()); - } catch (CertificateException e) { - System.err.println("Failed reading certificate " + e.getMessage()); - fail("Failed reading certificate " + e.getMessage()); - } catch (KeyStoreException e) { - System.err.println("Failed reading keystore " + e.getMessage()); - fail("Failed reading keystore " + e.getMessage()); - } catch (NoSuchAlgorithmException e) { - System.err.println("Couldn't find signing algorithm " + e.getMessage()); - fail("Couldn't find signing algorithm " + e.getMessage()); - } catch (UnrecoverableKeyException e) { - System.err.println("Couldn't find signing key " + e.getMessage()); - fail("Couldn't find signing key " + e.getMessage()); - } - - random = new Random(0); // We use insecure randomness in tests for repeatability - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished initializing GenericBulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - - private byte randomByte(){ - return (byte) random.nextInt(); - } - - private String randomString(){ - return new BigInteger(130, random).toString(32); - } - - /** - * Tests writing of several messages with multiple tags and signatures. - * @throws CommunicationException - * @throws SignatureException - * @throws InvalidKeyException - * @throws CertificateException - * @throws IOException - */ - public void testInsert() { - - System.err.println("Starting to insert messages to DB"); - long start = threadBean.getCurrentThreadCpuTime(); - - final int BYTES_PER_MESSAGE_DATA = 50; // Message size. - - tags = new String[TAG_NUM]; - data = new byte[MESSAGE_NUM][BYTES_PER_MESSAGE_DATA]; - - UnsignedBulletinBoardMessage.Builder unsignedMsgBuilder; - BulletinBoardMessage.Builder msgBuilder; - - int i, j; - - // Generate random data. - - for (i = 1; i <= MESSAGE_NUM; i++) { - for (j = 0; j < BYTES_PER_MESSAGE_DATA; j++) { - data[i - 1][j] = randomByte(); - } - } - - for (i = 0; i < TAG_NUM; i++) { - tags[i] = randomString(); - } - - // Build messages. - - for (i = 1; i <= MESSAGE_NUM; i++) { - unsignedMsgBuilder = UnsignedBulletinBoardMessage.newBuilder() - .setData(ByteString.copyFrom(data[i - 1])); - - // Add tags based on bit-representation of message number. - - int copyI = i; - for (j = 0; j < TAG_NUM; j++) { - if (copyI % 2 == 1) { - unsignedMsgBuilder.addTag(tags[j]); - } - - copyI >>>= 1; - } - - // Build message. - - msgBuilder = BulletinBoardMessage.newBuilder() - .setMsg(unsignedMsgBuilder.build()); - - // Add signatures. - - try { - - if (i % 2 == 1) { - signers[0].updateContent(msgBuilder.getMsg()); - msgBuilder.addSig(signers[0].sign()); - - if (i % 4 == 1) { - signers[1].updateContent(msgBuilder.getMsg()); - msgBuilder.addSig(signers[1].sign()); - } - } - - } catch (SignatureException e) { - fail(e.getMessage()); - } - - // Post message. - - try { - bulletinBoardServer.postMessage(msgBuilder.build()); - } catch (CommunicationException e) { - fail(e.getMessage()); - } - } - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished inserting messages to DB"); - System.err.println("Time of operation: " + (end - start)); - - } - - /** - * Tests retrieval of messages written in {@Link #testInsert()} - * Only queries using one tag filter - */ - public void testSimpleTagAndSignature(){ - - System.err.println("Starting to test tag and signature mechanism"); - long start = threadBean.getCurrentThreadCpuTime(); - - List messages; - - // Check tag mechanism - - for (int i = 0 ; i < TAG_NUM ; i++){ - - // Retrieve messages having tag i - - try { - - messages = bulletinBoardServer.readMessages( - MessageFilterList.newBuilder() - .addFilter(MessageFilter.newBuilder() - .setType(FilterType.TAG) - .setTag(tags[i]) - .build() - ) - .build() - ) - .getMessageList(); - - } catch (CommunicationException e) { - fail(e.getMessage()); - return; - } - - // Assert that the number of retrieved messages is correct. - - assertThat(messages.size(), is(MESSAGE_NUM / 2)); - - // Assert the identity of the messages. - - for (BulletinBoardMessage msg : messages){ - - // Assert serial number and raw data. - - assertThat((msg.getEntryNum() >>> i) % 2 , is((long) 1)); - assertThat(msg.getMsg().getData().toByteArray(), is(data[(int) msg.getEntryNum() - 1])); - - // Assert signatures. - - try { - - if (msg.getEntryNum() % 2 == 1) { - signers[0].initVerify(msg.getSig(0)); - signers[0].updateContent(msg.getMsg()); - assertTrue("Signature did not verify!", signers[0].verify()); - - if (msg.getEntryNum() % 4 == 1) { - signers[1].initVerify(msg.getSig(1)); - signers[1].updateContent(msg.getMsg()); - assertTrue("Signature did not verify!", signers[1].verify()); - - assertThat(msg.getSigCount(), is(2)); - } else { - assertThat(msg.getSigCount(), is(1)); - } - } else { - assertThat(msg.getSigCount(), is(0)); - } - } catch (Exception e) { - fail(e.getMessage()); - } - } - - } - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished testing tag and signature mechanism"); - System.err.println("Time of operation: " + (end - start)); - - } - - /** - * Tests retrieval of messages written in {@Link #testInsert()} using multiple tags/signature filters. - */ - public void testEnhancedTagsAndSignatures(){ - - System.err.println("Starting to test multiple tags and signatures"); - long start = threadBean.getCurrentThreadCpuTime(); - - List messages; - MessageFilterList.Builder filterListBuilder = MessageFilterList.newBuilder(); - - int expectedMsgCount = MESSAGE_NUM; - - // Check multiple tag filters. - - for (int i = 0 ; i < TAG_NUM ; i++) { - - filterListBuilder.addFilter( - MessageFilter.newBuilder() - .setType(FilterType.TAG) - .setTag(tags[i]) - .build() - ); - - try { - messages = bulletinBoardServer.readMessages(filterListBuilder.build()).getMessageList(); - } catch (CommunicationException e) { - System.err.println("Failed retrieving multi-tag messages from DB: " + e.getMessage()); - fail("Failed retrieving multi-tag messages from DB: " + e.getMessage()); - return; - } - - expectedMsgCount /= 2; - - assertThat(messages.size(), is(expectedMsgCount)); - - for (BulletinBoardMessage msg : messages) { - for (int j = 0 ; j <= i ; j++) { - assertThat((msg.getEntryNum() >>> j) % 2, is((long) 1)); - } - } - } - - // Check multiple signature filters. - - filterListBuilder = MessageFilterList.newBuilder() - .addFilter(MessageFilter.newBuilder() - .setType(FilterType.SIGNER_ID) - .setId(signerIDs[0]) - .build()) - .addFilter(MessageFilter.newBuilder() - .setType(FilterType.SIGNER_ID) - .setId(signerIDs[1]) - .build()); - - try { - messages = bulletinBoardServer.readMessages(filterListBuilder.build()).getMessageList(); - } catch (CommunicationException e) { - System.err.println("Failed retrieving multi-signature message from DB: " + e.getMessage()); - fail("Failed retrieving multi-signature message from DB: " + e.getMessage()); - return; - } - - assertThat(messages.size(), is(MESSAGE_NUM / 4)); - - for (BulletinBoardMessage message : messages) { - assertThat(message.getEntryNum() % 4, is((long) 1)); - } - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished testing multiple tags and signatures"); - System.err.println("Time of operation: " + (end - start)); - - } - - public void close(){ - signers[0].clearSigningKey(); - signers[1].clearSigningKey(); - try { - bulletinBoardServer.close(); - } catch (CommunicationException e) { - System.err.println("Error closing server " + e.getMessage()); - fail("Error closing server " + e.getMessage()); - } - } -} +package meerkat.bulletinboard; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.lang.reflect.InvocationTargetException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.*; + +import com.google.protobuf.BoolValue; +import com.google.protobuf.ByteString; + +import com.google.protobuf.Timestamp; +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.SHA256Digest; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Comm.*; +import meerkat.util.BulletinBoardMessageGenerator; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; + +public class GenericBulletinBoardServerTest { + + protected BulletinBoardServer bulletinBoardServer; + private GenericBatchDigitalSignature[] signers; + private ByteString[] signerIDs; + + private Random random; + + private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; + private static String KEYFILE_EXAMPLE3 = "/certs/enduser-certs/user3-key-with-password-shh.p12"; + + private static String KEYFILE_PASSWORD1 = "secret"; + private static String KEYFILE_PASSWORD3 = "shh"; + + public static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt"; + public static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt"; + + private final int TAG_NUM = 5; // Number of tags. + private final int MESSAGE_NUM = 32; // Number of messages (2^TAG_NUM). + + private String[] tags; + private byte[][] data; + + private List completeBatches; + + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests + + private BulletinBoardMessageGenerator bulletinBoardMessageGenerator; + + private Digest digest; + + /** + * @param bulletinBoardServer is an initialized server. + * @throws InstantiationException + * @throws IllegalAccessException + * @throws CertificateException + * @throws KeyStoreException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws UnrecoverableKeyException + * @throws CommunicationException + */ + public void init(BulletinBoardServer bulletinBoardServer) { + + System.err.println("Starting to initialize GenericBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + this.bulletinBoardServer = bulletinBoardServer; + + signers = new 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 = null; + try { + keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); + + signers[0].loadSigningCertificate(keyStoreBuilder); + + signers[0].loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE)); + + keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE3); + password = KEYFILE_PASSWORD3.toCharArray(); + + keyStoreBuilder = signers[1].getPKCS12KeyStoreBuilder(keyStream, password); + signers[1].loadSigningCertificate(keyStoreBuilder); + + signers[1].loadVerificationCertificates(getClass().getResourceAsStream(CERT3_PEM_EXAMPLE)); + + for (int i = 0 ; i < signers.length ; i++) { + signerIDs[i] = signers[i].getSignerID(); + } + + } catch (IOException e) { + System.err.println("Failed reading from signature file " + e.getMessage()); + fail("Failed reading from signature file " + e.getMessage()); + } catch (CertificateException e) { + System.err.println("Failed reading certificate " + e.getMessage()); + fail("Failed reading certificate " + e.getMessage()); + } catch (KeyStoreException e) { + System.err.println("Failed reading keystore " + e.getMessage()); + fail("Failed reading keystore " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + System.err.println("Couldn't find signing algorithm " + e.getMessage()); + fail("Couldn't find signing algorithm " + e.getMessage()); + } catch (UnrecoverableKeyException e) { + System.err.println("Couldn't find signing key " + e.getMessage()); + fail("Couldn't find signing key " + e.getMessage()); + } + + // We use insecure randomness in tests for repeatability + random = new Random(0); + bulletinBoardMessageGenerator = new BulletinBoardMessageGenerator(random); + + digest = new SHA256Digest(); + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished initializing GenericBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + + // Initialize Batch variables + + completeBatches = new ArrayList(10); + + } + + private byte randomByte(){ + return (byte) random.nextInt(); + } + + private String randomString(){ + return new BigInteger(130, random).toString(32); + } + + /** + * Tests writing of several messages with multiple tags and signatures. + * @throws CommunicationException + * @throws SignatureException + * @throws InvalidKeyException + * @throws CertificateException + * @throws IOException + */ + public void testInsert() { + + System.err.println("Starting to insert messages to DB"); + long start = threadBean.getCurrentThreadCpuTime(); + + final int BYTES_PER_MESSAGE_DATA = 50; // Message size. + + tags = new String[TAG_NUM]; + data = new byte[MESSAGE_NUM][BYTES_PER_MESSAGE_DATA]; + + UnsignedBulletinBoardMessage.Builder unsignedMsgBuilder; + BulletinBoardMessage.Builder msgBuilder; + + int i, j; + + // Generate random data. + + for (i = 1; i <= MESSAGE_NUM; i++) { + for (j = 0; j < BYTES_PER_MESSAGE_DATA; j++) { + data[i - 1][j] = randomByte(); + } + } + + for (i = 0; i < TAG_NUM; i++) { + tags[i] = randomString(); + } + + // Build messages. + + for (i = 1; i <= MESSAGE_NUM; i++) { + unsignedMsgBuilder = UnsignedBulletinBoardMessage.newBuilder() + .setData(ByteString.copyFrom(data[i - 1])) + .setTimestamp(Timestamp.newBuilder() + .setSeconds(i) + .setNanos(i) + .build()); + + // Add tags based on bit-representation of message number. + + int copyI = i; + for (j = 0; j < TAG_NUM; j++) { + if (copyI % 2 == 1) { + unsignedMsgBuilder.addTag(tags[j]); + } + + copyI >>>= 1; + } + + // Build message. + + msgBuilder = BulletinBoardMessage.newBuilder() + .setMsg(unsignedMsgBuilder.build()); + + // Add signatures. + + try { + + if (i % 2 == 1) { + signers[0].updateContent(msgBuilder.getMsg()); + msgBuilder.addSig(signers[0].sign()); + + if (i % 4 == 1) { + signers[1].updateContent(msgBuilder.getMsg()); + msgBuilder.addSig(signers[1].sign()); + } + } + + } catch (SignatureException e) { + fail(e.getMessage()); + } + + // Post message. + + try { + bulletinBoardServer.postMessage(msgBuilder.build()); + } catch (CommunicationException e) { + fail(e.getMessage()); + } + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished inserting messages to DB"); + System.err.println("Time of operation: " + (end - start)); + + } + + /** + * Tests retrieval of messages written in {@Link #testInsert()} + * Only queries using one tag filter + */ + public void testSimpleTagAndSignature(){ + + System.err.println("Starting to test tag and signature mechanism"); + long start = threadBean.getCurrentThreadCpuTime(); + + List messages = new LinkedList<>(); + + // Check tag mechanism + + for (int i = 0 ; i < TAG_NUM ; i++){ + + // Retrieve messages having tag i + + try { + + MessageFilterList filterList = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag(tags[i]) + .build() + ) + .build(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + bulletinBoardServer.readMessages(filterList, new MessageOutputStream(outputStream)); + + MessageInputStream 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()); + return; + } + + // Assert that the number of retrieved messages is correct. + + assertThat(messages.size(), is(MESSAGE_NUM / 2)); + + // Assert the identity of the messages. + + for (BulletinBoardMessage msg : messages){ + + // Assert serial number and raw data. + + assertThat((msg.getEntryNum() >>> i) % 2 , is((long) 1)); + assertThat(msg.getMsg().getData().toByteArray(), is(data[(int) msg.getEntryNum() - 1])); + + // Assert signatures. + + try { + + if (msg.getEntryNum() % 2 == 1) { + signers[0].initVerify(msg.getSig(0)); + signers[0].updateContent(msg.getMsg()); + assertTrue("Signature did not verify!", signers[0].verify()); + + if (msg.getEntryNum() % 4 == 1) { + signers[1].initVerify(msg.getSig(1)); + signers[1].updateContent(msg.getMsg()); + assertTrue("Signature did not verify!", signers[1].verify()); + + assertThat(msg.getSigCount(), is(2)); + } else { + assertThat(msg.getSigCount(), is(1)); + } + } else { + assertThat(msg.getSigCount(), is(0)); + } + } catch (Exception e) { + fail(e.getMessage()); + } + } + + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished testing tag and signature mechanism"); + System.err.println("Time of operation: " + (end - start)); + + } + + /** + * Tests retrieval of messages written in {@Link #testInsert()} using multiple tags/signature filters. + */ + public void testEnhancedTagsAndSignatures(){ + + System.err.println("Starting to test multiple tags and signatures"); + long start = threadBean.getCurrentThreadCpuTime(); + + List messages; + MessageFilterList.Builder filterListBuilder = MessageFilterList.newBuilder(); + + int expectedMsgCount = MESSAGE_NUM; + + // Check multiple tag filters. + + for (int i = 0 ; i < TAG_NUM ; i++) { + + filterListBuilder.addFilter( + MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag(tags[i]) + .build() + ); + + try { + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + bulletinBoardServer.readMessages(filterListBuilder.build(), new MessageOutputStream(outputStream)); + + MessageInputStream inputStream = + MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream( + outputStream.toByteArray()), + BulletinBoardMessage.class); + + messages = inputStream.asList(); + + } catch (CommunicationException e) { + System.err.println("Failed retrieving multi-tag messages from DB: " + e.getMessage()); + fail("Failed retrieving multi-tag messages from DB: " + e.getMessage()); + return; + } 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; + + assertThat(messages.size(), is(expectedMsgCount)); + + for (BulletinBoardMessage msg : messages) { + for (int j = 0 ; j <= i ; j++) { + assertThat((msg.getEntryNum() >>> j) % 2, is((long) 1)); + } + } + } + + // Check multiple signature filters. + + filterListBuilder = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.SIGNER_ID) + .setId(signerIDs[0]) + .build()) + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.SIGNER_ID) + .setId(signerIDs[1]) + .build()); + + try { + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + bulletinBoardServer.readMessages(filterListBuilder.build(), new MessageOutputStream(outputStream)); + + MessageInputStream inputStream = + MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream( + outputStream.toByteArray()), + BulletinBoardMessage.class); + + messages = inputStream.asList(); + + } catch (CommunicationException e) { + System.err.println("Failed retrieving multi-signature message from DB: " + e.getMessage()); + fail("Failed retrieving multi-signature message from DB: " + e.getMessage()); + return; + } 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)); + + for (BulletinBoardMessage message : messages) { + assertThat(message.getEntryNum() % 4, is((long) 1)); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished testing multiple tags and signatures"); + System.err.println("Time of operation: " + (end - start)); + + } + + /** + * 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()); + BoolValue 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(); + + BoolValue 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 dataOrder = new ArrayList(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(outputStream)); + + MessageInputStream inputStream = + MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream( + outputStream.toByteArray()), + BatchData.class); + + List 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); + + BoolValue 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(outputStream)); + + MessageInputStream 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(){ + signers[0].clearSigningKey(); + signers[1].clearSigningKey(); + try { + bulletinBoardServer.close(); + } catch (CommunicationException e) { + System.err.println("Error closing server " + e.getMessage()); + fail("Error closing server " + e.getMessage()); + } + } +} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java index ef19310..a55cc74 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java @@ -1,122 +1,165 @@ -package meerkat.bulletinboard; - -import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; -import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; -import meerkat.bulletinboard.sqlserver.H2QueryProvider; -import meerkat.comm.CommunicationException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.Result; - -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; -import java.sql.*; -import java.util.List; - -import static org.junit.Assert.fail; - -/** - * Created by Arbel Deutsch Peled on 07-Dec-15. - */ -public class H2BulletinBoardServerTest { - - private final String dbName = "meerkatTest"; - - private GenericBulletinBoardServerTest serverTest; - - private SQLQueryProvider queryProvider; - - private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests - - @Before - public void init(){ - - System.err.println("Starting to initialize H2BulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - queryProvider = new H2QueryProvider(dbName); - - try { - - Connection conn = queryProvider.getDataSource().getConnection(); - Statement stmt = conn.createStatement(); - - List deletionQueries = queryProvider.getSchemaDeletionCommands(); - - for (String deletionQuery : deletionQueries) { - stmt.execute(deletionQuery); - } - - } catch (SQLException e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider); - try { - bulletinBoardServer.init(""); - - } catch (CommunicationException e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - return; - } - - serverTest = new GenericBulletinBoardServerTest(); - try { - serverTest.init(bulletinBoardServer); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished initializing H2BulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - - @Test - public void bulkTest() { - System.err.println("Starting bulkTest of H2BulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - try { - serverTest.testInsert(); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - try{ - serverTest.testSimpleTagAndSignature(); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - try{ - serverTest.testEnhancedTagsAndSignatures(); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished bulkTest of H2BulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - - @After - public void close() { - System.err.println("Starting to close H2BulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - serverTest.close(); - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished closing H2BulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - -} +package meerkat.bulletinboard; + +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; +import meerkat.bulletinboard.sqlserver.H2QueryProvider; +import meerkat.comm.CommunicationException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Result; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.sql.*; +import java.util.List; + +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 07-Dec-15. + */ +public class H2BulletinBoardServerTest { + + private final String dbName = "meerkatTest"; + + private GenericBulletinBoardServerTest serverTest; + + private SQLQueryProvider queryProvider; + + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests + + @Before + public void init(){ + + System.err.println("Starting to initialize H2BulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + queryProvider = new H2QueryProvider(dbName); + + try { + + Connection conn = queryProvider.getDataSource().getConnection(); + Statement stmt = conn.createStatement(); + + List deletionQueries = queryProvider.getSchemaDeletionCommands(); + + for (String deletionQuery : deletionQueries) { + stmt.execute(deletionQuery); + } + + } catch (SQLException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider); + try { + bulletinBoardServer.init(""); + + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + return; + } + + serverTest = new GenericBulletinBoardServerTest(); + try { + serverTest.init(bulletinBoardServer); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished initializing H2BulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @Test + public void bulkTest() { + System.err.println("Starting bulkTest of H2BulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + try { + serverTest.testInsert(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testSimpleTagAndSignature(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testEnhancedTagsAndSignatures(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished bulkTest of H2BulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @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 + public void close() { + System.err.println("Starting to close H2BulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + serverTest.close(); + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished closing H2BulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + +} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/HelloProtoIntegrationTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/HelloProtoIntegrationTest.java index c7b16df..a0c3653 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/HelloProtoIntegrationTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/HelloProtoIntegrationTest.java @@ -1,42 +1,42 @@ -package meerkat.bulletinboard; - -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.rest.Constants; -import meerkat.rest.ProtobufMessageBodyReader; -import meerkat.rest.ProtobufMessageBodyWriter; -import org.junit.Test; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.WebTarget; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Created by talm on 10/11/15. - */ -public class HelloProtoIntegrationTest { - 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); - private static String HELLO_URL = "proto"; - - @Test - public void testHello() throws Exception { - Client client = ClientBuilder.newClient(); - client.register(ProtobufMessageBodyReader.class); - client.register(ProtobufMessageBodyWriter.class); - - WebTarget webTarget = client.target(BASE_URL).path(HELLO_URL); - BulletinBoardMessage response = webTarget.request(Constants.MEDIATYPE_PROTOBUF) - .get(BulletinBoardMessage.class); - - System.out.println(response.getMsg().getData()); - - assertThat(response.getMsg().getData().toStringUtf8(), is("Hello World!")); - assertThat(response.getMsg().getTagCount(), is(2)); - assertThat(response.getMsg().getTag(0), is("Greetings")); - assertThat(response.getMsg().getTag(1), is("FirstPrograms")); - } -} +package meerkat.bulletinboard; + +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.rest.Constants; +import meerkat.rest.ProtobufMessageBodyReader; +import meerkat.rest.ProtobufMessageBodyWriter; +import org.junit.Test; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Created by talm on 10/11/15. + */ +public class HelloProtoIntegrationTest { + 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); + private static String HELLO_URL = "proto"; + + @Test + public void testHello() throws Exception { + Client client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + + WebTarget webTarget = client.target(BASE_URL).path(HELLO_URL); + BulletinBoardMessage response = webTarget.request(Constants.MEDIATYPE_PROTOBUF) + .get(BulletinBoardMessage.class); + + System.out.println(response.getMsg().getData()); + + assertThat(response.getMsg().getData().toStringUtf8(), is("Hello World!")); + assertThat(response.getMsg().getTagCount(), is(2)); + assertThat(response.getMsg().getTag(0), is("Greetings")); + assertThat(response.getMsg().getTag(1), is("FirstPrograms")); + } +} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java index e473931..48f8d89 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java @@ -1,126 +1,172 @@ -package meerkat.bulletinboard; - -import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; -import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; -import meerkat.bulletinboard.sqlserver.MySQLQueryProvider; -import meerkat.comm.CommunicationException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.List; - -import static org.junit.Assert.fail; - -/** - * Created by Arbel Deutsch Peled on 07-Dec-15. - */ -public class MySQLBulletinBoardServerTest { - - private final String dbAddress = "localhost"; - private final int dbPort = 3306; - private final String dbName = "meerkat"; - private final String username = "arbel"; - private final String password = "mypass"; - - private GenericBulletinBoardServerTest serverTest; - - private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests - - @Before - public void init(){ - - System.err.println("Starting to initialize MySQLBulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - SQLQueryProvider queryProvider = new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password); - - try { - - Connection conn = queryProvider.getDataSource().getConnection(); - Statement stmt = conn.createStatement(); - - List deletionQueries = queryProvider.getSchemaDeletionCommands(); - - for (String deletionQuery : deletionQueries) { - stmt.execute(deletionQuery); - } - - } catch (SQLException e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider); - try { - bulletinBoardServer.init(""); - - } catch (CommunicationException e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - return; - } - - serverTest = new GenericBulletinBoardServerTest(); - try { - serverTest.init(bulletinBoardServer); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished initializing MySQLBulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - - @Test - public void bulkTest() { - System.err.println("Starting bulkTest of MySQLBulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - try { - serverTest.testInsert(); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - try{ - serverTest.testSimpleTagAndSignature(); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - try{ - serverTest.testEnhancedTagsAndSignatures(); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished bulkTest of MySQLBulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - - @After - public void close() { - System.err.println("Starting to close MySQLBulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - serverTest.close(); - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished closing MySQLBulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - -} +package meerkat.bulletinboard; + +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; +import meerkat.bulletinboard.sqlserver.MySQLQueryProvider; +import meerkat.comm.CommunicationException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.lang.reflect.InvocationTargetException; +import java.security.SignatureException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 07-Dec-15. + */ +public class MySQLBulletinBoardServerTest { + + private final String dbAddress = "localhost"; + private final int dbPort = 3306; + private final String dbName = "meerkat"; + private final String username = "arbel"; + private final String password = "mypass"; + + private GenericBulletinBoardServerTest serverTest; + + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests + + @Before + public void init(){ + + System.err.println("Starting to initialize MySQLBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + SQLQueryProvider queryProvider = new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password); + + try { + + Connection conn = queryProvider.getDataSource().getConnection(); + Statement stmt = conn.createStatement(); + + List deletionQueries = queryProvider.getSchemaDeletionCommands(); + + for (String deletionQuery : deletionQueries) { + stmt.execute(deletionQuery); + } + + } catch (SQLException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider); + try { + bulletinBoardServer.init(""); + + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + return; + } + + serverTest = new GenericBulletinBoardServerTest(); + try { + serverTest.init(bulletinBoardServer); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished initializing MySQLBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @Test + public void bulkTest() { + System.err.println("Starting bulkTest of MySQLBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + try { + serverTest.testInsert(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testSimpleTagAndSignature(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testEnhancedTagsAndSignatures(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished bulkTest of MySQLBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @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 + public void close() { + System.err.println("Starting to close MySQLBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + serverTest.close(); + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished closing MySQLBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + +} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java index 1d7aae0..cbbd6f9 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java @@ -1,106 +1,106 @@ -package meerkat.bulletinboard; - -import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; -import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider; -import meerkat.comm.CommunicationException; -import meerkat.protobuf.*; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; -import java.security.*; -import java.security.cert.CertificateException; - -import static org.junit.Assert.fail; - -/** - * Created by Arbel Deutsch Peled on 07-Dec-15. - */ -public class SQLiteBulletinBoardServerTest{ - - private String testFilename = "SQLiteDBTest.db"; - - private GenericBulletinBoardServerTest serverTest; - - private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests - - @Before - public void init(){ - - System.err.println("Starting to initialize SQLiteBulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - File old = new File(testFilename); - old.delete(); - - BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(new SQLiteQueryProvider(testFilename)); - try { - bulletinBoardServer.init(""); - - } catch (CommunicationException e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - return; - } - - serverTest = new GenericBulletinBoardServerTest(); - try { - serverTest.init(bulletinBoardServer); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished initializing SQLiteBulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - - @Test - public void bulkTest() { - System.err.println("Starting bulkTest of SQLiteBulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - try { - serverTest.testInsert(); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - try{ - serverTest.testSimpleTagAndSignature(); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - try{ - serverTest.testEnhancedTagsAndSignatures(); - } catch (Exception e) { - System.err.println(e.getMessage()); - fail(e.getMessage()); - } - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished bulkTest of SQLiteBulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - - @After - public void close() { - System.err.println("Starting to close SQLiteBulletinBoardServerTest"); - long start = threadBean.getCurrentThreadCpuTime(); - - serverTest.close(); - - long end = threadBean.getCurrentThreadCpuTime(); - System.err.println("Finished closing SQLiteBulletinBoardServerTest"); - System.err.println("Time of operation: " + (end - start)); - } - -} +package meerkat.bulletinboard; + +import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; +import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider; +import meerkat.comm.CommunicationException; +import meerkat.protobuf.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.security.*; +import java.security.cert.CertificateException; + +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 07-Dec-15. + */ +public class SQLiteBulletinBoardServerTest{ + + private String testFilename = "SQLiteDBTest.db"; + + private GenericBulletinBoardServerTest serverTest; + + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests + + @Before + public void init(){ + + System.err.println("Starting to initialize SQLiteBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + File old = new File(testFilename); + old.delete(); + + BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(new SQLiteQueryProvider(testFilename)); + try { + bulletinBoardServer.init(""); + + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + return; + } + + serverTest = new GenericBulletinBoardServerTest(); + try { + serverTest.init(bulletinBoardServer); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished initializing SQLiteBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @Test + public void bulkTest() { + System.err.println("Starting bulkTest of SQLiteBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + try { + serverTest.testInsert(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testSimpleTagAndSignature(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + try{ + serverTest.testEnhancedTagsAndSignatures(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished bulkTest of SQLiteBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + + @After + public void close() { + System.err.println("Starting to close SQLiteBulletinBoardServerTest"); + long start = threadBean.getCurrentThreadCpuTime(); + + serverTest.close(); + + long end = threadBean.getCurrentThreadCpuTime(); + System.err.println("Finished closing SQLiteBulletinBoardServerTest"); + System.err.println("Time of operation: " + (end - start)); + } + +} diff --git a/distributed-key-generation/build.gradle b/distributed-key-generation/build.gradle new file mode 100644 index 0000000..8f0a397 --- /dev/null +++ b/distributed-key-generation/build.gradle @@ -0,0 +1,223 @@ + +plugins { + id "us.kirchmeier.capsule" version "1.0.1" + id 'com.google.protobuf' version '0.7.0' +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' + +apply plugin: 'maven-publish' + +// Uncomment the lines below to define an application +// (this will also allow you to build a "fatCapsule" which includes +// the entire application, including all dependencies in a single jar) +//apply plugin: 'application' +//mainClassName='your.main.ApplicationClass' + +// Is this a snapshot version? +ext { isSnapshot = false } + +ext { + groupId = 'org.factcenter.meerkat' + nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" + + // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) + // Should be set in ${HOME}/.gradle/gradle.properties + nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" + nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" +} + +description = "TODO: Add a description" + +// Your project version +version = "0.0" + +version += "${isSnapshot ? '-SNAPSHOT' : ''}" + + +dependencies { + // Meerkat common + compile project(':meerkat-common') + + // Logging + compile 'org.slf4j:slf4j-api:1.7.7' + runtime 'ch.qos.logback:logback-classic:1.1.2' + runtime 'ch.qos.logback:logback-core:1.1.2' + + // Google protobufs + compile 'com.google.protobuf:protobuf-java:3.+' + + // Depend on test resources from meerkat-common + testCompile project(path: ':meerkat-common', configuration: 'testOutput') + + testCompile 'junit:junit:4.+' + + runtime 'org.codehaus.groovy:groovy:2.4.+' +} + + +/*==== You probably don't have to edit below this line =======*/ + +// Setup test configuration that can appear as a dependency in +// other subprojects +configurations { + testOutput.extendsFrom (testCompile) +} + +task testJar(type: Jar, dependsOn: testClasses) { + classifier = 'tests' + from sourceSets.test.output +} + +artifacts { + testOutput testJar +} + +// The run task added by the application plugin +// is also of type JavaExec. +tasks.withType(JavaExec) { + // Assign all Java system properties from + // the command line to the JavaExec task. + systemProperties System.properties +} + + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.+' + } +} + + +idea { + module { + project.sourceSets.each { sourceSet -> + + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + println "Adding $srcDir" + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) + } +} + + +/*=================================== + * "Fat" Build targets + *===================================*/ + + +if (project.hasProperty('mainClassName') && (mainClassName != null)) { + + task mavenCapsule(type: MavenCapsule) { + description = "Generate a capsule jar that automatically downloads and caches dependencies when run." + applicationClass mainClassName + destinationDir = buildDir + } + + task fatCapsule(type: FatCapsule) { + description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" + + destinationDir = buildDir + + def fatMain = hasProperty('fatmain') ? fatmain : mainClassName + + applicationClass fatMain + + def testJar = hasProperty('test') + + if (hasProperty('fatmain')) { + appendix = "fat-${fatMain}" + } else { + appendix = "fat" + } + + if (testJar) { + from sourceSets.test.output + } + } +} + + +/*=================================== + * Repositories + *===================================*/ + +repositories { + + // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) + maven { + url nexusRepository + + if (isSnapshot) { + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } + + // Use local maven repository + mavenLocal() + + // Use 'maven central' for other dependencies. + mavenCentral() +} + +task "info" << { + println "Project: ${project.name}" +println "Description: ${project.description}" + println "--------------------------" + println "GroupId: $groupId" + println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" + println "" +} +info.description 'Print some information about project parameters' + + +/*=================================== + * Publishing + *===================================*/ + +publishing { + publications { + mavenJava(MavenPublication) { + groupId project.groupId + pom.withXml { + asNode().appendNode('description', project.description) + } + from project.components.java + + } + } + repositories { + maven { + url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } +} + + + diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageHandler.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageHandler.java new file mode 100644 index 0000000..02c751b --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageHandler.java @@ -0,0 +1,47 @@ +package meerkat.crypto.dkg.comm; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.comm.Channel; +import meerkat.protobuf.Comm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by Tzlil on 2/14/2016. + * + * an implementation of ReceiverCallback + */ +public abstract class MessageHandler implements Channel.ReceiverCallback { + final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * fixed value for broadcasting + */ + public static final int BROADCAST = 0; + + /** + * Handle a broadcast (or unicast) message. + * If the message is invalid, the handler can throw an {@link InvalidProtocolBufferException}, in which + * case the message will simply be ignored. + * @param envelope + */ + public abstract void handleMessage(Comm.BroadcastMessage envelope) throws InvalidProtocolBufferException; + + /** + * Was this broadcastMessage was received by broadcast channel + * @param broadcastMessage + * @return broadcastMessage user destination == BROADCAST + */ + public boolean isBroadcast(Comm.BroadcastMessage broadcastMessage){ + return broadcastMessage.getDestination() == BROADCAST; + } + + @Override + public void receiveMessage(Comm.BroadcastMessage envelope) { + try { + handleMessage(envelope); + } catch (InvalidProtocolBufferException e) { + logger.warn("Received invalid protocol buffer from channel", e); + } + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageUtils.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageUtils.java new file mode 100644 index 0000000..a0cb79a --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageUtils.java @@ -0,0 +1,36 @@ +package meerkat.crypto.dkg.comm; + +import meerkat.protobuf.DKG; + +/** + * Created by talm on 12/04/16. + */ +public class MessageUtils { + public static DKG.Payload createMessage(DKG.Payload.Type type) { + return DKG.Payload.newBuilder().setType(type).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.ShareMessage share) { + return DKG.Payload.newBuilder().setType(type).setShare(share).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.ShareMessage.Builder share) { + return DKG.Payload.newBuilder().setType(type).setShare(share).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.IDMessage id) { + return DKG.Payload.newBuilder().setType(type).setId(id).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.IDMessage.Builder id) { + return DKG.Payload.newBuilder().setType(type).setId(id).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.CommitmentMessage commitment) { + return DKG.Payload.newBuilder().setType(type).setCommitment(commitment).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.CommitmentMessage.Builder commitment) { + return DKG.Payload.newBuilder().setType(type).setCommitment(commitment).build(); + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Party.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Party.java new file mode 100644 index 0000000..1b2cbe8 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Party.java @@ -0,0 +1,40 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.crypto.secretsharing.shamir.Polynomial; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Created by Tzlil on 3/14/2016. + * + * contains all relevant information on specific party during + * the run of Joint Feldamn protocol + */ +public class Party { + public final int id; + public Polynomial.Point share; + public ArrayList commitments; + public boolean doneFlag; + public Protocol.ComplaintState[] complaints; + public boolean aborted; + + /** + * + * @param id party identifier - 1 <= id <= n + * @param n number of parties in current run protocol + * @param t protocol's threshold + */ + public Party(int id, int n, int t) { + this.id = id; + this.share = null; + this.doneFlag = false; + this.complaints = new Protocol.ComplaintState[n]; + Arrays.fill(this.complaints, Protocol.ComplaintState.OK); + this.commitments = new ArrayList(t + 1); + for (int i = 0; i <= t ; i++){ + commitments.add(null); + } + this.aborted = false; + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Protocol.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Protocol.java new file mode 100644 index 0000000..d81c75a --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Protocol.java @@ -0,0 +1,368 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.comm.Channel; +import meerkat.crypto.secretsharing.feldman.VerifiableSecretSharing; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import com.google.protobuf.ByteString; +import meerkat.protobuf.DKG; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.util.ByteEncoder; + +import java.math.BigInteger; +import java.util.*; + +import static meerkat.crypto.dkg.comm.MessageUtils.*; + +/** + * Created by Tzlil on 3/14/2016. + * + * an implementation of JointFeldman distributed key generation protocol. + * + * allows set of n parties to generate random key with threshold t. + */ +public class Protocol extends VerifiableSecretSharing { + public enum ComplaintState { + /** + * No complaints, no response required at this point. + */ + OK, + + /** + * Party received complaint, waiting for response from party + */ + Waiting, + + /** + * Party gave invalid answer to conplaint. + */ + Disqualified, + + /** + * Party received complaint, gave valid answer. + */ + NonDisqualified + } + + /** + * My share id. + */ + protected final int id; + + /** + * All parties participating in key generation. + * parties[id-1] has my info. + */ + private Party[] parties; + + /** + * communication object + */ + protected Channel channel; + + /** + * Encode/Decode group elements + */ + protected final ByteEncoder encoder; + + /** + * constructor + * @param q a large prime. + * @param t threshold. Any t+1 share holders can recover the secret, + * but any set of at most t share holders cannot + * @param n number of share holders + * @param zi secret, chosen from Zq + * @param random use for generate random polynomial + * @param group + * @param q a large prime dividing group order. + * @param g a generator of cyclic group of order q. + * the generated group is a subgroup of the given group. + * it must be chosen such that computing discrete logarithms is hard in this group. + * @param encoder Encode/Decode group elements (of type T) to/from byte array + */ + public Protocol(int t, int n, BigInteger zi, Random random, BigInteger q, T g + , Group group, int id, ByteEncoder encoder) { + super(t, n, zi, random, q, g,group); + this.id = id; + this.parties = new Party[n]; + for (int i = 1; i <= n ; i++){ + this.parties[i - 1] = new Party(i,n,t); + } + this.parties[id - 1].share = getShare(id); + this.encoder = encoder; + } + + /** + * setter + * @param channel + */ + public void setChannel(Channel channel){ + this.channel = channel; + } + + /** + * setter + * @param parties + */ + protected void setParties(Party[] parties){ + this.parties = parties; + } + + /** + * getter + * @return + */ + protected Party[] getParties(){ + return parties; + } + + /** + * stage1.1 according to the protocol + * Pi broadcasts Aik for k = 0,...,t. + */ + public void broadcastCommitments(){ + broadcastCommitments(commitmentsArrayList); + } + + /** + * pack commitments as messages and broadcast them + * @param commitments + */ + public void broadcastCommitments(ArrayList commitments){ + DKG.CommitmentMessage commitmentMessage; + for (int k = 0; k <= t ; k++){ + commitmentMessage = DKG.CommitmentMessage.newBuilder() + .setCommitment(ByteString.copyFrom(encoder.encode(commitments.get(k)))) + .setK(k) + .build(); + channel.broadcastMessage(createMessage(DKG.Payload.Type.COMMITMENT, commitmentMessage)); + } + } + + /** + * Send channel j her secret share (of my polynomial) + * @param j + */ + public void sendSecret(int j){ + ByteString secret = ByteString.copyFrom(getShare(j).y.toByteArray()); + channel.sendMessage(j, createMessage(DKG.Payload.Type.SHARE, + DKG.ShareMessage.newBuilder() + .setI(id) + .setJ(j) + .setShare(secret) + )); + } + + /** + * stage1.2 according to the protocol + * Pi computes the shares Sij for j = 1,...,n and sends Sij secretly to Pj. + */ + public void sendSecrets(){ + for (int j = 1; j <= n ; j++){ + if(j != id){ + sendSecret(j); + } + } + } + + /** + * + * @param i + * @return computeVerificationValue(j,parties[i - 1].commitments,group) == g ^ parties[i - 1].share mod q + */ + public boolean isValidShare(int i){ + Party party = parties[i - 1]; + synchronized (parties[i - 1]) { + return isValidShare(party.share, party.commitments, id); + } + } + + /** + * @param share + * @param commitments + * @param j + * @return computeVerificationValue(j,commitments,group) == g ^ secret.y mod q + */ + public boolean isValidShare(Polynomial.Point share, ArrayList commitments, int j){ + try{ + T v = computeVerificationValue(j,commitments,group); + return group.multiply(g,share.y).equals(v); + } + catch (NullPointerException e){ + return false; + } + } + + /** + * stage2 according to the protocol + * Pj verifies all the shares he received (using isValidShare) + * if check fails for an index i, Pj broadcasts a complaint against Pi. + */ + public void broadcastComplaints(){ + for (int i = 1; i <= n ; i++ ){ + if(i != id && !isValidShare(i)) { + broadcastComplaint(i); + } + } + } + + /** + * create a complaint message against i and broadcast it + * @param i + */ + private void broadcastComplaint(int i){ + //message = new Message(Type.Complaint, j) + DKG.IDMessage complaint = DKG.IDMessage.newBuilder() + .setId(i) + .build(); + channel.broadcastMessage(createMessage(DKG.Payload.Type.COMPLAINT, complaint)); + } + + /** + * create an answer message for j and broadcast it + * @param j + */ + public void broadcastComplaintAnswer(int j){ + channel.broadcastMessage(createMessage(DKG.Payload.Type.ANSWER, DKG.ShareMessage.newBuilder() + .setI(id) + .setJ(j) + .setShare(ByteString.copyFrom(getShare(j).y.toByteArray())))); + } + + /** + * stage3.1 according to the protocol + * if more than t players complain against a player Pi he is disqualified. + */ + public void answerAllComplainingPlayers(){ + ComplaintState[] complaints = parties[id - 1].complaints; + for (int i = 1; i <= n; i++) { + switch (complaints[i - 1]) { + case Waiting: + broadcastComplaintAnswer(i); + break; + default: + break; + } + } + + } + + /** + * stage3.2 according to the protocol + * if any of the revealed shares fails the verification test, player Pi is disqualified. + * set QUAL to be the set of non-disqualified players. + */ + public Set calcQUAL(){ + Set QUAL = new HashSet(); + boolean nonDisqualified; + int counter; + for (int i = 1; i <= n; i++) { + synchronized (parties[i - 1]) { + ComplaintState[] complaints = parties[i - 1].complaints; + nonDisqualified = true; + counter = 0; + for (int j = 1; j <= n; j++) { + switch (complaints[j - 1]) { + case OK: + break; + case NonDisqualified: + counter++; + break; + default: + nonDisqualified = false; + break; + } + if (!nonDisqualified) + break; + } + if (nonDisqualified && counter <= t) { + QUAL.add(i); + } + } + } + return QUAL; + } + + /** + * compute Y, the commitment to the final public key (includes only qualifying set) + * stage4.1 according to the protocol + * public value y is computed as y = multiplication of yi mod p for i in QUAL + */ + public T calcY(Set QUAL){ + T y = group.zero(); + for (int i : QUAL) { + synchronized (parties[i - 1]) { + y = group.add(y, parties[i - 1].commitments.get(0)); + } + } + return y; + } + + /** + * stage4.2 according to the protocol + * public verification values are computed as Ak = multiplication + * of Aik mod p for i in QUAL for k = 0,...,t + */ + public ArrayList calcCommitments(Set QUAL){ + ArrayList commitments = new ArrayList(t+1); + T value; + for (int k = 0; k <= t; k++){ + value = group.zero(); + for (int i : QUAL) { + synchronized (parties[i - 1]) { + value = group.add(value, parties[i - 1].commitments.get(k)); + } + } + commitments.add(k,value); + } + return commitments; + } + + /** + * stage4.3 according to the protocol + * Pj sets is share of the share as xj = sum of Sij mod q for i in QUAL + */ + public Polynomial.Point calcShare(Set QUAL){ + BigInteger xj = BigInteger.ZERO; + for (int i : QUAL) { + synchronized (parties[i - 1]) { + xj = xj.add(parties[i - 1].share.y); + } + } + return new Polynomial.Point(BigInteger.valueOf(id) , xj.mod(q)); + } + + /** + * decode commitment from arr + * @param arr + * @return + */ + public T decodeCommitment(byte[] arr){ + return encoder.decode(arr); + } + + /** + * getter + * @return id + */ + public int getId() { + return id; + } + + /** + * getter + * @return channel + */ + public Channel getChannel() { + return channel; + } + + + /** + * getter + * @return encoder + */ + public ByteEncoder getEncoder() { + return encoder; + } + +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/User.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/User.java new file mode 100644 index 0000000..6563dfd --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/User.java @@ -0,0 +1,588 @@ +package meerkat.crypto.dkg.feldman; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.TextFormat; +import meerkat.comm.Channel; +import meerkat.crypto.dkg.comm.MessageHandler; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import com.google.protobuf.ByteString; +import meerkat.protobuf.Comm; +import meerkat.protobuf.DKG; +import org.factcenter.qilin.primitives.Group; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; + +import static meerkat.crypto.dkg.comm.MessageUtils.createMessage; + +/** + * Created by Tzlil on 3/14/2016. + * + * implementation of joint feldman protocol user. + * + * according to the protocol, each user run feldman verifiable secret sharing + * as a dealer. + * + * by the end of run(), each party in QUAL has his own share of the generated random key. + * this key can be recover by any subset of QUAL of size at least t + 1. + */ +public class User implements Runnable { + final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * joint feldman protocol object + */ + protected final Protocol dkg; + + /** + * a generator of cyclic group of order q. + * the generated group is a subgroup of the given group. + * it must be chosen such that computing discrete logarithms is hard in this group. + */ + protected final T g; + + /** + * cyclic group contains g. + */ + protected final Group group; + + /** + * user id + */ + protected final int id; + + /** + * threshold + */ + protected final int t; + + /** + * number of shares + */ + protected final int n; + + + /** + * channel object + */ + protected final Channel channel; // + + /** + * All parties participating in key generation. + * parties[id-1] has my info. + */ + protected final Party[] parties; + + /** + * set of all non-disqualified parties + */ + protected Set QUAL; + + /** + * my own share of the generated random key. + */ + protected Polynomial.Point share; + + /** + * public verification values + */ + protected ArrayList commitments; + + /** + * public value, + * y = g ^ key + */ + protected T y; + + protected BlockingQueue receiveQueue; + + /** + * constructor + * @param dkg joint feldman protocol object + * @param channel channel object + */ + public User(Protocol dkg, Channel channel) { + this.dkg = dkg; + + this.g = dkg.getGenerator(); + this.group = dkg.getGroup(); + this.n = dkg.getN(); + this.t = dkg.getT(); + this.id = dkg.getId(); + + this.channel = channel; + dkg.setChannel(channel); + registerReceiverCallback(); + + this.parties = dkg.getParties(); + this.QUAL = null; + this.commitments = null; + this.share = null; + this.y = null; + + this.receiveQueue = new LinkedBlockingDeque<>(); + + } + + /** + * create MessageHandler and register it as ReceiverCallback + */ + protected void registerReceiverCallback() { + channel.registerReceiverCallback(new meerkat.crypto.dkg.comm.MessageHandler() { + @Override + public void handleMessage(Comm.BroadcastMessage envelope) throws InvalidProtocolBufferException { + receiveQueue.add(envelope); + } + }); + +// this.messageHandler = new MessageHandler(); +// channel.registerReceiverCallback(messageHandler); + } + + /** + * Wait for at least one message to arrive, then handle any messages currently in the queue + */ + protected void waitAndHandleReceivedMessages() { + Comm.BroadcastMessage msg = null; + while (!stop && msg == null) { + try { + msg = receiveQueue.take(); + } catch (InterruptedException e) { + // Possibly stop + } + } + while (!stop && msg != null) { + try { + handleMessage(msg); + } catch (InvalidProtocolBufferException e) { + logger.warn("Received invalid message: {}", TextFormat.printToString(msg)); + } + msg = receiveQueue.poll(); + } + } + + /** + * stage1 according to the protocol + * 1. Pi broadcasts Aik for k = 0,...,t. + * 2. Pi computes the shares Sij for j = 1,...,n and sends Sij secretly to Pj. + */ + protected void stage1() { + dkg.broadcastCommitments(); + dkg.sendSecrets(); + } + + /** + * Check if all shares and commitments have arrived from other parties + */ + protected boolean isStageOneCompleted() { + for (int i = 0 ; i < n ; i++) { + if (!parties[i].aborted) { + if (parties[i].share == null) + return false; + for (int k = 0 ; k <= t ; k++) { + if (parties[i].commitments.get(k) == null) + return false; + } + } + } + return true; + } + + protected void waitUntilStageOneCompleted() { + while (!stop && !isStageOneCompleted()) + waitAndHandleReceivedMessages(); + } + + protected boolean isStageTwoCompleted() { + for (int i = 0 ; i < n ; i++) { + if (!parties[i].aborted && !parties[i].doneFlag) + return false; + } + return true; + } + + /** + * stage2 according to the protocol + * Pj verifies all the shares he received + * if check fails for an index i, Pj broadcasts a complaint against Pi. + * Pj broadcasts done message at the end of this stage + */ + protected void stage2() { + dkg.broadcastComplaints(); + //broadcast done message after all complaints + channel.broadcastMessage(createMessage(DKG.Payload.Type.DONE)); + } + + /** + * wait until all other parties done complaining by receiving done message + */ + protected void waitUntilStageTwoCompleted(){ + while (!stop && !isStageTwoCompleted()) + waitAndHandleReceivedMessages(); + } + + + protected boolean haveReceivedAllStage3ComplaintAnswers() { + for (int i = 0; i < n; i++) { + if (parties[i].aborted) + continue; + for (int j = 0; j < n; j++) { + if (parties[i].complaints[j].equals(Protocol.ComplaintState.Waiting)) + return false; + + } + } + return true; + } + + /** + * stage3 according to the protocol + * 1. if more than t players complain against a player Pi he is disqualified. + * otherwise Pi broadcasts the share Sij for each complaining player Pj. + * 2. if any of the revealed shares fails the verification test, player Pi is disqualified. + * set QUAL to be the set of non-disqualified players. + */ + protected void stage3(){ + dkg.answerAllComplainingPlayers(); + + // wait until there is no complaint waiting for answer + while (!stop && !haveReceivedAllStage3ComplaintAnswers()) + waitAndHandleReceivedMessages(); + + this.QUAL = dkg.calcQUAL(); + } + + /** + * stage4 according to the protocol + * 1. public value y is computed as y = multiplication of yi mod p for i in QUAL + * 2. public verification values are computed as Ak = multiplication of Aik mod p for i in QUAL for k = 0,...,t + * 3. Pj sets is share of the secret as xj = sum of Sij mod q for i in QUAL + */ + protected void stage4(){ + this.y = dkg.calcY(QUAL); + this.commitments = dkg.calcCommitments(QUAL); + this.share = dkg.calcShare(QUAL); + } + + @Override + public void run() { + this.runThread = Thread.currentThread(); + // For debugging + String previousName = runThread.getName(); + runThread.setName(getClass().getName() +":" + getID()); + + try { + stage1(); + waitUntilStageOneCompleted(); + if (stop) return; + stage2(); + waitUntilStageTwoCompleted(); + if (stop) return; + stage3(); + if (stop) return; + stage4(); + } finally { + runThread.setName(previousName); + } + } + + /** + * current thread in the main loop + */ + protected Thread runThread; + + /** + * flag indicates if there was request to stop the current run of the protocol + */ + protected boolean stop = false; + + /** + * Request the current run loop to exit gracefully + */ + public void stop() { + try { + stop = true; + runThread.interrupt(); + }catch (Exception e){ + //do nothing + } + + } + + /** + * getter + * @return commitments + */ + public ArrayList getCommitments() { + return commitments; + } + + /** + * getter + * @return g + */ + public T getGenerator() { + return g; + } + + /** + * getter + * @return group + */ + public Group getGroup() { + return group; + } + + /** + * getter + * @return share + */ + public Polynomial.Point getShare() { + return share; + } + + /** + * getter + * @return id + */ + public int getID() { + return id; + } + + /** + * getter + * @return n + */ + public int getN() { + return n; + } + + /** + * getter + * @return t + */ + public int getT() { + return t; + } + + /** + * getter + * @return y + */ + public T getPublicValue() { + return y; + } + + /** + * getter + * @return QUAL + */ + public Set getQUAL() { + return QUAL; + } + + /** + * getter + * @return channel + */ + public Channel getChannel() { + return channel; + } + + /** + * commitment message is valid if: + * 1. it was received in broadcast chanel + * 2. the sender didn't sent this commitment before + */ + protected boolean isValidCommitmentMessage(int sender, boolean isBroadcast, DKG.CommitmentMessage commitmentMessage){ + int i = sender - 1; + int k = commitmentMessage.getK(); + return isBroadcast && parties[i].commitments.get(k) == null; + } + + /** + * secret message is valid if: + * 1. it was received in private chanel + * 2. the sender didn't sent secret message before + * 3. secret.i == i + * 4. secret.j == id + */ + protected boolean isValidSecretMessage(int sender, boolean isBroadcast, DKG.ShareMessage secretMessage){ + int i = secretMessage.getI(); + int j = secretMessage.getJ(); + if(sender != i || isBroadcast) + return false; + else + return parties[i - 1].share == null && j == id; + + } + + /** + * done message is valid if: + * 1. it was received in broadcast chanel + * 2. the sender didn't sent done message before + */ + protected boolean isValidDoneMessage(int sender, boolean isBroadcast){ + return isBroadcast && !parties[sender - 1].doneFlag; + } + + + /** + * complaint message is valid if: + * 1. it was received in broadcast chanel + * 2. the sender didn't complained against id before + */ + protected boolean isValidComplaintMessage(int sender, boolean isBroadcast, DKG.IDMessage complaintMessage){ + int i = sender; + int j = complaintMessage.getId(); + + assert(i > 0); + assert(j > 0); + assert(i <= parties.length); + assert(j <= parties[i-1].complaints.length); + + return isBroadcast && parties[i - 1].complaints[j - 1].equals( Protocol.ComplaintState.OK); + } + + /** + * answer message is valid if: + * 1. it was received in broadcast chanel + * 2. secret.i == i + * 3. 1 <= secret.j <= n + * 4. it is marked that j complained against i and i didn't received + */ + protected boolean isValidAnswerMessage(int sender, boolean isBroadcast, DKG.ShareMessage secretMessage){ + int i = secretMessage.getI(); + int j = secretMessage.getJ(); + if(sender != i || !isBroadcast) + return false; + else + return j >= 1 && j <= n && parties[i - 1].complaints[j - 1].equals(Protocol.ComplaintState.Waiting); + } + + + public void handleMessage(Comm.BroadcastMessage envelope) throws InvalidProtocolBufferException { + int sender = envelope.getSender(); + boolean isBroadcast = !envelope.getIsPrivate(); + DKG.Payload msg = DKG.Payload.parseFrom(envelope.getPayload()); + + logger.debug("handling Message: Dst={}, Src={}, [{}]", + envelope.getDestination(), envelope.getSender(), TextFormat.printToString(msg)); + + switch (msg.getType()) { + case COMMITMENT: + /** + * saves the commitment + */ + assert msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.COMMITMENT; + DKG.CommitmentMessage commitmentMessage = msg.getCommitment(); + if (isValidCommitmentMessage(sender, isBroadcast, commitmentMessage)) { + int i = sender - 1; + int k = commitmentMessage.getK(); + + parties[i].commitments.set(k, extractCommitment(commitmentMessage)); + } + break; + + + case SHARE: + /** + * saves the secret + */ + assert msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.SHARE; + DKG.ShareMessage secretMessage = msg.getShare(); + if(isValidSecretMessage(sender,isBroadcast,secretMessage)) { + int i = secretMessage.getI(); + Polynomial.Point secret = extractShare(id,secretMessage.getShare()); + parties[i - 1].share = secret; + } + break; + + case DONE: + + /** + * marks that the sender was finished sending all his complaints + */ + if(isValidDoneMessage(sender,isBroadcast)) { + parties[sender - 1].doneFlag = true; + } + break; + + case COMPLAINT: + /** + * marks that the sender was complained against id + */ + if (msg.getPayloadDataCase() != DKG.Payload.PayloadDataCase.ID) { + logger.error("User {} Expecting ID message, got from SRC={} msg {}", getID(), envelope.getSender(), TextFormat.printToString(msg)); + assert (msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.ID); + } + + DKG.IDMessage complaintMessage = msg.getId(); + if(isValidComplaintMessage(sender,isBroadcast,complaintMessage)){ + int i = sender; + int j = complaintMessage.getId(); + parties[j - 1].complaints[i - 1] = Protocol.ComplaintState.Waiting; + } + break; + case ANSWER: + /** + * if the secret is valid, marks the complaint as NonDisqualified + * else marks it as Disqualified + * in case that the complainer is id ( j == id ), saves the secret + */ + assert msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.SHARE; + secretMessage = msg.getShare(); + if(isValidAnswerMessage(sender,isBroadcast,secretMessage)) { + int i = secretMessage.getI(); + int j = secretMessage.getJ(); + Polynomial.Point secret = extractShare(j,secretMessage.getShare()); + if (dkg.isValidShare(secret, parties[i - 1].commitments, j)) { + parties[i - 1].complaints[j - 1] = Protocol.ComplaintState.NonDisqualified; + } else { + parties[i - 1].complaints[j - 1] = Protocol.ComplaintState.Disqualified; + } + if (j == id) { + parties[i - 1].share = secret; + } + } + break; + case ABORT: + /** + * marks that the sender was aborted + */ + parties[sender - 1].aborted = true; + break; + default: + logger.error("Bad message: SRC={}, DST={}, Payload={}", envelope.getSender(), envelope.getDestination(), TextFormat.printToString(msg)); + break; + + } + } + + /** + * extract share value from ByteString + * @param i + * @param share + * @return new Point (i,share) + */ + public Polynomial.Point extractShare(int i, ByteString share){ + BigInteger x = BigInteger.valueOf(i); + BigInteger y = new BigInteger(share.toByteArray()); + return new Polynomial.Point(x,y); + } + + /** + * + * @param commitmentMessage + * @return + */ + public T extractCommitment(DKG.CommitmentMessage commitmentMessage){ + return dkg.decodeCommitment(commitmentMessage.getCommitment().toByteArray()); + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Party.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Party.java new file mode 100644 index 0000000..529b7be --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Party.java @@ -0,0 +1,28 @@ +package meerkat.crypto.dkg.gjkr; + +import meerkat.crypto.secretsharing.shamir.Polynomial; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by Tzlil on 3/16/2016. + * + * an extension of DistributedKeyGenerationParty + * contains all relevant information on specific party during + * the run of the safe protocol + */ +public class Party extends meerkat.crypto.dkg.feldman.Party { + public Polynomial.Point shareT; + public boolean ysDoneFlag; + public ArrayList verifiableValues; + public Set recoverSharesSet; + public Party(int id, int n, int t) { + super(id, n, t); + this.shareT = null; + this.ysDoneFlag = false; + this.verifiableValues = new ArrayList(this.commitments); + this.recoverSharesSet = new HashSet(); + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Protocol.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Protocol.java new file mode 100644 index 0000000..aad773c --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Protocol.java @@ -0,0 +1,165 @@ +package meerkat.crypto.dkg.gjkr; + +import meerkat.crypto.dkg.comm.MessageUtils; +import meerkat.crypto.secretsharing.feldman.VerifiableSecretSharing; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import com.google.protobuf.ByteString; +import meerkat.protobuf.DKG; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.util.ByteEncoder; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Random; +import java.util.Set; + +/** + * Created by Tzlil on 3/16/2016. + * TODO: comments + * TODO: put Channel (ChannelImpl) in constructor + */ +public class Protocol extends meerkat.crypto.dkg.feldman.Protocol { + + private VerifiableSecretSharing maskingShares; + private final T h; + private Party[] parties; + + public Protocol(int t, int n, BigInteger zi, Random random, BigInteger q, T g + , T h, Group group, int id, ByteEncoder byteEncoder) { + super(t, n, zi, random, q, g, group, id,byteEncoder); + this.h = h; + BigInteger r = new BigInteger(q.bitLength(),random).mod(q); + this.maskingShares = new VerifiableSecretSharing(t,n,r,random,q,h,group); + this.parties = new Party[n]; + for (int i = 1; i <= n ; i++){ + this.parties[i - 1] = new Party(i,n,t); + } + this.parties[id - 1].share = getShare(id); + this.parties[id - 1].shareT = maskingShares.getShare(id); + super.setParties(parties); + } + + protected Party[] getParties(){ + return parties; + } + + protected void setParties(Party[] parties) { + super.setParties(parties); + this.parties = parties; + } + + + @Override + public void sendSecret(int j) { + Polynomial.Point secret = getShare(j); + Polynomial.Point secretT = maskingShares.getShare(j); + DKG.ShareMessage doubleSecretMessage = createShareMessage(id,j,secret,secretT); + // TODO: Change SHARE to SHARE + channel.sendMessage(j, MessageUtils.createMessage(DKG.Payload.Type.SHARE, doubleSecretMessage)); + } + + + @Override + public boolean isValidShare(int i){ + Party party = parties[i - 1]; + return isValidShare(party.share, party.shareT, party.verifiableValues, id); + } + + /** + * test if share, shareT are valid with respect to verificationValues + * @param share + * @param shareT + * @param verificationValues + * @param j + * @return computeVerificationValue(j,verificationValues,group) == (g ^ share.y) * (h ^ shareT.y) mod q + */ + public boolean isValidShare(Polynomial.Point share, Polynomial.Point shareT, ArrayList verificationValues, int j){ + try { + T v = computeVerificationValue(j, verificationValues, group); + T exp = group.add(group.multiply(g, share.y), group.multiply(h, shareT.y)); + return exp.equals(v); + } + catch (NullPointerException e){ + return false; + } + } + + /** + * create complaint message against i and broadcast it + * @param share + * @param shareT + * @param i + */ + private void broadcastComplaint(Polynomial.Point share, Polynomial.Point shareT, int i){ + DKG.ShareMessage complaint = createShareMessage(i,id,share,shareT); + channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.COMPLAINT, complaint)); + } + + /** + * stage4.3 according to the protocol + * if check fails for index i, Pj + */ + public void computeAndBroadcastComplaints(Set QUAL){ + Party party; + for (int i : QUAL) { + party = parties[i - 1]; + if (i != id) { + if (!super.isValidShare(party.share, party.commitments, id)) { + broadcastComplaint(party.share, party.shareT, i); + } + } + } + } + + /** + * compute verification values and broadcast them + * verificationValues[k] = g ^ commitments [k] * h ^ maskingShares.commitments [k] + */ + public void computeAndBroadcastVerificationValues(){ + ArrayList verificationValues = new ArrayList(t+1); + ArrayList hBaseCommitments = maskingShares.getCommitmentsArrayList(); + for (int k = 0 ; k <= t ; k++){ + verificationValues.add(k,group.add(commitmentsArrayList.get(k),hBaseCommitments.get(k))); + } + broadcastCommitments(verificationValues); + } + + /** + * pack share, shareT i,j to createShareMessage + * @param i + * @param j + * @param share + * @param shareT + * @return + */ + + private DKG.ShareMessage createShareMessage(int i, int j, Polynomial.Point share, Polynomial.Point shareT){ + DKG.ShareMessage ShareMessage = DKG.ShareMessage.newBuilder() + .setI(i) + .setJ(j) + .setShare(ByteString.copyFrom(share.y.toByteArray())) + .setShareT(ByteString.copyFrom(shareT.y.toByteArray())) + .build(); + return ShareMessage; + } + + @Override + public void broadcastComplaintAnswer(int j) { + DKG.ShareMessage answer = createShareMessage(id,j,getShare(j) + , maskingShares.getShare(j)); + channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.ANSWER, answer)); + } + + public void broadcastAnswer(Polynomial.Point secret, Polynomial.Point secretT, int i){ + DKG.ShareMessage complaint = createShareMessage(i,id,secret,secretT); + channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.ANSWER,complaint)); + } + + /** + * getter + * @return h + */ + public T getH() { + return h; + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/User.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/User.java new file mode 100644 index 0000000..5c1b418 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/User.java @@ -0,0 +1,359 @@ +package meerkat.crypto.dkg.gjkr; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; +import meerkat.comm.Channel; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.secretsharing.shamir.SecretSharing; +import meerkat.protobuf.Comm; +import meerkat.protobuf.DKG; + +import java.math.BigInteger; +import java.util.ArrayList; + +import static meerkat.crypto.dkg.comm.MessageUtils.*; + +/** + * Created by Tzlil on 3/16/2016. + *

+ * implementation of gjkr protocol user. + *

+ * this protocol extends joint Feldman protocol by splitting the protocol to commitment stage (stages 1,2,3) + * and revealing stage (stage 4). + *

+ * as in joint Feldman, each party in QUAL has his own share of the generated random key. + * this key can be recover by any subset of QUAL of size at least t + 1. + */ +public class User extends meerkat.crypto.dkg.feldman.User { + + /** + * All parties participating in key generation. + * parties[id-1] has my info. + */ + protected Party[] parties; + + /** + * gjkr secure protocol object + */ + protected final Protocol sdkg; + + boolean isStage4; + + /** + * constructor + * + * @param sdkg gjkr protocol object + * @param channel channel object + */ + public User(Protocol sdkg, Channel channel) { + super(sdkg, channel); + this.sdkg = sdkg; + this.parties = sdkg.getParties(); + } + + /** + * stage1 according to the protocol + * 1. Pi broadcasts Cik=Aik*Bik for k = 0,...,t. + * 2. Pi computes the shares Sij,Sij' for j = 1,...,n and sends Sij,Sij' secretly to Pj. + */ + @Override + protected void stage1() { + sdkg.computeAndBroadcastVerificationValues(); + sdkg.sendSecrets(); + } + + + + @Override + protected void waitUntilStageOneCompleted() { + super.waitUntilStageOneCompleted(); + // save the received commitments as verification values + ArrayList temp; + for (int i = 0; i < n; i++) { + temp = parties[i].verifiableValues; + parties[i].verifiableValues = parties[i].commitments; + parties[i].commitments = temp; + } + } + + /** + * stage2 according to the protocol + * Pj verifies all the shares,sharesT he received + * if check fails for an index i, Pj broadcasts a complaint against Pi. + * Pj broadcasts done message at the end of this stage + */ + @Override + protected void stage2() { + sdkg.broadcastComplaints(); + //broadcast done message after all complaints + channel.broadcastMessage(createMessage(DKG.Payload.Type.DONE)); + } + + /** + * Check if all non-aborting qualified parties have sent commitments. + * @return + */ + protected boolean haveAllQualPartiesCommitted() { + for (int i : QUAL) { + if (parties[i - 1].aborted) + continue; + for (int k = 0; k <= t; k++) { + if (parties[i - 1].commitments.get(k) == null) + return false; + } + } + return true; + } + + + /** + * Check if all non-aborting qualified parties sent a done message + * @return + */ + protected boolean areAllQualPartiesDone() { + for (int i : QUAL) { + if (parties[i - 1].aborted) + continue; + for (int k = 0; k <= t; k++) { + if (!parties[i - 1].ysDoneFlag) + return false; + } + } + return true; + } + + /** + * Check if at least t + 1 secrets were received foreach i in QUAL that aborted + * @return + */ + protected boolean haveReceivedEnoughSecretShares() { + for (int i : QUAL) { + if (parties[i - 1].aborted && parties[i - 1].recoverSharesSet.size() <= t) + return false; + } + return true; + } + + /** + * broadcast commitments and recover parties information if necessary + */ + private void resolveQualifyingPublicKey() { + sdkg.broadcastCommitments(); + // wait until all parties in QUAL broadcast their commitments or aborted + while (!stop && !haveAllQualPartiesCommitted()) + waitAndHandleReceivedMessages(); + + if (stop) + return; + + sdkg.computeAndBroadcastComplaints(QUAL); + + //broadcast done message after all complaints + channel.broadcastMessage(createMessage(DKG.Payload.Type.DONE)); + + // wait until all parties in QUAL done or aborted + while (!stop && !areAllQualPartiesDone()) + waitAndHandleReceivedMessages(); + + if (stop) + return; + + // broadcast i private secret foreach i in QUAL that aborted + for (int i : QUAL) { + if (parties[i - 1].aborted) { + sdkg.broadcastAnswer(parties[i - 1].share, parties[i - 1].shareT, i); + } + } + // wait until at least t + 1 secrets will received foreach i in QUAL that aborted + while (!stop && !haveReceivedEnoughSecretShares()) + waitAndHandleReceivedMessages(); + + if (stop) + return; + + Arithmetic arithmetic = new Fp(sdkg.getQ()); + // restore necessary information + for (int i = 0; i < n; i++) { + if (parties[i].recoverSharesSet.isEmpty()) { + continue; + } + Polynomial.Point[] shares = new Polynomial.Point[t + 1]; + int j = 0; + for (Polynomial.Point share : parties[i].recoverSharesSet) { + shares[j++] = share; + if (j >= shares.length) { + break; + } + } + Polynomial polynomial = SecretSharing.recoverPolynomial(shares, arithmetic); + BigInteger[] coefficients = polynomial.getCoefficients(); + for (int k = 0; k <= t; k++) { + parties[i].commitments.add(k, group.multiply(g, coefficients[k])); + } + parties[i].share = new Polynomial.Point(BigInteger.valueOf(id), polynomial); + } + } + + /** + * notifies message handler and message handler that stage 4 was started + */ + protected void setStage4() { + isStage4 = true; + } + + @Override + protected void stage4() { + setStage4(); + resolveQualifyingPublicKey(); + if (stop) return; + super.stage4(); + } + + + /** + * if !isStage4 as super, with extension to double secret message + * else answer message is valid if: + * 1. it was received in broadcast chanel + * 2. secret.j == sender + * 3. QUAL contains i and j + */ + protected boolean isValidAnswerMessage(int sender, boolean isBroadcast, DKG.ShareMessage doubleSecretMessage) { + if (!isStage4) { + return super.isValidAnswerMessage(sender, isBroadcast, doubleSecretMessage); + } else { + int i = doubleSecretMessage.getI(); + int j = doubleSecretMessage.getJ(); + return isBroadcast && j == sender && parties[i - 1].aborted && !parties[j - 1].aborted + && QUAL.contains(i) && QUAL.contains(j); + } + } + + + /** + * as in super with respect to protocol stage + */ + @Override + protected boolean isValidDoneMessage(int sender, boolean isBroadcast) { + if (!isStage4) { + return super.isValidDoneMessage(sender, isBroadcast); + } else { + return isBroadcast && !parties[sender - 1].ysDoneFlag; + } + } + + /** + * use only in stage4 + * complaint message is valid if: + * 1. it was received in broadcast chanel + * 2. secret.j == sender + * 3. QUAL contains i and j + */ + protected boolean isValidComplaintMessage(int sender, boolean isBroadcast, + DKG.ShareMessage complaintMessage) { + int i = complaintMessage.getI(); + int j = complaintMessage.getJ(); + return isBroadcast && j == sender && QUAL.contains(i) && QUAL.contains(j); + } + + + @Override + public void handleMessage(Comm.BroadcastMessage envelope) throws InvalidProtocolBufferException { + int sender = envelope.getSender(); + boolean isBroadcast = !envelope.getIsPrivate(); + DKG.Payload msg = DKG.Payload.parseFrom(envelope.getPayload()); + switch (msg.getType()) { + case SHARE: + /** + * as in super, with extension to double secret message + */ + DKG.ShareMessage doubleSecretMessage = msg.getShare(); + if (isValidSecretMessage(sender, isBroadcast, doubleSecretMessage)) { + int i = doubleSecretMessage.getI(); + synchronized (parties[i - 1]) { + parties[i - 1].share = extractShare(id, doubleSecretMessage.getShare()); + parties[i - 1].shareT = extractShare(id, doubleSecretMessage.getShareT()); + parties[i - 1].notify(); + } + } + break; + case ANSWER: + /** + * if !isStage4 as super, with extension to double secret message + * else saves secret + */ + assert msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.SHARE; + doubleSecretMessage = msg.getShare(); + if (isValidAnswerMessage(sender, isBroadcast, doubleSecretMessage)) { + int i = doubleSecretMessage.getI(); + int j = doubleSecretMessage.getJ(); + Polynomial.Point secret = extractShare(j, doubleSecretMessage.getShare()); + Polynomial.Point secretT = extractShare(j, doubleSecretMessage.getShareT()); + synchronized (parties[i - 1]) { + if (!isStage4) { + if (sdkg.isValidShare(secret, secretT, parties[j - 1].verifiableValues, i)) { + parties[i - 1].complaints[j - 1] = meerkat.crypto.dkg.feldman.Protocol.ComplaintState.NonDisqualified; + + } else { + parties[i - 1].complaints[j - 1] = meerkat.crypto.dkg.feldman.Protocol.ComplaintState.Disqualified; + } + if (j == id) { + parties[i - 1].share = secret; + parties[i - 1].shareT = secretT; + } + } else if (sdkg.isValidShare(secret, secretT, parties[i - 1].verifiableValues, j)) { + parties[i - 1].recoverSharesSet.add(secret); + } + parties[i - 1].notify(); + } + } + break; + case DONE: + /** + * as in super with respect to protocol state + */ + if (!isStage4) + super.handleMessage(envelope); + else { + if (isValidDoneMessage(sender, isBroadcast)) { + synchronized (parties[sender - 1]) { + parties[sender - 1].ysDoneFlag = true; + parties[sender - 1].notify(); + } + } + } + break; + case COMPLAINT: + /** + * if !isStage4 as in super + * else if secret,secretT are valid with respect to verifiableValues but + * secret is not valid with respect to commitments then + * marks i as aborted + */ + if (!isStage4) { + super.handleMessage(envelope); + } else { + assert (msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.SHARE); + DKG.ShareMessage ysComplaintMessage = msg.getShare(); + if (isValidComplaintMessage(sender, isBroadcast, ysComplaintMessage)) { + int i = ysComplaintMessage.getI(); + int j = ysComplaintMessage.getJ(); + Polynomial.Point secret = extractShare(i, ysComplaintMessage.getShare()); + Polynomial.Point secretT = extractShare(i, ysComplaintMessage.getShareT()); + if (sdkg.isValidShare(secret, secretT, parties[i - 1].verifiableValues, j) + && !dkg.isValidShare(secret, parties[i - 1].commitments, j)) { + synchronized (parties[i - 1]) { + parties[i - 1].aborted = true; + parties[i - 1].notify(); + } + } + } + } + break; + default: + super.handleMessage(envelope); + break; + } + } + +} \ No newline at end of file diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharing.java b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharing.java new file mode 100644 index 0000000..0c91431 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharing.java @@ -0,0 +1,117 @@ +package meerkat.crypto.secretsharing.feldman; + +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.secretsharing.shamir.SecretSharing; +import org.factcenter.qilin.primitives.Group; + +import java.util.ArrayList; +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + * + * an implementation of Feldman's verifiable secret sharing scheme. + * + * allows trusted dealer to share a key x among n parties. + * + */ +public class VerifiableSecretSharing extends SecretSharing { + /** + * cyclic group contains g. + */ + protected final Group group; + /** + * a generator of cyclic group of order q. + * the generated group is a subgroup of the given group. + * it must be chosen such that computing discrete logarithms is hard in this group. + */ + protected final T g; + /** + * commitments to polynomial coefficients. + * commitments[k] = g ^ coefficients[k] (group operation) + */ + protected final ArrayList commitmentsArrayList; + + /** + * constructor + * @param q a large prime. + * @param t threshold. Any t+1 share holders can recover the secret, + * but any set of at most t share holders cannot + * @param n number of share holders + * @param zi secret, chosen from Zq + * @param random use for generate random polynomial + * @param group + * @param q a large prime dividing group order. + * @param g a generator of cyclic group of order q. + * the generated group is a subgroup of the given group. + * it must be chosen such that computing discrete logarithms is hard in this group. + */ + public VerifiableSecretSharing(int t, int n, BigInteger zi, Random random, BigInteger q, T g + , Group group) { + super(t, n, zi, random,q); + this.g = g; + this.group = group; + assert (this.group.contains(g)); + this.commitmentsArrayList = generateCommitments(); + } + + /** + * commitments[i] = g ^ polynomial.coefficients[i] + * @return commitments + */ + private ArrayList generateCommitments() { + + Polynomial polynomial = getPolynomial(); + BigInteger[] coefficients = polynomial.getCoefficients(); + ArrayList commitments = new ArrayList(t + 1); + for (int i = 0 ; i <= t;i++){ + commitments.add(i,group.multiply(g,coefficients[i])); + } + return commitments; + } + + /** + * Compute verification value (g^{share value}) using coefficient commitments sent by dealer and my share id. + * @param j my share holder id + * @param commitments commitments to polynomial coefficients of share (received from dealer) + * @param group + * + * @return product of Aik ^ (j ^ k) == g ^ polynomial(i) + */ + public static T computeVerificationValue(int j, ArrayList commitments, Group group) { + T v = group.zero(); + BigInteger power = BigInteger.ONE; + BigInteger J = BigInteger.valueOf(j); + for (int k = 0 ; k < commitments.size() ; k ++){ + v = group.add(v,group.multiply(commitments.get(k),power)); + power = power.multiply(J); + } + return v; + } + + /** + * getter + * @return generator of group + */ + public T getGenerator() { + return g; + } + + /** + * getter + * @return group + */ + public Group getGroup(){ + return group; + } + + /** + * getter + * @return commitmentsArrayList + */ + public ArrayList getCommitmentsArrayList() { + return commitmentsArrayList; + } + +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/LagrangePolynomial.java b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/LagrangePolynomial.java new file mode 100644 index 0000000..e12d65f --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/LagrangePolynomial.java @@ -0,0 +1,66 @@ +package meerkat.crypto.secretsharing.shamir; + +import meerkat.crypto.utils.Arithmetic; + +import java.math.BigInteger; + +/** + * Created by Tzlil on 1/28/2016. + * + * container of lagrange polynomial + * + * Constructor is private (use {@link #lagrangePolynomials(Polynomial.Point[], Arithmetic)} to construct) + * + * l = (evaluate/divisor)* polynomial + * + * Note : image and divisor stored separately for avoiding lose of information by division + */ +class LagrangePolynomial{ + public final Polynomial polynomial; + public final BigInteger image; + public final BigInteger divisor; + + /** + * inner constructor, stores all given parameters + * @param polynomial + * @param image + * @param divisor + */ + private LagrangePolynomial(Polynomial polynomial, BigInteger image, BigInteger divisor) { + this.polynomial = polynomial; + this.image = image; + this.divisor = divisor; + } + + /** + * static method + * @param points array points s.t there are no couple of points that shares the same x value + * + * @return the lagrange polynomials that mach to given points. + * in case there exists i != j s.t points[i].x == points[j].x returns null. + */ + public static LagrangePolynomial[] lagrangePolynomials(Polynomial.Point[] points,Arithmetic arithmetic) { + Polynomial one = new Polynomial(new BigInteger[]{BigInteger.ONE},arithmetic); + LagrangePolynomial[] lagrangePolynomials = new LagrangePolynomial[points.length]; + Polynomial[] factors = new Polynomial[points.length]; + for (int i = 0 ; i < factors.length ; i++){ + factors[i] = new Polynomial(new BigInteger[]{points[i].x.negate(),BigInteger.ONE},arithmetic); // X - Xi + } + Polynomial product; + BigInteger divisor; + for(int i = 0; i < points.length; i ++) { + product = one; + divisor = BigInteger.ONE; + for (int j = 0; j < points.length; j++) { + if (i != j) { + divisor = arithmetic.mul(divisor,arithmetic.sub(points[i].x,points[j].x)); + product = product.mul(factors[j]); + } + } + if(divisor.equals(BigInteger.ZERO)) + return null; + lagrangePolynomials[i] = new LagrangePolynomial(product,points[i].y,divisor); + } + return lagrangePolynomials; + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/Polynomial.java b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/Polynomial.java new file mode 100644 index 0000000..58f38fb --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/Polynomial.java @@ -0,0 +1,208 @@ +package meerkat.crypto.secretsharing.shamir; + +import meerkat.crypto.utils.Arithmetic; + +import java.math.BigInteger; +import java.util.Arrays; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class Polynomial implements Comparable { + private final int degree; + private final BigInteger[] coefficients; + private final Arithmetic arithmetic; + + /** + * constructor + * @param coefficients + * @param arithmetic + * degree set as max index such that coefficients[degree] not equals zero + */ + public Polynomial(BigInteger[] coefficients,Arithmetic arithmetic) { + int d = coefficients.length - 1; + while (d > 0 && coefficients[d].equals(BigInteger.ZERO)){ + d--; + } + this.degree = d; + this.coefficients = coefficients; + this.arithmetic = arithmetic; + } + + /** + * Compare to another polynomial (order by degree, then coefficients). + */ + @Override + public int compareTo(Polynomial other) { + if (this.degree != other.degree) + return this.degree - other.degree; + int compare; + for (int i = degree; i >= degree ; i--){ + compare = this.coefficients[i].compareTo(other.coefficients[i]); + if (compare != 0){ + return compare; + } + } + return 0; + } + + /** + * @param x + * @return sum of coefficients[i] * (x ^ i) + */ + public BigInteger evaluate(BigInteger x){ + BigInteger result = BigInteger.ZERO; + BigInteger power = BigInteger.ONE; + for(int i = 0 ; i <= degree ; i++){ + result = arithmetic.add(result,arithmetic.mul(coefficients[i],power)); + power = power.multiply(x); + } + return result; + } + + /** + * @param points + * @return polynomial of minimal degree which goes through all points. + * If there exists i != j s.t points[i].x == points[j].x, method returns null. + */ + public static Polynomial interpolation(Point[] points, Arithmetic arithmetic) { + LagrangePolynomial[] l = LagrangePolynomial.lagrangePolynomials(points,arithmetic); + if (l == null){ + return null; + } + // product = product of l[i].divisor + BigInteger product = BigInteger.ONE; + for (int i = 0; i < l.length;i++){ + product = arithmetic.mul(product,l[i].divisor); + } + + // factor[i] = product divided by l[i].divisor = product of l[j].divisor s.t j!=i + BigInteger[] factors = new BigInteger[l.length]; + for (int i = 0; i < l.length;i++){ + factors[i] = arithmetic.div(product,l[i].divisor); + } + int degree = l[0].polynomial.degree; + + // coefficients[j] = (sum of l[i].evaluate * factor[i] * l[i].coefficients[j] s.t i!=j) divide by product = + // = sum of l[i].evaluate * l[i].coefficients[j] / l[i].divisor s.t i!=j + BigInteger[] coefficients = new BigInteger[degree + 1]; + for (int j = 0; j < coefficients.length;j++){ + coefficients[j] = BigInteger.ZERO; + for (int i = 0; i < l.length; i++){ + BigInteger current = arithmetic.mul(l[i].image,factors[i]); + current = arithmetic.mul(current,l[i].polynomial.coefficients[j]); + coefficients[j] = arithmetic.add(coefficients[j],current); + } + coefficients[j] = arithmetic.div(coefficients[j],product); + } + return new Polynomial(coefficients,arithmetic); + } + + /** + * @param other + * @return new Polynomial of degree max(this degree,other degree) s.t for all x + * new.evaluate(x) = this.evaluate(x) + other.evaluate(x) + */ + public Polynomial add(Polynomial other){ + Polynomial bigger,smaller; + if(this.degree < other.degree){ + bigger = other; + smaller = this; + }else{ + bigger = this; + smaller = other; + } + BigInteger[] coefficients = bigger.getCoefficients(); + + for (int i = 0; i <= smaller.degree ; i++){ + coefficients[i] = arithmetic.add(smaller.coefficients[i],bigger.coefficients[i]); + } + return new Polynomial(coefficients,other.arithmetic); + } + + /** + * @param constant + * @return new Polynomial of degree this.degree s.t for all x + * new.evaluate(x) = constant * this.evaluate(x) + */ + public Polynomial mul(BigInteger constant){ + + BigInteger[] coefficients = this.getCoefficients(); + + for (int i = 0; i <= this.degree ; i++){ + coefficients[i] = arithmetic.mul(constant,coefficients[i]); + } + return new Polynomial(coefficients,arithmetic); + } + + /** + * @param other + * @return new Polynomial of degree this degree + other degree + 1 s.t for all x + * new.evaluate(x) = this.evaluate(x) * other.evaluate(x) + */ + public Polynomial mul(Polynomial other){ + + BigInteger[] coefficients = new BigInteger[this.degree + other.degree + 1]; + Arrays.fill(coefficients,BigInteger.ZERO); + + for (int i = 0; i <= this.degree ; i++){ + for (int j = 0; j <= other.degree; j++){ + coefficients[i+j] = arithmetic.add(coefficients[i+j],arithmetic.mul(this.coefficients[i],other.coefficients[j])); + } + } + return new Polynomial(coefficients,arithmetic); + } + + + /** getter + * @return copy of coefficients + */ + public BigInteger[] getCoefficients() { + return Arrays.copyOf(coefficients,coefficients.length); + } + + /** getter + * @return degree + */ + public int getDegree() { + return degree; + } + + /** + * inner class + * container for (x,y) x from range and y from evaluate of polynomial + */ + public static class Point implements java.io.Serializable { + public final BigInteger x; + public final BigInteger y; + + /** + * constructor + * @param x + * @param polynomial y = polynomial.evaluate(x) + */ + public Point(BigInteger x, Polynomial polynomial) { + this.x = x; + this.y = polynomial.evaluate(x); + } + + /** + * constructor + * @param x + * @param y + */ + public Point(BigInteger x,BigInteger y) { + this.x = x; + this.y = y; + } + + @Override + public boolean equals(Object obj) { + if(!super.equals(obj)) + return false; + Point other = (Point)obj; + return this.x.equals(other.x) && this.y.equals(other.y); + } + } + +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/SecretSharing.java b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/SecretSharing.java new file mode 100644 index 0000000..fb86993 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/SecretSharing.java @@ -0,0 +1,123 @@ +package meerkat.crypto.secretsharing.shamir; + +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + * an implementation of Shamire's secret sharing scheme + */ +public class SecretSharing{ + /** + * threshold + */ + protected final int t; + /** + * number of shares + */ + protected final int n; + /** + * a large prime + */ + protected final BigInteger q; + /** + * random polynomial of degree s.t polynomial.evaluate(0) = secret + */ + protected final Polynomial polynomial; + + /** + * constructor + * @param q a large prime. + * @param t threshold. Any t+1 share holders can recover the secret, + * but any set of at most t share holders cannot + * @param n number of share holders + * @param zi secret, chosen from Zq + * @param random use for generate random polynomial + */ + public SecretSharing(int t, int n, BigInteger zi, Random random, BigInteger q) { + this.q = q; + this.t = t; + this.n = n; + this.polynomial = generateRandomPolynomial(zi,random); + } + + /** + * @param x + * @param random + * @return new Polynomial polynomial of degree t ,such that + * 1. polynomial(0) = x + * 2. polynomial coefficients randomly chosen from Zq (except of coefficients[0] = x) + */ + private Polynomial generateRandomPolynomial(BigInteger x, Random random) { + BigInteger[] coefficients = new BigInteger[t + 1]; + coefficients[0] = x.mod(q); + int bits = q.bitLength(); + for (int i = 1 ; i <= t; i++ ){ + coefficients[i] = new BigInteger(bits,random).mod(q); + } + return new Polynomial(coefficients,new Fp(q)); + } + + /** + * @param i in range of [1,...n] + * + * @return polynomial.evaluate(i) + */ + public Polynomial.Point getShare(int i){ + assert (i > 0 && i <= n); + return new Polynomial.Point(BigInteger.valueOf(i), polynomial); + } + + /** + * @param shares - subset of the original shares + * + * @return evaluate of interpolation(shares) at x = 0 + */ + public static BigInteger recoverSecret(Polynomial.Point[] shares, Arithmetic arithmetic) { + return recoverPolynomial(shares,arithmetic).evaluate(BigInteger.ZERO); + } + /** + * @param shares - subset of the original shares + * + * @return interpolation(shares) + */ + public static Polynomial recoverPolynomial(Polynomial.Point[] shares, Arithmetic arithmetic) { + return Polynomial.interpolation(shares,arithmetic); + } + + /** + * getter + * @return threshold + */ + public int getT() { + return t; + } + + /** + * getter + * @return number of share holders + */ + public int getN() { + return n; + } + + /** + * getter + * @return the prime was given in the constructor + */ + public BigInteger getQ() { + return q; + } + + + /** + * getter + * @return the polynomial was generated in constructor + */ + public Polynomial getPolynomial() { + return polynomial; + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/utils/Arithmetic.java b/distributed-key-generation/src/main/java/meerkat/crypto/utils/Arithmetic.java new file mode 100644 index 0000000..6221a5c --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/utils/Arithmetic.java @@ -0,0 +1,38 @@ +package meerkat.crypto.utils; + +/** + * Created by Tzlil on 3/17/2016. + * defines the properties of the traditional operations : add,sub,mul,div + * between two objects of type T + */ +public interface Arithmetic { + /** + * addition + * @param a + * @param b + * @return a + b + */ + T add(T a, T b); + /** + * subtraction + * @param a + * @param b + * @return a - b + */ + T sub(T a, T b); + /** + * multiplication + * @param a + * @param b + * @return a * b + */ + T mul(T a, T b); + /** + * division + * @param a + * @param b + * @return a / b + */ + T div(T a, T b); + +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/utils/concrete/Fp.java b/distributed-key-generation/src/main/java/meerkat/crypto/utils/concrete/Fp.java new file mode 100644 index 0000000..52fb324 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/utils/concrete/Fp.java @@ -0,0 +1,44 @@ +package meerkat.crypto.utils.concrete; + +import meerkat.crypto.utils.Arithmetic; +import org.factcenter.qilin.primitives.concrete.Zpstar; + +import java.math.BigInteger; + +/** + * Created by Tzlil on 3/17/2016. + * an implementation of Arithmetic over prime fields: integers modulo p + */ +public class Fp implements Arithmetic { + public final BigInteger p; + private final Zpstar zp; + + /** + * constructor + * @param p prime + */ + public Fp(BigInteger p) { + this.p = p; + this.zp = new Zpstar(p); + } + + @Override + public BigInteger add(BigInteger a, BigInteger b){ + return a.add(b).mod(p); + } + + @Override + public BigInteger sub(BigInteger a, BigInteger b){ + return a.add(p).subtract(b).mod(p); + } + + @Override + public BigInteger mul(BigInteger a, BigInteger b){ + return zp.add(a,b); + } + + @Override + public BigInteger div(BigInteger a, BigInteger b){ + return mul(a,zp.negate(b)); + } +} diff --git a/distributed-key-generation/src/main/proto/meerkat/DKG.proto b/distributed-key-generation/src/main/proto/meerkat/DKG.proto new file mode 100644 index 0000000..7d43a67 --- /dev/null +++ b/distributed-key-generation/src/main/proto/meerkat/DKG.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +message Payload { + enum Type { + SHARE = 0; + COMMITMENT = 1; + COMPLAINT = 2; + DONE = 3; + ANSWER = 4; + YCOMMITMENT = 5; + YCOMPLAINT = 6; + YANSWER = 7; + ABORT = 8; + } + + + // Type of message in protocol + Type type = 1; + + oneof payload_data { + IDMessage id = 5; + ShareMessage share = 6; + CommitmentMessage commitment = 7; + } +} + +message IDMessage { + int32 id = 1; +} + +message ShareMessage { + int32 i = 1; + int32 j = 2; + bytes share = 3; + // For double shares (used in GJKR protocol) + bytes share_t = 4; +} + +message CommitmentMessage { + int32 k = 1; + bytes commitment = 2; +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGMaliciousUser.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGMaliciousUser.java new file mode 100644 index 0000000..78b70d4 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGMaliciousUser.java @@ -0,0 +1,73 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.comm.Channel; + +import java.math.BigInteger; +import java.util.*; + +/** + * Created by Tzlil on 3/21/2016. + */ +public class DKGMaliciousUser extends User { + + private final Protocol maliciousDkg; + private final Set falls; + public DKGMaliciousUser(Protocol dkg, Protocol maliciousDKG, Channel channel, Set falls) { + super(dkg, channel); + this.falls = falls; + this.maliciousDkg = maliciousDKG; + maliciousDKG.setParties(parties); + } + + public static Set selectFallsRandomly(Set ids, Random random){ + Set falls = new HashSet(); + ArrayList idsList = new ArrayList(); + for (int id : ids){ + idsList.add(id); + } + int fallsSize = random.nextInt(idsList.size()) + 1;// 1 - (n-1) + while (falls.size() < fallsSize){ + falls.add(idsList.remove(random.nextInt(idsList.size()))); + } + return falls; + } + + public static Protocol generateMaliciousDKG(Protocol dkg,Channel channel,Random random){ + BigInteger q = dkg.getQ(); + BigInteger zi = new BigInteger(q.bitLength(), random).mod(q); + Protocol malicious = new Protocol(dkg.getT(),dkg.getN(),zi,random,dkg.getQ() + ,dkg.getGenerator(),dkg.getGroup(),dkg.getId(),dkg.getEncoder()); + malicious.setChannel(channel); + return malicious; + } + + @Override + public void stage1() { + dkg.broadcastCommitments(); + sendSecrets(); //insteadof crypto.sendSecrets(channel); + } + + @Override + public void stage3() { + maliciousDkg.answerAllComplainingPlayers(); + } + + @Override + public void stage4(){ + // do nothing + } + + private void sendSecrets(){ + for (int j = 1; j <= n ; j++){ + if(j != id){ + if(falls.contains(j)){ + maliciousDkg.sendSecret(j); + }else { + dkg.sendSecret(j); + } + } + } + } + + +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGTest.java new file mode 100644 index 0000000..f522a42 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGTest.java @@ -0,0 +1,170 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.comm.ChannelImpl; +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; +import meerkat.comm.Channel; +import meerkat.crypto.secretsharing.feldman.VerifiableSecretSharing; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.secretsharing.shamir.SecretSharing; +import meerkat.crypto.utils.BigIntegerByteEncoder; +import meerkat.crypto.utils.GenerateRandomPrime; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.primitives.concrete.Zpstar; +import org.factcenter.qilin.util.ByteEncoder; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +/** + * Created by Tzlil on 3/21/2016. + */ +public class DKGTest { + + int tests = 10; + BigInteger p = GenerateRandomPrime.SafePrime100Bits; + BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); + Group group = new Zpstar(p); + Arithmetic arithmetic = new Fp(q); + int t = 9; + int n = 20; + + public void oneTest(Testable testable) throws Exception { + for (int i = 0; i < testable.threads.length ; i++){ + testable.threads[i].start(); + } + for (int i = 0; i < testable.threads.length ; i++){ + testable.threads[i].join(); + } + + // got the right public value + BigInteger publicValue = group.multiply(testable.g,testable.secret); + for (int i: testable.valids){ + assert (testable.dkgs[i - 1].getPublicValue().equals(publicValue)); + } + + // assert valid verification values + BigInteger expected,verification; + for (int i: testable.valids){ + expected = group.multiply(testable.g, testable.dkgs[i - 1].getShare().y); + verification = VerifiableSecretSharing.computeVerificationValue(i, testable.dkgs[i - 1].getCommitments(), group); + assert (expected.equals(verification)); + } + + + // restore the secret from shares + ArrayList sharesList = new ArrayList(); + + for (int i: testable.valids){ + sharesList.add(testable.dkgs[i - 1].getShare()); + } + Polynomial.Point[] shares = new Polynomial.Point[sharesList.size()]; + for (int i = 0; i < shares.length; i ++){ + shares[i] = sharesList.get(i); + } + + BigInteger calculatedSecret = SecretSharing.recoverSecret(shares,arithmetic); + assert (calculatedSecret.equals(testable.secret)); + } + + + @Test + public void test() throws Exception { + Testable testable; + for (int i = 0; i < tests; i++){ + testable = new Testable(new Random()); + oneTest(testable); + } + } + + class Testable{ + Set valids; + Set QUAL; + Set aborted; + Set malicious; + User[] dkgs; + Thread[] threads; + BigInteger g; + BigInteger secret; + + public Testable(Random random) { + this.dkgs = new User[n]; + this.valids = new HashSet(); + this.QUAL = new HashSet(); + this.aborted = new HashSet(); + this.malicious = new HashSet(); + this.threads = new Thread[n]; + this.g = sampleGenerator(random); + ArrayList ids = new ArrayList(); + for (int id = 1; id<= n ; id++){ + ids.add(id); + } + int id; + BigInteger s; + Protocol dkg; + this.secret = BigInteger.ZERO; + ChannelImpl channels = new ChannelImpl(); + ByteEncoder byteEncoder = new BigIntegerByteEncoder(); + while (!ids.isEmpty()) { + id = ids.remove(random.nextInt(ids.size())); + Channel channel = channels.getChannel(id); + s = randomIntModQ(random); + dkg = new meerkat.crypto.dkg.feldman.Protocol(t, n, s, random, q, g, group, id,byteEncoder); + dkgs[id - 1] = randomDKGUser(id,channel,dkg,random); + threads[id - 1] = new Thread(dkgs[id - 1]); + if(QUAL.contains(id)){ + this.secret = this.secret.add(s).mod(q); + } + } + + } + + public User randomDKGUser(int id, Channel channel, Protocol dkg, Random random){ + if (QUAL.size() <= t) { + valids.add(id); + QUAL.add(id); + return new User(dkg,channel); + }else{ + int type = random.nextInt(3); + switch (type){ + case 0:// regular + valids.add(id); + QUAL.add(id); + return new User(dkg,channel); + case 1:// abort + int abortStage = random.nextInt(2) + 1; // 1 or 2 + aborted.add(id); + if (abortStage == 2){ + QUAL.add(id); + } + return new DKGUserImplAbort(dkg,channel,abortStage); + case 2:// malicious + malicious.add(id); + Set falls = DKGMaliciousUser.selectFallsRandomly(valids,random); + Protocol maliciousDKG = DKGMaliciousUser.generateMaliciousDKG(dkg,channel,random); + return new DKGMaliciousUser(dkg,maliciousDKG,channel,falls); + default: + return null; + } + } + } + + public BigInteger sampleGenerator(Random random){ + BigInteger ZERO = group.zero(); + BigInteger g; + do { + g = group.sample(random); + } while (!g.equals(ZERO) && !group.multiply(g, q).equals(ZERO)); + return g; + } + + public BigInteger randomIntModQ(Random random){ + return new BigInteger(q.bitLength(), random).mod(q); + } + + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGUserImplAbort.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGUserImplAbort.java new file mode 100644 index 0000000..528ea9f --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGUserImplAbort.java @@ -0,0 +1,65 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.comm.Channel; +import meerkat.protobuf.DKG; + +import static meerkat.crypto.dkg.comm.MessageUtils.createMessage; + +/** + * Created by Tzlil on 3/14/2016. + */ +public class DKGUserImplAbort extends User { + + final int abortStage; + int stage; + public DKGUserImplAbort(Protocol dkg, Channel channel, int abortStage) { + super(dkg, channel); + this.abortStage = abortStage;// 1 - 2 + this.stage = 1; + } + + + private void sendAbort(){ + channel.broadcastMessage(createMessage(DKG.Payload.Type.ABORT)); + } + + @Override + protected void stage1() { + if(stage < abortStage) + super.stage1(); + else if(stage == abortStage){ + sendAbort(); + } + stage++; + } + + @Override + protected void stage2() { + if(stage < abortStage) + super.stage2(); + else if(stage == abortStage){ + sendAbort(); + } + stage++; + } + + @Override + protected void stage3() { + if(stage < abortStage) + super.stage3(); + else if(stage == abortStage){ + sendAbort(); + } + stage++; + } + + @Override + protected void stage4() { + if(stage < abortStage) + super.stage4(); + else if(stage == abortStage){ + sendAbort(); + } + stage++; + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGMaliciousUserImpl.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGMaliciousUserImpl.java new file mode 100644 index 0000000..baf1a81 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGMaliciousUserImpl.java @@ -0,0 +1,61 @@ +package meerkat.crypto.dkg.gjkr; + +import meerkat.comm.Channel; + +import java.math.BigInteger; +import java.util.Random; +import java.util.Set; + +/** + * Created by Tzlil on 3/29/2016. + */ +public class SDKGMaliciousUserImpl extends User { + + private final Protocol maliciousSDKG; + private final Set falls; + public SDKGMaliciousUserImpl(Protocol sdkg, Protocol maliciousSDKG + , Channel channel, Set falls) { + super(sdkg, channel); + this.falls = falls; + this.maliciousSDKG = maliciousSDKG; + maliciousSDKG.setParties(parties); + } + + public static Protocol generateMaliciousSDKG(Protocol sdkg,Channel channel,Random random){ + BigInteger q = sdkg.getQ(); + BigInteger zi = new BigInteger(q.bitLength(), random).mod(q); + Protocol malicious = new Protocol(sdkg.getT(),sdkg.getN(),zi,random,sdkg.getQ() + ,sdkg.getGenerator(),sdkg.getH(),sdkg.getGroup(),sdkg.getId(),sdkg.getEncoder()); + malicious.setChannel(channel); + return malicious; + } + + @Override + public void stage1() { + sdkg.computeAndBroadcastVerificationValues(); + sendSecrets(); //insteadof crypto.sendSecrets(channel); + } + + @Override + public void stage3() { + maliciousSDKG.answerAllComplainingPlayers(); + } + + @Override + public void stage4(){ + //do nothing + } + + private void sendSecrets(){ + for (int j = 1; j <= n ; j++){ + if(j != id){ + if(falls.contains(j)){ + maliciousSDKG.sendSecret(j); + }else { + sdkg.sendSecret(j); + } + } + } + } + +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGTest.java new file mode 100644 index 0000000..e4550f0 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGTest.java @@ -0,0 +1,206 @@ +package meerkat.crypto.dkg.gjkr; + +import meerkat.comm.ChannelImpl; +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; +import meerkat.comm.Channel; +import meerkat.crypto.secretsharing.feldman.VerifiableSecretSharing; +import meerkat.crypto.dkg.feldman.DKGMaliciousUser; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.secretsharing.shamir.SecretSharing; +import meerkat.crypto.utils.BigIntegerByteEncoder; +import meerkat.crypto.utils.GenerateRandomPrime; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.primitives.concrete.Zpstar; +import org.factcenter.qilin.util.ByteEncoder; +import org.junit.Assert; +import org.junit.Test; +import org.junit.internal.runners.statements.Fail; + +import static org.junit.Assert.*; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * Created by Tzlil on 3/29/2016. + * TODO: Separate into multiple tests, + * TODO: Make tests deterministic (using constant seed for random generator) + */ +public class SDKGTest { + + private ExecutorService executorService = Executors.newCachedThreadPool(); + + final static int NUM_TESTS = 10; + BigInteger p = GenerateRandomPrime.SafePrime100Bits; + BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); + Group group = new Zpstar(p); + Arithmetic arithmetic = new Fp(q); + int t = 1; + int n = 20; + Random rand = new Random(1); + + public void oneTest(Testable testable) throws Exception { + for (int i = 0; i < testable.sdkgs.length ; i++){ + testable.futures[i] = executorService.submit(testable.sdkgs[i]); + } + for (int i = 0; i < testable.futures.length ; i++){ + testable.futures[i].get(); + } + + // got the right public value + BigInteger publicValue = group.multiply(testable.g,testable.secret); + for (int i: testable.valids){ + assert (testable.sdkgs[i - 1].getPublicValue().equals(publicValue)); + } + + // assert valid verification values + BigInteger expected,verification; + for (int i: testable.valids){ + expected = group.multiply(testable.g, testable.sdkgs[i - 1].getShare().y); + verification = VerifiableSecretSharing.computeVerificationValue(i, testable.sdkgs[i - 1].getCommitments(), group); + assert (expected.equals(verification)); + } + + + // restore the secret from shares + ArrayList sharesList = new ArrayList(); + + for (int i: testable.valids){ + sharesList.add(testable.sdkgs[i - 1].getShare()); + } + Polynomial.Point[] shares = new Polynomial.Point[sharesList.size()]; + for (int i = 0; i < shares.length; i ++){ + shares[i] = sharesList.get(i); + } + + BigInteger calculatedSecret = SecretSharing.recoverSecret(shares,arithmetic); + assert (calculatedSecret.equals(testable.secret)); + } + + + @Test + public void test() throws Exception { + Testable testable; + for (int i = 0; i < NUM_TESTS; i++) { + testable = new Testable(n, t, group, q, rand); + oneTest(testable); + } + } + + static class Testable { + Set valids; + Set QUAL; + Set aborted; + Set malicious; + User[] sdkgs; + Future[] futures; + BigInteger g; + BigInteger h; + BigInteger secret; + Group group; + int n; + int t; + BigInteger q; + Random random; + ChannelImpl channels = new ChannelImpl(); + + public Testable(int n, int t, Group group, BigInteger q, Random random) { + this.n = n; + this.t = t; + this.group = group; + this.q = q; + this.random = random; + this.sdkgs = new User[n]; + this.valids = new HashSet(); + this.QUAL = new HashSet(); + this.aborted = new HashSet(); + this.malicious = new HashSet(); + this.futures = new Future[n]; + this.g = sampleGenerator(random); + this.h = group.multiply(g,randomIntModQ(random)); + ArrayList ids = new ArrayList(); + for (int id = 1; id<= n ; id++){ + ids.add(id); + } + int id; + BigInteger s; + Channel channel; + Protocol sdkg; + this.secret = BigInteger.ZERO; + ByteEncoder encoder = new BigIntegerByteEncoder(); + while (!ids.isEmpty()) { + id = ids.remove(random.nextInt(ids.size())); + s = randomIntModQ(random); + channel = channels.getChannel(id); + sdkg = new Protocol(t, n, s, random, q, g , h, group, id,encoder); + sdkgs[id - 1] = randomSDKGUser(id,channel,sdkg); + if(QUAL.contains(id)){ + this.secret = this.secret.add(s).mod(q); + } + } + + } + + enum UserType { + HONEST, + FAILSTOP, + MALICIOUS, + } + + public User newSDKGUser(int id, Channel channel, Protocol sdkg, UserType userType) { + switch(userType) { + case HONEST: + valids.add(id); + QUAL.add(id); + return new User(sdkg,channel); + + case FAILSTOP: + int abortStage = random.nextInt(3) + 1; // 1 or 2 or 3 + aborted.add(id); + if (abortStage > 1){ + QUAL.add(id); + } + return new SDKGUserImplAbort(sdkg,channel,abortStage); + + case MALICIOUS: + malicious.add(id); + Set falls = DKGMaliciousUser.selectFallsRandomly(valids,random); + Protocol maliciousSDKG = SDKGMaliciousUserImpl.generateMaliciousSDKG(sdkg,channel,random); + return new SDKGMaliciousUserImpl(sdkg,maliciousSDKG,channel,falls); + + } + fail("Unknown user type"); + return null; + } + + public User randomSDKGUser(int id, Channel channel, Protocol sdkg){ + if (QUAL.size() <= t) { + return newSDKGUser(id, channel, sdkg, UserType.HONEST); + } else { + UserType type = UserType.values()[random.nextInt(UserType.values().length)]; + return newSDKGUser(id, channel, sdkg, type); + } + } + + public BigInteger sampleGenerator(Random random){ + BigInteger ZERO = group.zero(); + BigInteger g; + do { + g = group.sample(random); + } while (!g.equals(ZERO) && !group.multiply(g, q).equals(ZERO)); + return g; + } + + public BigInteger randomIntModQ(Random random){ + return new BigInteger(q.bitLength(), random).mod(q); + } + + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGUserImplAbort.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGUserImplAbort.java new file mode 100644 index 0000000..5b0e11b --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGUserImplAbort.java @@ -0,0 +1,80 @@ +package meerkat.crypto.dkg.gjkr; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.crypto.dkg.comm.MessageUtils; +import meerkat.comm.Channel; +import meerkat.protobuf.Comm; +import meerkat.protobuf.DKG; + +/** + * Created by Tzlil on 3/14/2016. + */ +public class SDKGUserImplAbort extends User { + + public static class AbortException extends RuntimeException { + + } + + final int abortStage; + int stage; + public SDKGUserImplAbort(Protocol sdkg, Channel channel, int abortStage) { + super(sdkg, channel); + this.abortStage = abortStage;// 1 - 4 + this.stage = 1; + } + + private void abort(){ + //stopReceiver(); + channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.ABORT)); + throw new AbortException(); + } + + @Override + protected void stage1() { + if(stage < abortStage) + super.stage1(); + else if(stage == abortStage){ + abort(); + } + stage++; + } + + @Override + protected void stage2() { + if(stage < abortStage) + super.stage2(); + else if(stage == abortStage){ + abort(); + } + stage++; + } + + @Override + protected void stage3() { + if(stage < abortStage) + super.stage3(); + else if(stage == abortStage){ + abort(); + } + stage++; + } + + @Override + protected void stage4() { + if(stage < abortStage) + super.stage4(); + else if(stage == abortStage){ + abort(); + } + stage++; + } + + @Override + public void run() { + try { + super.run(); + } catch (AbortException e) { + // Expected + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharingTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharingTest.java new file mode 100644 index 0000000..7992dfb --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharingTest.java @@ -0,0 +1,68 @@ +package meerkat.crypto.secretsharing.feldman; + +import meerkat.crypto.secretsharing.shamir.Polynomial; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.primitives.concrete.Zpstar; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Random; + +/** + * Created by Tzlil on 1/29/2016. + */ +public class VerifiableSecretSharingTest { + + + VerifiableSecretSharing[] verifiableSecretSharingArray; + int tests = 1 << 10; + Random random; + + @Before + public void settings(){ + BigInteger p = BigInteger.valueOf(2903); + BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); + Zpstar zpstar = new Zpstar(p); + random = new Random(); + BigInteger g; + BigInteger ZERO = zpstar.zero(); + do{ + g = zpstar.sample(random); + }while (!g.equals(ZERO) && !zpstar.multiply(g,q).equals(ZERO));// sample from QRZp* + int t = 8; + int n = 20; + verifiableSecretSharingArray = new VerifiableSecretSharing[tests]; + for (int i = 0; i < verifiableSecretSharingArray.length; i++){ + verifiableSecretSharingArray[i] = new VerifiableSecretSharing(t,n + ,new BigInteger(q.bitLength(),random).mod(q),random,q,g,zpstar); + } + } + + public void oneTest(VerifiableSecretSharing verifiableSecretSharing) throws Exception { + int n = verifiableSecretSharing.getN(); + Group zpstar = verifiableSecretSharing.getGroup(); + BigInteger g = verifiableSecretSharing.getGenerator(); + Polynomial.Point[] shares = new Polynomial.Point[n]; + ArrayList commitments = verifiableSecretSharing.getCommitmentsArrayList(); + BigInteger[] verifications = new BigInteger[n]; + for (int i = 1 ; i <= shares.length; i ++){ + shares[i - 1] = verifiableSecretSharing.getShare(i); + verifications[i - 1] = VerifiableSecretSharing.computeVerificationValue(i,commitments,zpstar); + } + BigInteger expected; + for (int i = 0 ; i < shares.length ; i++){ + expected = zpstar.multiply(g,shares[i].y); + assert (expected.equals(verifications[i])); + } + + } + + @Test + public void secretSharingTest() throws Exception { + for (int i = 0 ; i < verifiableSecretSharingArray.length; i ++){ + oneTest(verifiableSecretSharingArray[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/AddTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/AddTest.java new file mode 100644 index 0000000..79a9aef --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/AddTest.java @@ -0,0 +1,46 @@ +package meerkat.crypto.secretsharing.shamir.PolynomialTests; +import meerkat.crypto.utils.GenerateRandomPolynomial; +import meerkat.crypto.utils.Z; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class AddTest { + + Polynomial[] arr1; + Polynomial[] arr2; + int tests = 1 << 12; + int maxDegree = 15; + int bits = 128; + Random random; + + @Before + public void settings(){ + random = new Random(); + arr1 = new Polynomial[tests]; + arr2 = new Polynomial[tests]; + for (int i = 0; i < arr1.length; i++){ + arr1[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + arr2[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + } + } + + public void oneTest(Polynomial p1, Polynomial p2){ + Polynomial sum = p1.add(p2); + BigInteger x = new BigInteger(bits,random); + assert(sum.evaluate(x).equals(p1.evaluate(x).add(p2.evaluate(x)))); + } + + @Test + public void addTest(){ + for (int i = 0 ; i < arr1.length; i ++){ + oneTest(arr1[i],arr2[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/InterpolationTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/InterpolationTest.java new file mode 100644 index 0000000..ea857f8 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/InterpolationTest.java @@ -0,0 +1,68 @@ +package meerkat.crypto.secretsharing.shamir.PolynomialTests; + +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; +import meerkat.crypto.utils.GenerateRandomPolynomial; +import meerkat.crypto.utils.GenerateRandomPrime; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class InterpolationTest { + Polynomial[] polynomials; + int tests = 1 << 10; + int maxDegree = 15; + int bits = 128; + Random random; + Polynomial.Point[][] pointsArrays; + Arithmetic arithmetic; + BigInteger p = GenerateRandomPrime.SafePrime100Bits; + + @Before + public void settings(){ + random = new Random(); + polynomials = new Polynomial[tests]; + pointsArrays = new Polynomial.Point[tests][]; + arithmetic = new Fp(p); + for (int i = 0; i < polynomials.length; i++){ + polynomials[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,p); + pointsArrays[i] = randomPoints(polynomials[i]); + } + } + + public Polynomial.Point[] randomPoints(Polynomial polynomial){ + Polynomial.Point[] points = new Polynomial.Point[polynomial.getDegree() + 1]; + BigInteger x; + Set set = new HashSet(); + for (int i = 0; i < points.length; i++){ + x = new BigInteger(bits,random).mod(p); + if(set.contains(x)){ + i--; + continue; + } + set.add(x); + points[i] = new Polynomial.Point(x,polynomial); + } + return points; + } + + public void oneTest(Polynomial p, Polynomial.Point[] points) throws Exception { + Polynomial interpolation = Polynomial.interpolation(points,arithmetic); + assert (p.compareTo(interpolation) == 0); + } + + @Test + public void interpolationTest() throws Exception { + for (int i = 0; i < polynomials.length; i ++){ + oneTest(polynomials[i],pointsArrays[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulByConstTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulByConstTest.java new file mode 100644 index 0000000..77afc32 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulByConstTest.java @@ -0,0 +1,48 @@ +package meerkat.crypto.secretsharing.shamir.PolynomialTests; + +import meerkat.crypto.utils.GenerateRandomPolynomial; +import meerkat.crypto.utils.Z; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class MulByConstTest { + + + Polynomial[] arr1; + BigInteger[] arr2; + int tests = 1 << 12; + int maxDegree = 15; + int bits = 128; + Random random; + + @Before + public void settings(){ + random = new Random(); + arr1 = new Polynomial[tests]; + arr2 = new BigInteger[tests]; + for (int i = 0; i < arr1.length; i++){ + arr1[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + arr2[i] = new BigInteger(bits,random); + } + } + + public void oneTest(Polynomial p, BigInteger c){ + Polynomial product = p.mul(c); + BigInteger x = new BigInteger(bits,random); + assert(product.evaluate(x).equals(p.evaluate(x).multiply(c))); + } + + @Test + public void mulByConstTest(){ + for (int i = 0 ; i < arr1.length; i ++){ + oneTest(arr1[i],arr2[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulTest.java new file mode 100644 index 0000000..1f54247 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulTest.java @@ -0,0 +1,48 @@ +package meerkat.crypto.secretsharing.shamir.PolynomialTests; + +import meerkat.crypto.utils.GenerateRandomPolynomial; +import meerkat.crypto.utils.Z; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class MulTest { + + + Polynomial[] arr1; + Polynomial[] arr2; + int tests = 1 << 12; + int maxDegree = 15; + int bits = 128; + Random random; + + @Before + public void settings(){ + random = new Random(); + arr1 = new Polynomial[tests]; + arr2 = new Polynomial[tests]; + for (int i = 0; i < arr1.length; i++){ + arr1[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + arr2[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + } + } + + public void oneTest(Polynomial p1, Polynomial p2){ + Polynomial product = p1.mul(p2); + BigInteger x = new BigInteger(bits,random); + assert(product.evaluate(x).equals(p1.evaluate(x).multiply(p2.evaluate(x)))); + } + + @Test + public void mulTest(){ + for (int i = 0 ; i < arr1.length; i ++){ + oneTest(arr1[i],arr2[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/SecretSharingTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/SecretSharingTest.java new file mode 100644 index 0000000..021b529 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/SecretSharingTest.java @@ -0,0 +1,64 @@ +package meerkat.crypto.secretsharing.shamir; + +import meerkat.crypto.utils.concrete.Fp; +import meerkat.crypto.utils.GenerateRandomPrime; +import org.factcenter.qilin.primitives.CyclicGroup; +import org.factcenter.qilin.primitives.concrete.Zn; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Created by Tzlil on 1/29/2016. + */ +public class SecretSharingTest { + + SecretSharing[] secretSharingArray; + BigInteger[] secrets; + CyclicGroup group; + int tests = 1 << 10; + Random random; + BigInteger p = GenerateRandomPrime.SafePrime100Bits; + BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); + + @Before + public void settings(){ + group = new Zn(q); + int t = 9; + int n = 20; + random = new Random(); + secretSharingArray = new SecretSharing[tests]; + secrets = new BigInteger[tests]; + + for (int i = 0; i < secretSharingArray.length; i++){ + secrets[i] = group.sample(random); + secretSharingArray[i] = new SecretSharing(t,n,secrets[i],random,q); + } + } + + public void oneTest(SecretSharing secretSharing, BigInteger secret) throws Exception { + int t = secretSharing.getT(); + int n = secretSharing.getN(); + Polynomial.Point[] shares = new Polynomial.Point[t + 1]; + List indexes = new ArrayList(n); + for (int i = 1 ; i <= n; i ++){ + indexes.add(i); + } + for (int i = 0 ; i < shares.length ; i++){ + shares[i] = secretSharing.getShare(indexes.remove(random.nextInt(indexes.size()))); + } + BigInteger calculated = SecretSharing.recoverSecret(shares,new Fp(q)); + assert (secret.equals(calculated)); + } + + @Test + public void secretSharingTest() throws Exception { + for (int i = 0 ; i < secretSharingArray.length; i ++){ + oneTest(secretSharingArray[i],secrets[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/utils/BigIntegerByteEncoder.java b/distributed-key-generation/src/test/java/meerkat/crypto/utils/BigIntegerByteEncoder.java new file mode 100644 index 0000000..2781abb --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/utils/BigIntegerByteEncoder.java @@ -0,0 +1,28 @@ +package meerkat.crypto.utils; + +import java.math.BigInteger; + +/** + * Created by Tzlil on 4/7/2016. + */ +public class BigIntegerByteEncoder implements org.factcenter.qilin.util.ByteEncoder { + @Override + public byte[] encode(BigInteger input) { + return input.toByteArray(); + } + + @Override + public BigInteger decode(byte[] input) { + return new BigInteger(1,input); + } + + @Override + public int getMinLength() { + return 0; + } + + @Override + public BigInteger denseDecode(byte[] input) { + return decode(input); + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/utils/GenerateRandomPolynomial.java b/distributed-key-generation/src/test/java/meerkat/crypto/utils/GenerateRandomPolynomial.java new file mode 100644 index 0000000..802c479 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/utils/GenerateRandomPolynomial.java @@ -0,0 +1,30 @@ +package meerkat.crypto.utils; + +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.utils.concrete.Fp; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class GenerateRandomPolynomial { + + public static Polynomial generateRandomPolynomial(int degree, int bits, Random random, Arithmetic arithmetic) { + BigInteger[] coefficients = new BigInteger[degree + 1]; + + for (int i = 0 ; i <= degree; i++ ){ + coefficients[i] = new BigInteger(bits,random); // sample from Zp [0,... q-1] + } + return new Polynomial(coefficients,arithmetic); + } + + public static Polynomial generateRandomPolynomial(int degree,int bits,Random random,BigInteger p) { + BigInteger[] coefficients = generateRandomPolynomial(degree,bits,random,new Fp(p)).getCoefficients(); + for (int i = 0; i { + @Override + public BigInteger add(BigInteger a, BigInteger b) { + return a.add(b); + } + + @Override + public BigInteger sub(BigInteger a, BigInteger b) { + return a.subtract(b); + } + + @Override + public BigInteger mul(BigInteger a, BigInteger b) { + return a.multiply(b); + } + + @Override + public BigInteger div(BigInteger a, BigInteger b) { + return a.divide(b); + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 30d399d..9411448 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 864f396..48f7220 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ -#Tue Aug 05 03:26:05 IDT 2014 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip -distributionSha256Sum=4647967f8de78d6d6d8093cdac50f368f8c2b8038f41a5afe1c3bce4c69219a9 +#Fri Jan 29 21:00:29 IST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip diff --git a/gradlew b/gradlew index 91a7e26..9d82f78 100755 --- a/gradlew +++ b/gradlew @@ -42,11 +42,6 @@ case "`uname`" in ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" @@ -61,9 +56,9 @@ while [ -h "$PRG" ] ; do fi done SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- +cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" -cd "$SAVED" >&- +cd "$SAVED" >/dev/null CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -114,6 +109,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` diff --git a/meerkat-common/.gitignore b/meerkat-common/.gitignore index ae3c172..3e2fcc7 100644 --- a/meerkat-common/.gitignore +++ b/meerkat-common/.gitignore @@ -1 +1 @@ -/bin/ +/bin/ diff --git a/meerkat-common/build.gradle b/meerkat-common/build.gradle index c510e0a..33840bd 100644 --- a/meerkat-common/build.gradle +++ b/meerkat-common/build.gradle @@ -1,218 +1,219 @@ - -plugins { - id "us.kirchmeier.capsule" version "1.0.1" - id 'com.google.protobuf' version '0.7.0' -} - -apply plugin: 'java' -apply plugin: 'com.google.protobuf' -apply plugin: 'eclipse' -apply plugin: 'idea' - -apply plugin: 'application' - -apply plugin: 'maven-publish' - -mainClassName='Demo' - -// Is this a snapshot version? -ext { isSnapshot = false } - -ext { - groupId = 'org.factcenter.meerkat' - nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" - - // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) - // Should be set in ${HOME}/.gradle/gradle.properties - nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" - nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" -} - -description = "Meerkat Voting Common Library" - -// Your project version -version = "0.0" - -version += "${isSnapshot ? '-SNAPSHOT' : ''}" - - -dependencies { - // Logging - compile 'org.slf4j:slf4j-api:1.7.7' - runtime 'ch.qos.logback:logback-classic:1.1.2' - runtime 'ch.qos.logback:logback-core:1.1.2' - - // Google protobufs - compile 'com.google.protobuf:protobuf-java:3.+' - - // ListeningExecutor - compile 'com.google.guava:guava:11.0.+' - - // Crypto - compile 'org.factcenter.qilin:qilin:1.2+' - compile 'org.bouncycastle:bcprov-jdk15on:1.53' - - testCompile 'junit:junit:4.+' - - runtime 'org.codehaus.groovy:groovy:2.4.+' -} - - -/*==== You probably don't have to edit below this line =======*/ - - -// Setup test configuration that can appear as a dependency in -// other subprojects -configurations { - testOutput.extendsFrom (testCompile) -} - -task testJar(type: Jar, dependsOn: testClasses) { - classifier = 'tests' - from sourceSets.test.output -} - -artifacts { - testOutput testJar -} - - - -// The run task added by the application plugin -// is also of type JavaExec. -tasks.withType(JavaExec) { - // Assign all Java system properties from - // the command line to the JavaExec task. - systemProperties System.properties -} - - -protobuf { - // Configure the protoc executable - protoc { - // Download from repositories - artifact = 'com.google.protobuf:protoc:3.+' - } -} - -idea { - module { - project.sourceSets.each { sourceSet -> - - def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" - - // add protobuf generated sources to generated source dir. - if ("test".equals(sourceSet.name)) { - testSourceDirs += file(srcDir) - } else { - sourceDirs += file(srcDir) - } - generatedSourceDirs += file(srcDir) - - } - - // Don't exclude build directory - excludeDirs -= file(buildDir) - } -} - -/*=================================== - * "Fat" Build targets - *===================================*/ - - -task mavenCapsule(type: MavenCapsule){ - description = "Generate a capsule jar that automatically downloads and caches dependencies when run." - applicationClass mainClassName - destinationDir = buildDir -} - -task fatCapsule(type: FatCapsule){ - description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" - - destinationDir = buildDir - - def fatMain = hasProperty('fatmain') ? fatmain : mainClassName - - applicationClass fatMain - - def testJar = hasProperty('test') - - if (hasProperty('fatmain')) { - appendix = "fat-${fatMain}" - } else { - appendix = "fat" - } - - if (testJar) { - from sourceSets.test.output - } -} - -/*=================================== - * Repositories - *===================================*/ - -repositories { - - mavenLocal(); - - // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) - maven { - url nexusRepository - - if (isSnapshot) { - credentials { username - password - - username nexusUser - password nexusPassword - } - } - } - - // Use 'maven central' for other dependencies. - mavenCentral() -} - -task "info" << { - println "Project: ${project.name}" -println "Description: ${project.description}" - println "--------------------------" - println "GroupId: $groupId" - println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" - println "" -} -info.description 'Print some information about project parameters' - - -/*=================================== - * Publishing - *===================================*/ - -publishing { - publications { - mavenJava(MavenPublication) { - groupId project.groupId - pom.withXml { - asNode().appendNode('description', project.description) - } - from project.components.java - - } - } - repositories { - maven { - url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" - credentials { username - password - - username nexusUser - password nexusPassword - } - } - } -} - - - + +plugins { + id "us.kirchmeier.capsule" version "1.0.1" + id 'com.google.protobuf' version '0.7.0' +} + +apply plugin: 'java' +apply plugin: 'com.google.protobuf' +apply plugin: 'eclipse' +apply plugin: 'idea' + +apply plugin: 'application' + +apply plugin: 'maven-publish' + +mainClassName='Demo' + +// Is this a snapshot version? +ext { isSnapshot = false } + +ext { + groupId = 'org.factcenter.meerkat' + nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" + + // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) + // Should be set in ${HOME}/.gradle/gradle.properties + nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" + nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" +} + +description = "Meerkat Voting Common Library" + +// Your project version +version = "0.0" + +version += "${isSnapshot ? '-SNAPSHOT' : ''}" + + +dependencies { + // Logging + 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-core:1.1.2' + + // Google protobufs + compile 'com.google.protobuf:protobuf-java:3.+' + + // ListeningExecutor + compile 'com.google.guava:guava:15.0' + + // Crypto + compile 'org.factcenter.qilin:qilin:1.2+' + compile 'org.bouncycastle:bcprov-jdk15on:1.53' + + testCompile 'junit:junit:4.+' + + runtime 'org.codehaus.groovy:groovy:2.4.+' +} + + +/*==== You probably don't have to edit below this line =======*/ + + +// Setup test configuration that can appear as a dependency in +// other subprojects +configurations { + testOutput.extendsFrom (testCompile) +} + +task testJar(type: Jar, dependsOn: testClasses) { + classifier = 'tests' + from sourceSets.test.output +} + +artifacts { + testOutput testJar +} + + + +// The run task added by the application plugin +// is also of type JavaExec. +tasks.withType(JavaExec) { + // Assign all Java system properties from + // the command line to the JavaExec task. + systemProperties System.properties +} + + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.+' + } +} + +idea { + module { + project.sourceSets.each { sourceSet -> + + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) + } +} + +/*=================================== + * "Fat" Build targets + *===================================*/ + + +task mavenCapsule(type: MavenCapsule){ + description = "Generate a capsule jar that automatically downloads and caches dependencies when run." + applicationClass mainClassName + destinationDir = buildDir +} + +task fatCapsule(type: FatCapsule){ + description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" + + destinationDir = buildDir + + def fatMain = hasProperty('fatmain') ? fatmain : mainClassName + + applicationClass fatMain + + def testJar = hasProperty('test') + + if (hasProperty('fatmain')) { + appendix = "fat-${fatMain}" + } else { + appendix = "fat" + } + + if (testJar) { + from sourceSets.test.output + } +} + +/*=================================== + * Repositories + *===================================*/ + +repositories { + + mavenLocal(); + + // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) + maven { + url nexusRepository + + if (isSnapshot) { + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } + + // Use 'maven central' for other dependencies. + mavenCentral() +} + +task "info" << { + println "Project: ${project.name}" +println "Description: ${project.description}" + println "--------------------------" + println "GroupId: $groupId" + println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" + println "" +} +info.description 'Print some information about project parameters' + + +/*=================================== + * Publishing + *===================================*/ + +publishing { + publications { + mavenJava(MavenPublication) { + groupId project.groupId + pom.withXml { + asNode().appendNode('description', project.description) + } + from project.components.java + + } + } + repositories { + maven { + url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } +} + + + diff --git a/meerkat-common/src/main/java/Demo.java b/meerkat-common/src/main/java/Demo.java index 130adcc..9f5f379 100644 --- a/meerkat-common/src/main/java/Demo.java +++ b/meerkat-common/src/main/java/Demo.java @@ -1,29 +1,29 @@ -import com.google.protobuf.ByteString; -import static meerkat.protobuf.BulletinBoardAPI.*; -import java.io.IOException; - -/** - * Created by talm on 10/26/15. - */ -public class Demo { - public static void main(String args[]) { - System.out.println("Nothing to see yet"); - - BulletinBoardMessage msg; - - UnsignedBulletinBoardMessage msgContents = UnsignedBulletinBoardMessage.newBuilder() - .addTag("test") - .setData(ByteString.copyFromUtf8("some data")) - .build(); - - msg = BulletinBoardMessage.newBuilder() - .setMsg(msgContents) - .build(); - - try { - msg.writeTo(System.err); - } catch (IOException e) { - // Ignore - } - } -} +import com.google.protobuf.ByteString; +import static meerkat.protobuf.BulletinBoardAPI.*; +import java.io.IOException; + +/** + * Created by talm on 10/26/15. + */ +public class Demo { + public static void main(String args[]) { + System.out.println("Nothing to see yet"); + + BulletinBoardMessage msg; + + UnsignedBulletinBoardMessage msgContents = UnsignedBulletinBoardMessage.newBuilder() + .addTag("test") + .setData(ByteString.copyFromUtf8("some data")) + .build(); + + msg = BulletinBoardMessage.newBuilder() + .setMsg(msgContents) + .build(); + + try { + msg.writeTo(System.err); + } catch (IOException e) { + // Ignore + } + } +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/AsyncBulletinBoardClient.java b/meerkat-common/src/main/java/meerkat/bulletinboard/AsyncBulletinBoardClient.java new file mode 100644 index 0000000..d74c1ea --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/AsyncBulletinBoardClient.java @@ -0,0 +1,107 @@ +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 { + + /** + * 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 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 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 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 batchDataList, + int startPosition, FutureCallback callback); + + /** + * Overloading of the postBatchData method which starts at the first position in the batch + */ + public void postBatchData(byte[] signerId, int batchId, List batchDataList, FutureCallback callback); + + /** + * Overloading of the postBatchData method which uses ByteString + */ + public void postBatchData(ByteString signerId, int batchId, List batchDataList, + int startPosition, FutureCallback 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 batchDataList, FutureCallback 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 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 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> 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 callback); + + + /** + * Perform a Sync Query on the bulletin board + * @param syncQuery defines the query + * @param callback is a callback for handling the result of the query + */ + public void querySync(SyncQuery syncQuery, FutureCallback callback); + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BatchDigest.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BatchDigest.java new file mode 100644 index 0000000..6e30fe9 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BatchDigest.java @@ -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); + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BatchDigitalSignature.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BatchDigitalSignature.java new file mode 100644 index 0000000..e04f3c5 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BatchDigitalSignature.java @@ -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; + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java index c51e561..1d4343b 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java @@ -1,54 +1,63 @@ -package meerkat.bulletinboard; - -import meerkat.comm.*; -import meerkat.protobuf.Voting.*; - -import static meerkat.protobuf.BulletinBoardAPI.*; - -import java.util.List; - -/** - * Created by talm on 24/10/15. - */ -public interface BulletinBoardClient { - - interface ClientCallback { - void handleCallback(T msg); - void handleFailure(Throwable t); - } - - /** - * Initialize the client to use some specified servers - * @param clientParams contains the parameters required for the client setup - */ - void init(BulletinBoardClientParams clientParams); - - /** - * Post a message to the bulletin board - * @param msg - */ - MessageID postMessage(BulletinBoardMessage msg, ClientCallback callback); - - /** - * Check how "safe" a given message is - * @param id - * @return a normalized "redundancy score" from 0 (local only) to 1 (fully published) - */ - void getRedundancy(MessageID id, ClientCallback callback); - - /** - * Read all messages posted matching the given filter - * Note that if messages haven't been "fully posted", this might return a different - * set of messages in different calls. However, messages that are fully posted - * are guaranteed to be included. - * @param filterList return only messages that match the filters (null means no filtering). - */ - void readMessages(MessageFilterList filterList, ClientCallback> callback); - - /** - * Closes all connections, if any. - * This is done in a synchronous (blocking) way. - */ - void close(); - -} +package meerkat.bulletinboard; + +import meerkat.comm.CommunicationException; +import meerkat.protobuf.Voting.*; + +import static meerkat.protobuf.BulletinBoardAPI.*; + +import java.util.Collection; +import java.util.List; + +/** + * Created by talm on 24/10/15. + */ +public interface BulletinBoardClient { + + /** + * Initialize the client to use some specified servers + * @param clientParams contains the parameters required for the client setup + */ + void init(BulletinBoardClientParams clientParams); + + /** + * Post a message to the bulletin board in a synchronous manner + * @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) throws CommunicationException; + + /** + * Check how "safe" a given message is in a synchronous manner + * @param id is the unique message identifier for retrieval + * @return a normalized "redundancy score" from 0 (local only) to 1 (fully published) + */ + float getRedundancy(MessageID id); + + /** + * Read all messages posted matching the given filter in a synchronous 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) + * @return the list of messages + */ + List readMessages(MessageFilterList filterList); + + /** + * Create a SyncQuery to test against that corresponds with the current server state for a specific filter list + * Should only be called on instances for which the actual server contacted is known (i.e. there is only one server) + * @param GenerateSyncQueryParams defines the required information needed to generate the query + * These are represented as fractions of the total number of relevant messages + * @return The generated SyncQuery + * @throws CommunicationException when no DB can be contacted + */ + SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) throws CommunicationException; + + /** + * Closes all connections, if any. + * This is done in a synchronous (blocking) way. + */ + void close(); + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardConstants.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardConstants.java new file mode 100644 index 0000000..9ddee90 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardConstants.java @@ -0,0 +1,25 @@ +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 GENERATE_SYNC_QUERY_PATH = "/generatesyncquery"; + 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#"; + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java index da53c1f..1507ab7 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java @@ -1,42 +1,104 @@ -package meerkat.bulletinboard; - -import meerkat.comm.CommunicationException; -import meerkat.protobuf.BulletinBoardAPI.*; - -/** - * Created by Arbel on 07/11/15. - * - * 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. - */ - -public interface BulletinBoardServer{ - - /** - * This method initializes the server by reading the signature data and storing it. - * It also establishes the connection to the DB. - * @throws CommunicationException on DB connection error. - */ - public void init(String meerkatDB) throws CommunicationException; - - /** - * Post a message to bulletin board. - * @param msg is the actual (signed) message - * @return TRUE if the message has been authenticated and FALSE otherwise (in ProtoBuf form). - * @throws CommunicationException on DB connection error. - */ - public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException; - - /** - * Read all messages posted matching the given filter. - * @param filter return only messages that match the filter (empty list means no filtering). - * @return - */ - BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException; - - /** - * This method closes the connection to the DB. - * @throws CommunicationException on DB connection error. - */ - public void close() throws CommunicationException; -} +package meerkat.bulletinboard; + +import com.google.protobuf.BoolValue; +import meerkat.comm.CommunicationException; +import meerkat.comm.MessageOutputStream; +import meerkat.protobuf.BulletinBoardAPI.*; + +import java.util.Collection; + + +/** + * Created by Arbel on 07/11/15. + * + * 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. + */ + +public interface BulletinBoardServer{ + + /** + * This method initializes the server by reading the signature data and storing it + * It also establishes the connection to the DB + * @throws CommunicationException on DB connection error + */ + public void init(String meerkatDB) throws CommunicationException; + + /** + * Post a message to bulletin board. + * @param msg is the actual (signed) message + * @return TRUE if the message has been authenticated and FALSE otherwise (in ProtoBuf form) + * @throws CommunicationException on DB connection error + */ + public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException; + + /** + * Read all messages posted matching the given filter + * @param filterList return only messages that match the filters (empty list or null means no filtering) + * @param out is an output stream into which the matching messages are written + * @throws CommunicationException on DB connection error + */ + public void readMessages(MessageFilterList filterList, MessageOutputStream 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 BoolValue 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 BoolValue postBatchMessage(BatchMessage batchMessage) throws CommunicationException; + + /** + * Attempts to stop and finalize a batch message + * @param message contains the data necessary to stop 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 BoolValue 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 out) throws CommunicationException, IllegalArgumentException; + + /** + * Create a SyncQuery to test against that corresponds with the current server state for a specific filter list + * @param generateSyncQueryParams defines the information needed to generate the query + * @return The generated SyncQuery + * @throws CommunicationException on DB connection error + */ + SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException; + + /** + * 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 + * @throws CommunicationException on DB connection error + */ + public void close() throws CommunicationException; +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardSubscriber.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardSubscriber.java new file mode 100644 index 0000000..85eb2cc --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardSubscriber.java @@ -0,0 +1,32 @@ +package meerkat.bulletinboard; + +import com.google.common.util.concurrent.FutureCallback; +import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; +import meerkat.protobuf.BulletinBoardAPI.MessageFilterList; + +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 03-Mar-16. + * This interface defines the behaviour required from a subscription service to Bulletin Board messages + */ +public interface BulletinBoardSubscriber { + + /** + * Subscribes to a notifier that will return any new messages on the server that match the given filters + * In case of communication error: the subscription is terminated + * @param filterList defines the set of filters for message retrieval + * @param callback defines how to handle new messages received and/or a failures in communication + */ + public void subscribe(MessageFilterList filterList, FutureCallback> callback); + + /** + * Subscribes to a notifier that will return any new messages on the server that match the given filters + * In case of communication error: the subscription is terminated + * @param filterList defines the set of filters for message retrieval + * @param startEntry defines the first entry number to consider + * @param callback defines how to handle new messages received and/or a failures in communication + */ + public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback> callback); + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardSynchronizer.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardSynchronizer.java new file mode 100644 index 0000000..4b07225 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardSynchronizer.java @@ -0,0 +1,22 @@ +package meerkat.bulletinboard; + +/** + * Created by Arbel Deutsch Peled on 08-Mar-16. + * This interface defines the behaviour of a bulletin board synchronizer + * This is used to make sure that data in a specific instance of a bulletin board server is duplicated to a sufficient percentage of the other servers + */ +public interface BulletinBoardSynchronizer extends Runnable{ + + /** + * + * @param localClient is a client for the local DB instance + * @param remoteClient is a client for the remote DBs + * @param minRedundancy + */ + public void init(BulletinBoardClient localClient, AsyncBulletinBoardClient remoteClient, float minRedundancy); + + @Override + public void run(); + + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/Checksum.java b/meerkat-common/src/main/java/meerkat/bulletinboard/Checksum.java new file mode 100644 index 0000000..b8ddb0b --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/Checksum.java @@ -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 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 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 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 bulletinBoardMessages) throws IllegalStateException; + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/CompleteBatch.java b/meerkat-common/src/main/java/meerkat/bulletinboard/CompleteBatch.java new file mode 100644 index 0000000..649fd8b --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/CompleteBatch.java @@ -0,0 +1,150 @@ +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 batchDataList; + private Signature signature; + private Timestamp timestamp; + + public CompleteBatch() { + batchDataList = new LinkedList(); + } + + public CompleteBatch(BeginBatchMessage newBeginBatchMessage) { + this(); + beginBatchMessage = newBeginBatchMessage; + } + + public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List newDataList) { + this(newBeginBatchMessage); + appendBatchData(newDataList); + } + + public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List newDataList, Signature newSignature) { + this(newBeginBatchMessage, newDataList); + signature = newSignature; + } + + public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List 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 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 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() { + + if (beginBatchMessage == null || beginBatchMessage.getSignerId() == null) + return "Unspecified batch " + super.toString(); + + return "Batch " + beginBatchMessage.getSignerId().toString() + ":" + beginBatchMessage.getBatchId(); + + } + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBatchDigest.java b/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBatchDigest.java new file mode 100644 index 0000000..852bb24 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBatchDigest.java @@ -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()); + } + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBatchDigitalSignature.java b/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBatchDigitalSignature.java new file mode 100644 index 0000000..7327b8e --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBatchDigitalSignature.java @@ -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(); + } + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/SimpleChecksum.java b/meerkat-common/src/main/java/meerkat/bulletinboard/SimpleChecksum.java new file mode 100644 index 0000000..90d7ae5 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/SimpleChecksum.java @@ -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 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 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 messageIDs) { + update(messageIDs); + return getChecksum(); + } + + @Override + public long digestAndGetChecksum(BulletinBoardMessage bulletinBoardMessage) throws IllegalStateException { + digestAndUpdate(bulletinBoardMessage); + return getChecksum(); + } + + @Override + public long digestAndGetChecksum(Collection bulletinBoardMessages) throws IllegalStateException { + digestAndUpdate(bulletinBoardMessages); + return getChecksum(); + } +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/SubscriptionAsyncBulletinBoardClient.java b/meerkat-common/src/main/java/meerkat/bulletinboard/SubscriptionAsyncBulletinBoardClient.java new file mode 100644 index 0000000..b07e655 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/SubscriptionAsyncBulletinBoardClient.java @@ -0,0 +1,7 @@ +package meerkat.bulletinboard; + +/** + * Created by Arbel Deutsch Peled on 03-Mar-16. + */ +public interface SubscriptionAsyncBulletinBoardClient extends AsyncBulletinBoardClient, BulletinBoardSubscriber { +} diff --git a/meerkat-common/src/main/java/meerkat/comm/Channel.java b/meerkat-common/src/main/java/meerkat/comm/Channel.java new file mode 100644 index 0000000..7dd74c1 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/comm/Channel.java @@ -0,0 +1,42 @@ +package meerkat.comm; + +import com.google.protobuf.Message; +import meerkat.protobuf.Comm; + +/** + * A generic communication channel that supports point-to-point and broadcast operation + */ + +public interface Channel { + /** + * Return the id of the channel's endpoint (this will be used as the source of message sent from the channel). + * @return + */ + public int getSourceId(); + + public interface ReceiverCallback { + public void receiveMessage(Comm.BroadcastMessage envelope); + } + + /** + * sends a private message + * @param destUser destination user's identifier + * @param msg message + */ + public void sendMessage(int destUser, Message msg); + + + /** + * broadcasts a message to all parties (including the sender) + * @param msg message + */ + public void broadcastMessage(Message msg); + + /** + * Register a callback to handle received messages. + * The callback is called in the Channel thread, so no long processing should + * occur in the callback method. + * @param callback + */ + public void registerReceiverCallback(ReceiverCallback callback); +} diff --git a/meerkat-common/src/main/java/meerkat/comm/CommunicationException.java b/meerkat-common/src/main/java/meerkat/comm/CommunicationException.java index 05e7a22..f96dced 100644 --- a/meerkat-common/src/main/java/meerkat/comm/CommunicationException.java +++ b/meerkat-common/src/main/java/meerkat/comm/CommunicationException.java @@ -1,36 +1,36 @@ -package meerkat.comm; - -/** - * Created by talm on 24/10/15. - */ -public class CommunicationException extends Exception { - - /** - * Generated serial. - */ - - private static final long serialVersionUID = 2279440129497891293L; - private String message; - - /** - * Default constructor. To be used only if error type is unknown. - */ - public CommunicationException(){ - message = "Unknown communication exception"; - } - - /** - * Constructor enabling specifying of an error message. - * @param errorMessage - */ - public CommunicationException(String errorMessage){ - message = errorMessage; - } - - /** - * @return the error message specified. - */ - public String getMessage(){ - return message; - } -} +package meerkat.comm; + +/** + * Created by talm on 24/10/15. + */ +public class CommunicationException extends Exception { + + /** + * Generated serial. + */ + + private static final long serialVersionUID = 2279440129497891293L; + private String message; + + /** + * Default constructor. To be used only if error type is unknown. + */ + public CommunicationException(){ + message = "Unknown communication exception"; + } + + /** + * Constructor enabling specifying of an error message. + * @param errorMessage + */ + public CommunicationException(String errorMessage){ + message = errorMessage; + } + + /** + * @return the error message specified. + */ + public String getMessage(){ + return message; + } +} diff --git a/meerkat-common/src/main/java/meerkat/comm/MessageInputStream.java b/meerkat-common/src/main/java/meerkat/comm/MessageInputStream.java new file mode 100644 index 0000000..b1e5255 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/comm/MessageInputStream.java @@ -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 implements Iterable{ + + private T.Builder builder; + + private InputStream in; + + MessageInputStream(InputStream in, Class type) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + this.in = in; + this.builder = (T.Builder) type.getMethod("newBuilder").invoke(type); + } + + @Override + public Iterator iterator() { + + return new Iterator() { + + @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 MessageInputStream createMessageInputStream(InputStream in, Class 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 asList() throws IOException{ + + List list = new LinkedList<>(); + + while (isAvailable()){ + list.add(readMessage()); + } + + return list; + + } + + public void close() throws IOException { + + in.close(); + + } + +} diff --git a/meerkat-common/src/main/java/meerkat/comm/MessageOutputStream.java b/meerkat-common/src/main/java/meerkat/comm/MessageOutputStream.java new file mode 100644 index 0000000..f72c04c --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/comm/MessageOutputStream.java @@ -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 { + + 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(); + } + +} diff --git a/meerkat-common/src/main/java/meerkat/comm/Timestamp.java b/meerkat-common/src/main/java/meerkat/comm/Timestamp.java deleted file mode 100644 index 6c63854..0000000 --- a/meerkat-common/src/main/java/meerkat/comm/Timestamp.java +++ /dev/null @@ -1,7 +0,0 @@ -package meerkat.comm; - -/** - * Created by talm on 24/10/15. - */ -public class Timestamp { -} diff --git a/meerkat-common/src/main/java/meerkat/crypto/Digest.java b/meerkat-common/src/main/java/meerkat/crypto/Digest.java index 06b012c..b7d86dc 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/Digest.java +++ b/meerkat-common/src/main/java/meerkat/crypto/Digest.java @@ -1,6 +1,7 @@ package meerkat.crypto; import com.google.protobuf.Message; +import meerkat.protobuf.BulletinBoardAPI.MessageID; import java.security.MessageDigest; @@ -13,7 +14,13 @@ public interface Digest { * (copied from {@link MessageDigest#digest()}) * @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) @@ -22,12 +29,12 @@ public interface Digest { * @param msg * @return */ - void update(Message msg); + public void update(Message msg); /** * Resets the digest for further use. */ - void reset(); + public void reset(); /** * Clone the current digest state diff --git a/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java b/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java index e7b64e5..76c32a6 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java +++ b/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java @@ -5,11 +5,13 @@ import com.google.protobuf.Message; import java.io.IOException; import java.io.InputStream; -import java.security.InvalidKeyException; import java.security.KeyStore; -import java.security.SignatureException; -import java.security.UnrecoverableKeyException; 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.*; /** @@ -71,6 +73,20 @@ public interface DigitalSignature { */ 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 * key parts. diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java index 887b8e8..ab8084b 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java @@ -30,7 +30,10 @@ import javax.security.auth.callback.UnsupportedCallbackException; * * 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 public static String KEYSTORE_TYPE = "PKCS12"; @@ -208,6 +211,7 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur * @throws KeyStoreException * @throws NoSuchAlgorithmException */ + @Override public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException { KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE); diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java index f9936c7..ab14421 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java @@ -31,7 +31,10 @@ import java.util.Random; /** * 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()); public final static String KEY_ALGORITHM = "ECDH"; @@ -110,7 +113,7 @@ public class ECElGamalEncryption extends GlobalCryptoSetup implements Encryption Pair randomizer = elGamalPK.encrypt(curve.getInfinity(), rndInt); ConcreteCrypto.ElGamalCiphertext originalEncodedCipher= ConcreteCrypto.ElGamalCiphertext.parseFrom(msg.getData()); - Pair originalCipher = new Pair<>( + Pair originalCipher = new Pair( curve.decodePoint(originalEncodedCipher.getC1().toByteArray()), curve.decodePoint(originalEncodedCipher.getC2().toByteArray())); Pair newCipher = elGamalPK.add(originalCipher, randomizer); diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java index 4f2e7a5..3d12701 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java @@ -10,13 +10,26 @@ import java.security.Security; /** * 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); - static boolean loadedBouncyCastle = false; - static Provider bouncyCastleProvider; + private boolean loadedBouncyCastle = false; + 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 String[] version = System.getProperty("java.version").split("\\."); int major = Integer.parseInt(version[0]); @@ -24,9 +37,11 @@ public class GlobalCryptoSetup { 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) { bouncyCastleProvider = new BouncyCastleProvider(); // Make bouncycastle our default provider if we're running on a JVM version < 8 @@ -39,5 +54,4 @@ public class GlobalCryptoSetup { } } - public GlobalCryptoSetup() { doSetup(); } } diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/SHA256Digest.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/SHA256Digest.java index 4f60af3..a7723ec 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/SHA256Digest.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/SHA256Digest.java @@ -3,6 +3,7 @@ package meerkat.crypto.concrete; import com.google.protobuf.ByteString; import com.google.protobuf.Message; import meerkat.crypto.Digest; +import meerkat.protobuf.BulletinBoardAPI.MessageID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,7 +14,7 @@ import java.security.NoSuchAlgorithmException; /** * 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()); public static final String SHA256 = "SHA-256"; @@ -60,6 +61,11 @@ public class SHA256Digest extends GlobalCryptoSetup implements Digest { return hash.digest(); } + @Override + public MessageID digestAsMessageID() { + return MessageID.newBuilder().setID(ByteString.copyFrom(digest())).build(); + } + @Override public void update(Message msg) { diff --git a/meerkat-common/src/main/java/meerkat/logging/LogVerifier.java b/meerkat-common/src/main/java/meerkat/logging/LogVerifier.java index 7ba928a..f27f740 100644 --- a/meerkat-common/src/main/java/meerkat/logging/LogVerifier.java +++ b/meerkat-common/src/main/java/meerkat/logging/LogVerifier.java @@ -1,7 +1,7 @@ -package meerkat.logging; - -/** - * Created by talm on 25/10/15. - */ -public class LogVerifier { -} +package meerkat.logging; + +/** + * Created by talm on 25/10/15. + */ +public class LogVerifier { +} diff --git a/meerkat-common/src/main/java/meerkat/logging/Logger.java b/meerkat-common/src/main/java/meerkat/logging/Logger.java index 343ff31..26ec424 100644 --- a/meerkat-common/src/main/java/meerkat/logging/Logger.java +++ b/meerkat-common/src/main/java/meerkat/logging/Logger.java @@ -1,7 +1,7 @@ -package meerkat.logging; - -/** - * Created by talm on 25/10/15. - */ -public class Logger { -} +package meerkat.logging; + +/** + * Created by talm on 25/10/15. + */ +public class Logger { +} diff --git a/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationConstants.java b/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationConstants.java new file mode 100644 index 0000000..1bc6d3c --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationConstants.java @@ -0,0 +1,13 @@ +package meerkat.pollingstation; + +/** + * Created by Arbel Deutsch Peled on 21-Dec-15. + */ +public interface PollingStationConstants { + + // Relative addresses for Scanner operations + + public static final String POLLING_STATION_WEB_SCANNER_SCAN_PATH = "/scan"; + public static final String POLLING_STATION_WEB_SCANNER_ERROR_PATH = "/error"; + +} diff --git a/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationScanner.java b/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationScanner.java new file mode 100644 index 0000000..290d0ee --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationScanner.java @@ -0,0 +1,61 @@ +package meerkat.pollingstation; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.BoolValue; +import meerkat.protobuf.PollingStation.*; +/** + * Created by Arbel on 05/05/2016. + * An interface for the scanner used by the Polling Station Committee + * The scanner works as a producer, while the polling station is the consumer + * That is to say: scans are pushed from the scanner rather than requested by the polling station + */ +public interface PollingStationScanner { + + /** + * An interface for processing scans (Polling Station side) + */ + public interface Consumer { + + /** + * Sets up the connection to the scanner and begins receiving scans + * @throws Exception when the operation fails + */ + public void start() throws Exception; + + /** + * Closes the connection to the scanner + * @throws Exception when the operation fails + */ + public void stop() throws Exception; + + /** + * Subscribes to new scans + * + * @param scanCallback is the handler for scanned data + */ + public void subscribe(FutureCallback scanCallback); + + } + + /** + * An interface for submitting scanned data (scanner side) + */ + public interface Producer { + + /** + * Sends a scan to all subscribers + * @param scannedData contains the scanned data + * @return a BoolValue containing TRUE iff the scanned data has been sent to at least one subscriber + */ + public BoolValue newScan(ScannedData scannedData); + + /** + * Notifies subscribers about an error that occurred during scan + * @param errorMsg is the error that occurred + * @return a BoolValue containing TRUE iff the error has been sent to at least one subscriber + */ + public BoolValue reportScanError(ErrorMsg errorMsg); + + } + +} \ No newline at end of file diff --git a/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java index 77a6663..653f130 100644 --- a/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java +++ b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java @@ -1,49 +1,49 @@ -package meerkat.util; - -import meerkat.protobuf.BulletinBoardAPI; -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.protobuf.Crypto.*; - -import java.util.Comparator; -import java.util.List; - -/** - * Created by Arbel Deutsch Peled on 05-Dec-15. - * This class implements a comparison between BulletinBoardMessage instances that disregards: - * 1. The entry number (since this can be different between database instances) - * 2. The order of the signatures - */ -public class BulletinBoardMessageComparator implements Comparator { - - /** - * Compare the messages - * @param msg1 - * @param msg2 - * @return 0 if the messages are equivalent (see above) and -1 otherwise. - */ - @Override - public int compare(BulletinBoardMessage msg1, BulletinBoardMessage msg2) { - - List msg1Sigs = msg1.getSigList(); - List msg2Sigs = msg2.getSigList(); - - // Compare unsigned message - if (!msg1.getMsg().equals(msg2.getMsg())){ - return -1; - } - - // Compare signatures - - if (msg1Sigs.size() != msg2Sigs.size()){ - return -1; - } - - for (Signature sig : msg1Sigs){ - if (!msg2Sigs.contains(sig)) { - return -1; - } - } - - return 0; - } -} +package meerkat.util; + +import meerkat.protobuf.BulletinBoardAPI; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Crypto.*; + +import java.util.Comparator; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 05-Dec-15. + * This class implements a comparison between BulletinBoardMessage instances that disregards: + * 1. The entry number (since this can be different between database instances) + * 2. The order of the signatures + */ +public class BulletinBoardMessageComparator implements Comparator { + + /** + * Compare the messages + * @param msg1 + * @param msg2 + * @return 0 if the messages are equivalent (see above) and -1 otherwise. + */ + @Override + public int compare(BulletinBoardMessage msg1, BulletinBoardMessage msg2) { + + List msg1Sigs = msg1.getSigList(); + List msg2Sigs = msg2.getSigList(); + + // Compare unsigned message + if (!msg1.getMsg().equals(msg2.getMsg())){ + return -1; + } + + // Compare signatures + + if (msg1Sigs.size() != msg2Sigs.size()){ + return -1; + } + + for (Signature sig : msg1Sigs){ + if (!msg2Sigs.contains(sig)) { + return -1; + } + } + + return 0; + } +} diff --git a/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageGenerator.java b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageGenerator.java new file mode 100644 index 0000000..dff562e --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageGenerator.java @@ -0,0 +1,118 @@ +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.LinkedList; +import java.util.List; +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 + * @param tags is a list of initial tags (on top of which more will be added according to the method input) + * @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, List tags) + throws SignatureException{ + + // Generate random data. + + byte[] data = new byte[dataSize]; + String[] newTags = new String[tagNumber]; + + for (int i = 0; i < dataSize; i++) { + data[i] = randomByte(); + } + + for (int i = 0; i < tagNumber; i++) { + newTags[i] = randomString(); + } + + UnsignedBulletinBoardMessage unsignedMessage = + UnsignedBulletinBoardMessage.newBuilder() + .setData(ByteString.copyFrom(data)) + .setTimestamp(timestamp) + .addAllTag(tags) + .addAllTag(Arrays.asList(newTags)) + .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 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 { + + List tags = new LinkedList<>(); + return generateRandomMessage(signers, timestamp, dataSize, tagNumber, tags); + + } + + /** + * 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); + + } + +} diff --git a/meerkat-common/src/main/java/meerkat/util/BulletinBoardUtils.java b/meerkat-common/src/main/java/meerkat/util/BulletinBoardUtils.java new file mode 100644 index 0000000..8793b2d --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/util/BulletinBoardUtils.java @@ -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 removePrefixTags(BulletinBoardMessage message, Iterable prefixes) { + + if (prefixes == null) + return message.getMsg().getTagList(); + + List 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); + } + +} diff --git a/meerkat-common/src/main/java/meerkat/util/Hex.java b/meerkat-common/src/main/java/meerkat/util/Hex.java index 2d0e4ef..8064d7d 100644 --- a/meerkat-common/src/main/java/meerkat/util/Hex.java +++ b/meerkat-common/src/main/java/meerkat/util/Hex.java @@ -1,26 +1,26 @@ -package meerkat.util; - -import com.google.protobuf.ByteString; - -/** - * Convert to/from Hex - */ -public class Hex { - /** - * Encode a {@link ByteString} as a hex string. - * @param str - * @return - */ - public static String encode(ByteString str) { - StringBuilder s = new StringBuilder(); - for (byte b : str) { - s.append(Integer.toHexString(((int) b) & 0xff)); - } - return s.toString(); - } - - public static String encode(byte[] bytes) { - return encode(ByteString.copyFrom(bytes)); - } -} - +package meerkat.util; + +import com.google.protobuf.ByteString; + +/** + * Convert to/from Hex + */ +public class Hex { + /** + * Encode a {@link ByteString} as a hex string. + * @param str + * @return + */ + public static String encode(ByteString str) { + StringBuilder s = new StringBuilder(); + for (byte b : str) { + s.append(Integer.toHexString(((int) b) & 0xff)); + } + return s.toString(); + } + + public static String encode(byte[] bytes) { + return encode(ByteString.copyFrom(bytes)); + } +} + diff --git a/meerkat-common/src/main/java/meerkat/util/TimestampComparator.java b/meerkat-common/src/main/java/meerkat/util/TimestampComparator.java new file mode 100644 index 0000000..9acdaa6 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/util/TimestampComparator.java @@ -0,0 +1,31 @@ +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 { + + @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; + + } + + } + +} diff --git a/meerkat-common/src/main/java/meerkat/voting/VotingBooth.java b/meerkat-common/src/main/java/meerkat/voting/VotingBooth.java index e337a83..5f8a7cf 100644 --- a/meerkat-common/src/main/java/meerkat/voting/VotingBooth.java +++ b/meerkat-common/src/main/java/meerkat/voting/VotingBooth.java @@ -1,70 +1,70 @@ -package meerkat.voting; - -import static meerkat.protobuf.Voting.*; - -/** - * Created by talm on 25/10/15. - */ -public interface VotingBooth { - - public interface UI { - /** - * Prepare UI for a new user. - */ - void votingBegin(); - - /** - * UI must physically commit to an encrypted (or Wombat style) ballot. - * (probably by printing) - * - * When commitment is complete, should ask voter to choose between - * cast and audit. - * - * Called by votingbooth thread. - */ - void commitToEncryptedBallot(EncryptedBallot ballot); - - - /** - * Finalize a vote for casting - * Called by votingbooth in case user decides to cast. - */ - void castVote(); - - /** - * Submit audit information and spoil vote. - * Called by votingbooth in case user decides to audit - * @param ballotSecrets - */ - void auditVote(BallotSecrets ballotSecrets); - - - } - - /** - * Must be called before using any other method. - * @param globalParams global election parameters (e.g., global signing key, global encryption key) - * @param boothParams local parameters (e.g., private signature key for booth, randomness table?) - */ - void init(ElectionParams globalParams, BoothParams boothParams); - - - /** - * Called from UI thread when voter has finished making selection. - * - * Should encrypt ballot and commit. - * @param ballot - */ - void submitBallot(PlaintextBallot ballot); - - /** - * UI calls this when the user cancels the voting process in the middle. - */ - void cancelBallot(); - - /** - * Called by UI thread after voter made choice to cast or audit ballot. - * @param castVote - */ - void voterCastOrAudit(boolean castVote); -} +package meerkat.voting; + +import static meerkat.protobuf.Voting.*; + +/** + * Created by talm on 25/10/15. + */ +public interface VotingBooth { + + public interface UI { + /** + * Prepare UI for a new user. + */ + void votingBegin(); + + /** + * UI must physically commit to an encrypted (or Wombat style) ballot. + * (probably by printing) + * + * When commitment is complete, should ask voter to choose between + * cast and audit. + * + * Called by votingbooth thread. + */ + void commitToEncryptedBallot(EncryptedBallot ballot); + + + /** + * Finalize a vote for casting + * Called by votingbooth in case user decides to cast. + */ + void castVote(); + + /** + * Submit audit information and spoil vote. + * Called by votingbooth in case user decides to audit + * @param ballotSecrets + */ + void auditVote(BallotSecrets ballotSecrets); + + + } + + /** + * Must be called before using any other method. + * @param globalParams global election parameters (e.g., global signing key, global encryption key) + * @param boothParams local parameters (e.g., private signature key for booth, randomness table?) + */ + void init(ElectionParams globalParams, BoothParams boothParams); + + + /** + * Called from UI thread when voter has finished making selection. + * + * Should encrypt ballot and commit. + * @param ballot + */ + void submitBallot(PlaintextBallot ballot); + + /** + * UI calls this when the user cancels the voting process in the middle. + */ + void cancelBallot(); + + /** + * Called by UI thread after voter made choice to cast or audit ballot. + * @param castVote + */ + void voterCastOrAudit(boolean castVote); +} diff --git a/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto b/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto index 0fe35f8..27ad512 100644 --- a/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto +++ b/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto @@ -1,80 +1,163 @@ -syntax = "proto3"; - -package meerkat; - -option java_package = "meerkat.protobuf"; - -import 'meerkat/crypto.proto'; - -message BoolMsg { - bool value = 1; -} - -message IntMsg { - int32 value = 1; -} - -message MessageID { - // The ID of a message for unique retrieval. - // Note that it is assumed that this ID is a function of the message itself. - bytes ID = 1; -} - -message UnsignedBulletinBoardMessage { - // Optional tags describing message - repeated string tag = 1; - - // The actual content of the message - bytes data = 2; -} - -message BulletinBoardMessage { - - // Serial entry number of message in database - int64 entryNum = 1; - - // Unsigned raw data of message - UnsignedBulletinBoardMessage msg = 2; - - // Signature of message (and tags), excluding the entry number. - repeated meerkat.Signature sig = 3; -} - -message BulletinBoardMessageList { - - repeated BulletinBoardMessage message = 1; - -} - -enum FilterType { - MSG_ID = 0; // Match exact message ID - EXACT_ENTRY = 1; // Match exact entry number in database (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) - TAG = 4; // Find all entries in database that have a specific tag - - // NOTE: The MAX_MESSAGES filter must remain the last filter type - // This is because the condition it specifies in an SQL statement must come last in the statement - // Keeping it last here allows for easily sorting the filters and keeping the code general - MAX_MESSAGES = 5; // Return at most some specified number of messages -} - -message MessageFilter { - - FilterType type = 1; - - oneof filter{ - bytes id = 2; - int64 entry = 3; - string tag = 4; - int64 maxMessages = 5; - } -} - -message MessageFilterList { - - // Combination of filters. - // To be implemented using intersection ("AND") operations. - repeated MessageFilter filter = 1; - +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +import 'meerkat/crypto.proto'; +import 'google/protobuf/timestamp.proto'; + +message MessageID { + // The ID of a message for unique retrieval. + // Note that it is assumed that this ID is a function of the message itself. + bytes ID = 1; +} + +message UnsignedBulletinBoardMessage { + // Optional tags describing message; Used for message retrieval + repeated string tag = 1; + + // Timestamp of the message (as defined by client) + google.protobuf.Timestamp timestamp = 2; + + // The actual content of the message + bytes data = 3; +} + +message BulletinBoardMessage { + + // Serial entry number of message in database + int64 entryNum = 1; + + // Unsigned raw data of message + UnsignedBulletinBoardMessage msg = 2; + + // Signature of message (and tags), excluding the entry number. + repeated meerkat.Signature sig = 3; + +} + +message BulletinBoardMessageList { + + repeated BulletinBoardMessage message = 1; + +} + +enum FilterType { + MSG_ID = 0; // Match exact message ID + EXACT_ENTRY = 1; // Match exact entry number in database (chronological) + MAX_ENTRY = 2; // Find all entries in database up to specified entry number (chronological) + MIN_ENTRY = 3; // Find all entries in database starting from specified entry number (chronological) + 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 + // This is because the condition it specifies in an SQL statement must come last in the statement + // Keeping it last here allows for easily sorting the filters and keeping the code general + MAX_MESSAGES = 8; // Return at most some specified number of messages +} + +message MessageFilter { + + FilterType type = 1; + + oneof filter{ + bytes id = 2; + int64 entry = 3; + string tag = 4; + int64 maxMessages = 5; + google.protobuf.Timestamp timestamp = 6; + } +} + +message MessageFilterList { + + // Combination of filters. + // To be implemented using intersection ("AND") operations. + 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 required information for generation of a SyncQuery instance by the server +message GenerateSyncQueryParams { + + // Defines the set of messages required + MessageFilterList filterList = 1; + + // Defines the locations in the list of messages to calculate single sync queries for + // The values should be between 0.0 and 1.0 and define the location in fractions of the size of the message set + repeated float breakpointList = 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; + } \ No newline at end of file diff --git a/meerkat-common/src/main/proto/meerkat/PollingStation.proto b/meerkat-common/src/main/proto/meerkat/PollingStation.proto new file mode 100644 index 0000000..0cdc658 --- /dev/null +++ b/meerkat-common/src/main/proto/meerkat/PollingStation.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +// Container for scanned data +message ScannedData { + bytes data = 1; +} + +// Container for error messages +message ErrorMsg { + string msg = 1; +} \ No newline at end of file diff --git a/meerkat-common/src/main/proto/meerkat/comm.proto b/meerkat-common/src/main/proto/meerkat/comm.proto new file mode 100644 index 0000000..6808288 --- /dev/null +++ b/meerkat-common/src/main/proto/meerkat/comm.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +message BroadcastMessage { + int32 sender = 1; + int32 destination = 2; + bool is_private = 3; + + bytes payload = 5; +} \ No newline at end of file diff --git a/meerkat-common/src/main/proto/meerkat/concrete_crypto.proto b/meerkat-common/src/main/proto/meerkat/concrete_crypto.proto index d8c40d3..9f1f818 100644 --- a/meerkat-common/src/main/proto/meerkat/concrete_crypto.proto +++ b/meerkat-common/src/main/proto/meerkat/concrete_crypto.proto @@ -1,22 +1,22 @@ -// Protobufs for specific crypto primitives - -syntax = "proto3"; - -package meerkat; - -import 'meerkat/crypto.proto'; - -option java_package = "meerkat.protobuf"; - - -message ElGamalPublicKey { - // DER-encoded SubjectPublicKeyInfo as in RFC 3279 - bytes subject_public_key_info = 1; -} - -// An El-Gamal ciphertext -// Each group element should be an ASN.1 encoded curve point with compression. -message ElGamalCiphertext { - bytes c1 = 1; // First group element - bytes c2 = 2; // Second group element +// Protobufs for specific crypto primitives + +syntax = "proto3"; + +package meerkat; + +import 'meerkat/crypto.proto'; + +option java_package = "meerkat.protobuf"; + + +message ElGamalPublicKey { + // DER-encoded SubjectPublicKeyInfo as in RFC 3279 + bytes subject_public_key_info = 1; +} + +// An El-Gamal ciphertext +// Each group element should be an ASN.1 encoded curve point with compression. +message ElGamalCiphertext { + bytes c1 = 1; // First group element + bytes c2 = 2; // Second group element } \ No newline at end of file diff --git a/meerkat-common/src/main/proto/meerkat/crypto.proto b/meerkat-common/src/main/proto/meerkat/crypto.proto index eeec159..3b3e906 100644 --- a/meerkat-common/src/main/proto/meerkat/crypto.proto +++ b/meerkat-common/src/main/proto/meerkat/crypto.proto @@ -1,54 +1,54 @@ -syntax = "proto3"; - -package meerkat; - -option java_package = "meerkat.protobuf"; - -enum SignatureType { - ECDSA = 0; - DSA = 1; -} - -message BigInteger { - bytes data = 1; -} - -// A digital signature -message Signature { - SignatureType type = 1; - - // Data encoding depends on type; default is DER-encoded - bytes data = 2; - - // ID of the signer (should be the fingerprint of the signature verification key) - bytes signer_id = 3; -} - -// Public key used to verify signatures -message SignatureVerificationKey { - SignatureType type = 1; - - // Data encoding depends on type; default is x509 DER-encoded - bytes data = 2; -} - -// A public encryption key -message EncryptionPublicKey { - bytes data = 1; -} - -// Randomness used for encryption -message EncryptionRandomness { - bytes data = 1; -} - -// A proof that randomness is correctly generated -message RandomnessGenerationProof { - bytes data = 1; -} - -// An encrypted message (rerandomizable) -message RerandomizableEncryptedMessage { - bytes data = 1; -} - +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +enum SignatureType { + ECDSA = 0; + DSA = 1; +} + +message BigInteger { + bytes data = 1; +} + +// A digital signature +message Signature { + SignatureType type = 1; + + // Data encoding depends on type; default is DER-encoded + bytes data = 2; + + // ID of the signer (should be the fingerprint of the signature verification key) + bytes signer_id = 3; +} + +// Public key used to verify signatures +message SignatureVerificationKey { + SignatureType type = 1; + + // Data encoding depends on type; default is x509 DER-encoded + bytes data = 2; +} + +// A public encryption key +message EncryptionPublicKey { + bytes data = 1; +} + +// Randomness used for encryption +message EncryptionRandomness { + bytes data = 1; +} + +// A proof that randomness is correctly generated +message RandomnessGenerationProof { + bytes data = 1; +} + +// An encrypted message (rerandomizable) +message RerandomizableEncryptedMessage { + bytes data = 1; +} + diff --git a/meerkat-common/src/main/proto/meerkat/mixing.proto b/meerkat-common/src/main/proto/meerkat/mixing.proto index 50cffc7..2d5d6da 100644 --- a/meerkat-common/src/main/proto/meerkat/mixing.proto +++ b/meerkat-common/src/main/proto/meerkat/mixing.proto @@ -1,12 +1,12 @@ -syntax = "proto3"; - -package meerkat; - -option java_package = "meerkat.protobuf"; - -import 'meerkat/crypto.proto'; - -// TODO: -message ZeroKnowledgeProof { - bytes data = 1; +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +import 'meerkat/crypto.proto'; + +// TODO: +message ZeroKnowledgeProof { + bytes data = 1; } \ No newline at end of file diff --git a/meerkat-common/src/main/proto/meerkat/voting.proto b/meerkat-common/src/main/proto/meerkat/voting.proto index 09c8b21..c253a88 100644 --- a/meerkat-common/src/main/proto/meerkat/voting.proto +++ b/meerkat-common/src/main/proto/meerkat/voting.proto @@ -151,3 +151,5 @@ message SimpleCategoriesSelectionData { Category shared_defaults = 1; repeated CategoryChooser categoryChooser = 2; } + + diff --git a/meerkat-common/src/main/resources/logback.groovy b/meerkat-common/src/main/resources/logback.groovy index 076b6c4..85fa80d 100644 --- a/meerkat-common/src/main/resources/logback.groovy +++ b/meerkat-common/src/main/resources/logback.groovy @@ -1,46 +1,46 @@ - - -import ch.qos.logback.classic.encoder.PatternLayoutEncoder -import ch.qos.logback.classic.filter.ThresholdFilter -import ch.qos.logback.core.ConsoleAppender -import ch.qos.logback.core.util.Duration -import static ch.qos.logback.classic.Level.* - -if (System.getProperty("log.debug") != null) { - println "Logback configuration debugging enabled" - - statusListener(OnConsoleStatusListener) -} - -def LOG_LEVEL = toLevel(System.getProperty("log.level"), INFO) - -def haveBeagle = System.getProperty("log.beagle") != null -def logOps = System.getProperty("log.ops") != null - -appender("CONSOLE", ConsoleAppender) { - - filter(ThresholdFilter) { - level = toLevel(System.getProperty("log.level"), TRACE) - } - - encoder(PatternLayoutEncoder) { - pattern = "%d{HH:mm:ss.SSS} [%thread %file:%line] %-5level %logger{0} - %msg%n" - } -} - -def appenders = [ "CONSOLE" ] - -if (haveBeagle) { - appender("SOCKET", SocketAppender) { - includeCallerData = true - remoteHost = "localhost" - port = 4321 - reconnectionDelay = new Duration(10000) - } - - appenders += ["SOCKET"] -} - -root(LOG_LEVEL, appenders) - - + + +import ch.qos.logback.classic.encoder.PatternLayoutEncoder +import ch.qos.logback.classic.filter.ThresholdFilter +import ch.qos.logback.core.ConsoleAppender +import ch.qos.logback.core.util.Duration +import static ch.qos.logback.classic.Level.* + +if (System.getProperty("log.debug") != null) { + println "Logback configuration debugging enabled" + + statusListener(OnConsoleStatusListener) +} + +def LOG_LEVEL = toLevel(System.getProperty("log.level"), INFO) + +def haveBeagle = System.getProperty("log.beagle") != null +def logOps = System.getProperty("log.ops") != null + +appender("CONSOLE", ConsoleAppender) { + + filter(ThresholdFilter) { + level = toLevel(System.getProperty("log.level"), TRACE) + } + + encoder(PatternLayoutEncoder) { + pattern = "%d{HH:mm:ss.SSS} [%thread %file:%line] %-5level %logger{0} - %msg%n" + } +} + +def appenders = [ "CONSOLE" ] + +if (haveBeagle) { + appender("SOCKET", SocketAppender) { + includeCallerData = true + remoteHost = "localhost" + port = 4321 + reconnectionDelay = new Duration(10000) + } + + appenders += ["SOCKET"] +} + +root(LOG_LEVEL, appenders) + + diff --git a/meerkat-common/src/test/java/meerkat/comm/ChannelImpl.java b/meerkat-common/src/test/java/meerkat/comm/ChannelImpl.java new file mode 100644 index 0000000..cfe32c2 --- /dev/null +++ b/meerkat-common/src/test/java/meerkat/comm/ChannelImpl.java @@ -0,0 +1,96 @@ +package meerkat.comm; + +import com.google.protobuf.Message; +import com.google.protobuf.TextFormat; +import meerkat.protobuf.Comm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; +import java.util.TreeMap; +import java.util.concurrent.*; + +/** + * Created by Tzlil on 2/14/2016. + */ +// TODO: Change nane to network + +public class ChannelImpl { + final Logger logger = LoggerFactory.getLogger(getClass()); + + //private ExecutorService executorService = Executors.newCachedThreadPool(); + public static int BROADCAST = 0; + Map channels = new TreeMap<>(); + + + public ChannelImpl() { + } + + public Channel getChannel(int id) { + return new SingleChannel(id); + } + + public class SingleChannel implements Channel { + protected final int id; + + ReceiverCallback callback; + + SingleChannel(int id) { + this.id = id; + channels.put(id, this); + } + + @Override + public int getSourceId() { + return id; + } + + + @Override + public void sendMessage(int destUser, Message msg) { + if (destUser < 1) + return; + SingleChannel channel = channels.get(destUser); + if (channel == null) { + logger.warn("Party {} attempting to send message to non-existing party {}", getSourceId(), destUser); + return; + } + Comm.BroadcastMessage broadcastMessage = Comm.BroadcastMessage.newBuilder() + .setSender(id) + .setDestination(destUser) + .setIsPrivate(true) + .setPayload(msg.toByteString()) + .build(); + + logger.debug("sending Message: Dst={},Src={} [{}]", broadcastMessage.getDestination(), + broadcastMessage.getSender(), TextFormat.printToString(msg)); + + channel.callback.receiveMessage(broadcastMessage); + } + + @Override + public void broadcastMessage(Message msg) { + Comm.BroadcastMessage broadcastMessage = Comm.BroadcastMessage.newBuilder() + .setSender(id) + .setDestination(BROADCAST) + .setIsPrivate(false) + .setPayload(msg.toByteString()) + .build(); + + logger.debug("broadcasting Message: Src={} [{}]", + broadcastMessage.getSender(), TextFormat.printToString(msg)); + + for (SingleChannel channel : channels.values()) { + channel.callback.receiveMessage(broadcastMessage); + } + } + + @Override + public void registerReceiverCallback(final ReceiverCallback callback) { + this.callback = callback; + } + } + +} diff --git a/meerkat-common/src/test/java/meerkat/comm/MessageStreamTest.java b/meerkat-common/src/test/java/meerkat/comm/MessageStreamTest.java new file mode 100644 index 0000000..58dc7c1 --- /dev/null +++ b/meerkat-common/src/test/java/meerkat/comm/MessageStreamTest.java @@ -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 out; + MessageInputStream 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); + } + + } + +} diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSADeterministicSignatureTest.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSADeterministicSignatureTest.java index cc41497..4baa741 100644 --- a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSADeterministicSignatureTest.java +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSADeterministicSignatureTest.java @@ -3,7 +3,6 @@ package meerkat.crypto.concrete; import com.google.protobuf.ByteString; import com.google.protobuf.Message; -import meerkat.crypto.concrete.ECDSASignature; import meerkat.protobuf.Crypto; import org.junit.Test; diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java index 37c2d1b..e5b448d 100644 --- a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java @@ -3,7 +3,6 @@ package meerkat.crypto.concrete; import com.google.protobuf.ByteString; import com.google.protobuf.Message; import meerkat.protobuf.Crypto; -import meerkat.crypto.concrete.ECDSASignature; import org.junit.Before; import org.junit.Test; diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java index 66e1647..e57c817 100644 --- a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java @@ -26,6 +26,9 @@ import java.security.spec.InvalidKeySpecException; * utilities for ECElgamal */ public class ECElGamalUtils { + + private static GlobalCryptoSetup globalCryptoSetup = GlobalCryptoSetup.getInstance(); + final static Logger logger = LoggerFactory.getLogger(ECElGamalUtils.class); public final static String ENCRYPTION_KEY_ALGORITHM = "ECDH"; @@ -43,7 +46,7 @@ public class ECElGamalUtils { try { KeyFactory fact = KeyFactory.getInstance(ENCRYPTION_KEY_ALGORITHM, - GlobalCryptoSetup.getBouncyCastleProvider()); + globalCryptoSetup.getBouncyCastleProvider()); PublicKey javaPk = fact.generatePublic(pubKeySpec); ConcreteCrypto.ElGamalPublicKey serializedPk = ConcreteCrypto.ElGamalPublicKey.newBuilder() .setSubjectPublicKeyInfo(ByteString.copyFrom(javaPk.getEncoded())).build(); diff --git a/meerkat-common/src/test/resources/certs/README.md b/meerkat-common/src/test/resources/certs/README.md index f7283d9..ecb74d0 100644 --- a/meerkat-common/src/test/resources/certs/README.md +++ b/meerkat-common/src/test/resources/certs/README.md @@ -1,6 +1,6 @@ -Certs and private keys for testing generated using OpenSSL - -.crt and .pem files are in PEM format -.der files are in binary DER format - -files that have a name of the form *-with-password-xxxx.pem are encrypted with the password xxxx +Certs and private keys for testing generated using OpenSSL + +.crt and .pem files are in PEM format +.der files are in binary DER format + +files that have a name of the form *-with-password-xxxx.pem are encrypted with the password xxxx diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user1-key-with-password-secret.pem b/meerkat-common/src/test/resources/certs/enduser-certs/user1-key-with-password-secret.pem index e859995..dccf58d 100644 --- a/meerkat-common/src/test/resources/certs/enduser-certs/user1-key-with-password-secret.pem +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user1-key-with-password-secret.pem @@ -1,8 +1,8 @@ ------BEGIN EC PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-256-CBC,243D718A0D80C59590E582A26E87A49C - -RG6ITUTIdbJdWYX57oMn3tTCzHJSTjXAIZLjoVxy/v4UFYjluaFhGonIlbH1q2pP -ueu29Q3eT6144ypB8ARUJ1x0kRX1OL9zNHgdF9ulrCf9/nhGyC2nL+tHZ0YPbxoQ -+6yCQcRWvjUXLVzPEUnwMuHXJDpaXES8X0R4CISQKIA= ------END EC PRIVATE KEY----- +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,243D718A0D80C59590E582A26E87A49C + +RG6ITUTIdbJdWYX57oMn3tTCzHJSTjXAIZLjoVxy/v4UFYjluaFhGonIlbH1q2pP +ueu29Q3eT6144ypB8ARUJ1x0kRX1OL9zNHgdF9ulrCf9/nhGyC2nL+tHZ0YPbxoQ ++6yCQcRWvjUXLVzPEUnwMuHXJDpaXES8X0R4CISQKIA= +-----END EC PRIVATE KEY----- diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user1-key.pem b/meerkat-common/src/test/resources/certs/enduser-certs/user1-key.pem index 6619e37..239bd21 100644 --- a/meerkat-common/src/test/resources/certs/enduser-certs/user1-key.pem +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user1-key.pem @@ -1,5 +1,5 @@ ------BEGIN PRIVATE KEY----- -MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQge8JqCoaLoZq61aQki5Xm -GppcfAAkhHDGNQw/wLof5LmhRANCAAQJD1kW6BsNkRY9tslaugpOJOaoKX4uBz4S -Q96lPaPWkatNVgQchwNeB/hdjZwNuwE7A7XAwr69HFmhXRhsM005 ------END PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQge8JqCoaLoZq61aQki5Xm +GppcfAAkhHDGNQw/wLof5LmhRANCAAQJD1kW6BsNkRY9tslaugpOJOaoKX4uBz4S +Q96lPaPWkatNVgQchwNeB/hdjZwNuwE7A7XAwr69HFmhXRhsM005 +-----END PRIVATE KEY----- diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user1-pubkey.pem b/meerkat-common/src/test/resources/certs/enduser-certs/user1-pubkey.pem index 1c0a0c1..4724631 100644 --- a/meerkat-common/src/test/resources/certs/enduser-certs/user1-pubkey.pem +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user1-pubkey.pem @@ -1,4 +1,4 @@ ------BEGIN PUBLIC KEY----- -MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAECQ9ZFugbDZEWPbbJWroKTiTmqCl+Lgc+ -EkPepT2j1pGrTVYEHIcDXgf4XY2cDbsBOwO1wMK+vRxZoV0YbDNNOQ== ------END PUBLIC KEY----- +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAECQ9ZFugbDZEWPbbJWroKTiTmqCl+Lgc+ +EkPepT2j1pGrTVYEHIcDXgf4XY2cDbsBOwO1wMK+vRxZoV0YbDNNOQ== +-----END PUBLIC KEY----- diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user1.crt b/meerkat-common/src/test/resources/certs/enduser-certs/user1.crt index d80093f..e3d4843 100644 --- a/meerkat-common/src/test/resources/certs/enduser-certs/user1.crt +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user1.crt @@ -1,19 +1,19 @@ ------BEGIN CERTIFICATE----- -MIIDFjCCArygAwIBAgICEAAwCgYIKoZIzj0EAwIwgYIxKTAnBgNVBAMMIE1lZXJr -YXQgVm90aW5nIEludGVybWVkaWF0ZSBDQSAxMRMwEQYDVQQIDApTb21lLVN0YXRl -MQswCQYDVQQGEwJJTDEVMBMGA1UECgwMSURDIEhlcnpsaXlhMRwwGgYDVQQLDBNN -ZWVya2F0IFZvdGluZyBUZWFtMB4XDTE1MTExMTE2MTM1NFoXDTI1MTEwODE2MTM1 -NFowbjEaMBgGA1UEAwwRUG9sbGluZyBTdGF0aW9uIDExEzARBgNVBAgMClNvbWUt -U3RhdGUxCzAJBgNVBAYTAklMMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV -BAsMDk1lZXJrYXQgVm90aW5nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAECQ9ZFugb -DZEWPbbJWroKTiTmqCl+Lgc+EkPepT2j1pGrTVYEHIcDXgf4XY2cDbsBOwO1wMK+ -vRxZoV0YbDNNOaOCATYwggEyMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLamS8o2 -hFNd0vWy/irEBNWVNwFXMB8GA1UdIwQYMBaAFBeyv0c75eT6PNumHo9TZ2B9vtcp -MAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATBEBgNVHR8EPTA7MDmg -N6A1hjNodHRwOi8vY3JsLmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRp -YXRlMS5jcmwwegYIKwYBBQUHAQEEbjBsMEEGCCsGAQUFBzAChjVodHRwOi8vcGtp -LmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRpYXRlLWNhLmNydDAnBggr -BgEFBQcwAYYbaHR0cDovL29jc3AuZmFjdGNlbnRlci5vcmcvMAoGCCqGSM49BAMC -A0gAMEUCIQD6QbhNNmB3AVVqhmXuiLA7WF6raShw6n0g/VloVGQebQIgEvxYclpO -MMynt5wH6X65rtn4Q1EGaDMvNbFweCDsldk= ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDFjCCArygAwIBAgICEAAwCgYIKoZIzj0EAwIwgYIxKTAnBgNVBAMMIE1lZXJr +YXQgVm90aW5nIEludGVybWVkaWF0ZSBDQSAxMRMwEQYDVQQIDApTb21lLVN0YXRl +MQswCQYDVQQGEwJJTDEVMBMGA1UECgwMSURDIEhlcnpsaXlhMRwwGgYDVQQLDBNN +ZWVya2F0IFZvdGluZyBUZWFtMB4XDTE1MTExMTE2MTM1NFoXDTI1MTEwODE2MTM1 +NFowbjEaMBgGA1UEAwwRUG9sbGluZyBTdGF0aW9uIDExEzARBgNVBAgMClNvbWUt +U3RhdGUxCzAJBgNVBAYTAklMMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV +BAsMDk1lZXJrYXQgVm90aW5nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAECQ9ZFugb +DZEWPbbJWroKTiTmqCl+Lgc+EkPepT2j1pGrTVYEHIcDXgf4XY2cDbsBOwO1wMK+ +vRxZoV0YbDNNOaOCATYwggEyMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLamS8o2 +hFNd0vWy/irEBNWVNwFXMB8GA1UdIwQYMBaAFBeyv0c75eT6PNumHo9TZ2B9vtcp +MAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATBEBgNVHR8EPTA7MDmg +N6A1hjNodHRwOi8vY3JsLmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRp +YXRlMS5jcmwwegYIKwYBBQUHAQEEbjBsMEEGCCsGAQUFBzAChjVodHRwOi8vcGtp +LmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRpYXRlLWNhLmNydDAnBggr +BgEFBQcwAYYbaHR0cDovL29jc3AuZmFjdGNlbnRlci5vcmcvMAoGCCqGSM49BAMC +A0gAMEUCIQD6QbhNNmB3AVVqhmXuiLA7WF6raShw6n0g/VloVGQebQIgEvxYclpO +MMynt5wH6X65rtn4Q1EGaDMvNbFweCDsldk= +-----END CERTIFICATE----- diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user1.csr b/meerkat-common/src/test/resources/certs/enduser-certs/user1.csr index 20e1efc..0f59d09 100644 --- a/meerkat-common/src/test/resources/certs/enduser-certs/user1.csr +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user1.csr @@ -1,9 +1,9 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBOjCB4QIBADCBgTELMAkGA1UEBhMCSUwxEzARBgNVBAgMClNvbWUtU3RhdGUx -ETAPBgNVBAcMCEhlcnpsaXlhMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV -BAsMDk1lZXJrYXQgVm90aW5nMRowGAYDVQQDDBFQb2xsaW5nIFN0YXRpb24gMTBW -MBAGByqGSM49AgEGBSuBBAAKA0IABAkPWRboGw2RFj22yVq6Ck4k5qgpfi4HPhJD -3qU9o9aRq01WBByHA14H+F2NnA27ATsDtcDCvr0cWaFdGGwzTTmgADAKBggqhkjO -PQQDAgNIADBFAiEA8gmIhALr7O5M1QLReGH3jheildTIr1mDWl14WyMf9U4CIF23 -mInyo4VqNHLzxMLg5Cn3Oddokng3OXa63y4nTfv+ ------END CERTIFICATE REQUEST----- +-----BEGIN CERTIFICATE REQUEST----- +MIIBOjCB4QIBADCBgTELMAkGA1UEBhMCSUwxEzARBgNVBAgMClNvbWUtU3RhdGUx +ETAPBgNVBAcMCEhlcnpsaXlhMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV +BAsMDk1lZXJrYXQgVm90aW5nMRowGAYDVQQDDBFQb2xsaW5nIFN0YXRpb24gMTBW +MBAGByqGSM49AgEGBSuBBAAKA0IABAkPWRboGw2RFj22yVq6Ck4k5qgpfi4HPhJD +3qU9o9aRq01WBByHA14H+F2NnA27ATsDtcDCvr0cWaFdGGwzTTmgADAKBggqhkjO +PQQDAgNIADBFAiEA8gmIhALr7O5M1QLReGH3jheildTIr1mDWl14WyMf9U4CIF23 +mInyo4VqNHLzxMLg5Cn3Oddokng3OXa63y4nTfv+ +-----END CERTIFICATE REQUEST----- diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user2-key.pem b/meerkat-common/src/test/resources/certs/enduser-certs/user2-key.pem index 2d31bb8..1967905 100644 --- a/meerkat-common/src/test/resources/certs/enduser-certs/user2-key.pem +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user2-key.pem @@ -1,5 +1,5 @@ ------BEGIN PRIVATE KEY----- -MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgYpBEO+XWm/n6VPeMVK76 -mrZkDTpiwLsDykG7M4fU5RKhRANCAAR71/kVGyA3hdxcLBBT3NPQF6R3LholmLRN -qhnvHqzJWuy7ev+Xbuxtt9AN0ajyeFDy8Oe1bUSidnLyQi+nXC0f ------END PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgYpBEO+XWm/n6VPeMVK76 +mrZkDTpiwLsDykG7M4fU5RKhRANCAAR71/kVGyA3hdxcLBBT3NPQF6R3LholmLRN +qhnvHqzJWuy7ev+Xbuxtt9AN0ajyeFDy8Oe1bUSidnLyQi+nXC0f +-----END PRIVATE KEY----- diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user2-pubkey.pem b/meerkat-common/src/test/resources/certs/enduser-certs/user2-pubkey.pem index 5d86d4c..72f7acf 100644 --- a/meerkat-common/src/test/resources/certs/enduser-certs/user2-pubkey.pem +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user2-pubkey.pem @@ -1,4 +1,4 @@ ------BEGIN PUBLIC KEY----- -MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEe9f5FRsgN4XcXCwQU9zT0Bekdy4aJZi0 -TaoZ7x6syVrsu3r/l27sbbfQDdGo8nhQ8vDntW1EonZy8kIvp1wtHw== ------END PUBLIC KEY----- +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEe9f5FRsgN4XcXCwQU9zT0Bekdy4aJZi0 +TaoZ7x6syVrsu3r/l27sbbfQDdGo8nhQ8vDntW1EonZy8kIvp1wtHw== +-----END PUBLIC KEY----- diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user2.crt b/meerkat-common/src/test/resources/certs/enduser-certs/user2.crt index a211365..e618261 100644 --- a/meerkat-common/src/test/resources/certs/enduser-certs/user2.crt +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user2.crt @@ -1,19 +1,19 @@ ------BEGIN CERTIFICATE----- -MIIDFjCCArygAwIBAgICEAEwCgYIKoZIzj0EAwIwgYIxKTAnBgNVBAMMIE1lZXJr -YXQgVm90aW5nIEludGVybWVkaWF0ZSBDQSAxMRMwEQYDVQQIDApTb21lLVN0YXRl -MQswCQYDVQQGEwJJTDEVMBMGA1UECgwMSURDIEhlcnpsaXlhMRwwGgYDVQQLDBNN -ZWVya2F0IFZvdGluZyBUZWFtMB4XDTE1MTExMTE2MjAzM1oXDTI1MTEwODE2MjAz -M1owbjEaMBgGA1UEAwwRUG9sbGluZyBTdGF0aW9uIDIxEzARBgNVBAgMClNvbWUt -U3RhdGUxCzAJBgNVBAYTAklMMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV -BAsMDk1lZXJrYXQgVm90aW5nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEe9f5FRsg -N4XcXCwQU9zT0Bekdy4aJZi0TaoZ7x6syVrsu3r/l27sbbfQDdGo8nhQ8vDntW1E -onZy8kIvp1wtH6OCATYwggEyMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKCdquYj -DGHqAHt+4PIDlw0h2UvuMB8GA1UdIwQYMBaAFBeyv0c75eT6PNumHo9TZ2B9vtcp -MAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATBEBgNVHR8EPTA7MDmg -N6A1hjNodHRwOi8vY3JsLmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRp -YXRlMS5jcmwwegYIKwYBBQUHAQEEbjBsMEEGCCsGAQUFBzAChjVodHRwOi8vcGtp -LmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRpYXRlLWNhLmNydDAnBggr -BgEFBQcwAYYbaHR0cDovL29jc3AuZmFjdGNlbnRlci5vcmcvMAoGCCqGSM49BAMC -A0gAMEUCIQDpo5B0vvEJSax3YzOMfE8l0pfDUIKLdBWJVGeq0VLtIgIgVr0+4/0e -n+R+l1OVOLh2GirloOgbv5Ch5BQ2pQNAG2Y= ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDFjCCArygAwIBAgICEAEwCgYIKoZIzj0EAwIwgYIxKTAnBgNVBAMMIE1lZXJr +YXQgVm90aW5nIEludGVybWVkaWF0ZSBDQSAxMRMwEQYDVQQIDApTb21lLVN0YXRl +MQswCQYDVQQGEwJJTDEVMBMGA1UECgwMSURDIEhlcnpsaXlhMRwwGgYDVQQLDBNN +ZWVya2F0IFZvdGluZyBUZWFtMB4XDTE1MTExMTE2MjAzM1oXDTI1MTEwODE2MjAz +M1owbjEaMBgGA1UEAwwRUG9sbGluZyBTdGF0aW9uIDIxEzARBgNVBAgMClNvbWUt +U3RhdGUxCzAJBgNVBAYTAklMMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV +BAsMDk1lZXJrYXQgVm90aW5nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEe9f5FRsg +N4XcXCwQU9zT0Bekdy4aJZi0TaoZ7x6syVrsu3r/l27sbbfQDdGo8nhQ8vDntW1E +onZy8kIvp1wtH6OCATYwggEyMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKCdquYj +DGHqAHt+4PIDlw0h2UvuMB8GA1UdIwQYMBaAFBeyv0c75eT6PNumHo9TZ2B9vtcp +MAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATBEBgNVHR8EPTA7MDmg +N6A1hjNodHRwOi8vY3JsLmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRp +YXRlMS5jcmwwegYIKwYBBQUHAQEEbjBsMEEGCCsGAQUFBzAChjVodHRwOi8vcGtp +LmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRpYXRlLWNhLmNydDAnBggr +BgEFBQcwAYYbaHR0cDovL29jc3AuZmFjdGNlbnRlci5vcmcvMAoGCCqGSM49BAMC +A0gAMEUCIQDpo5B0vvEJSax3YzOMfE8l0pfDUIKLdBWJVGeq0VLtIgIgVr0+4/0e +n+R+l1OVOLh2GirloOgbv5Ch5BQ2pQNAG2Y= +-----END CERTIFICATE----- diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user2.csr b/meerkat-common/src/test/resources/certs/enduser-certs/user2.csr index bb3c2d0..774a0d3 100644 --- a/meerkat-common/src/test/resources/certs/enduser-certs/user2.csr +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user2.csr @@ -1,9 +1,9 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBOzCB4QIBADCBgTELMAkGA1UEBhMCSUwxEzARBgNVBAgMClNvbWUtU3RhdGUx -ETAPBgNVBAcMCEhlcnpsaXlhMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV -BAsMDk1lZXJrYXQgVm90aW5nMRowGAYDVQQDDBFQb2xsaW5nIFN0YXRpb24gMjBW -MBAGByqGSM49AgEGBSuBBAAKA0IABHvX+RUbIDeF3FwsEFPc09AXpHcuGiWYtE2q -Ge8erMla7Lt6/5du7G230A3RqPJ4UPLw57VtRKJ2cvJCL6dcLR+gADAKBggqhkjO -PQQDAgNJADBGAiEA6Ls/ojRaZT+u4YeOBYcPbRcJE3jSTe1Sm/lR7fDyEhMCIQCk -UOca+e2b8+CqM3CURBv6TqUMmZ3HeMRvEAxFPqOWSw== ------END CERTIFICATE REQUEST----- +-----BEGIN CERTIFICATE REQUEST----- +MIIBOzCB4QIBADCBgTELMAkGA1UEBhMCSUwxEzARBgNVBAgMClNvbWUtU3RhdGUx +ETAPBgNVBAcMCEhlcnpsaXlhMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV +BAsMDk1lZXJrYXQgVm90aW5nMRowGAYDVQQDDBFQb2xsaW5nIFN0YXRpb24gMjBW +MBAGByqGSM49AgEGBSuBBAAKA0IABHvX+RUbIDeF3FwsEFPc09AXpHcuGiWYtE2q +Ge8erMla7Lt6/5du7G230A3RqPJ4UPLw57VtRKJ2cvJCL6dcLR+gADAKBggqhkjO +PQQDAgNJADBGAiEA6Ls/ojRaZT+u4YeOBYcPbRcJE3jSTe1Sm/lR7fDyEhMCIQCk +UOca+e2b8+CqM3CURBv6TqUMmZ3HeMRvEAxFPqOWSw== +-----END CERTIFICATE REQUEST----- diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user3-key-with-password-shh.p12 b/meerkat-common/src/test/resources/certs/enduser-certs/user3-key-with-password-shh.p12 new file mode 100644 index 0000000..c62cee1 Binary files /dev/null and b/meerkat-common/src/test/resources/certs/enduser-certs/user3-key-with-password-shh.p12 differ diff --git a/meerkat-common/src/test/resources/certs/enduser-certs/user3.crt b/meerkat-common/src/test/resources/certs/enduser-certs/user3.crt new file mode 100644 index 0000000..23ee857 --- /dev/null +++ b/meerkat-common/src/test/resources/certs/enduser-certs/user3.crt @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE----- +MIIBGjCBw6ADAgECAgEBMAkGByqGSM49BAEwEDEOMAwGA1UEAxMFY2VydDEwHhcN +MTUxMTI4MTEwNDAwWhcNMTYxMTI4MTEwNDAwWjAQMQ4wDAYDVQQDEwVjZXJ0MTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABLiyFMVWQtFi4fCjOGLDwQcdjyr48Y8j +P+eLEIGMYKKv8bqL3Vchs0iOPoyGH6jxYj2/ShnLSIEuIMPfVgV9kxSjDzANMAsG +A1UdDwQEAwIHgDAJBgcqhkjOPQQBA0cAMEQCIH7R0AWO0AYiHOs+QsHEpWiebFc1 +cyxCKJGkf8KA1KJrAiArCia7PWl0KzaqA0RQC4J0BKp4rZo1PCqKI8DirKQf/Q== +-----END CERTIFICATE----- diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/1000.pem b/meerkat-common/src/test/resources/certs/intermediate-ca-1/1000.pem index d80093f..e3d4843 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/1000.pem +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/1000.pem @@ -1,19 +1,19 @@ ------BEGIN CERTIFICATE----- -MIIDFjCCArygAwIBAgICEAAwCgYIKoZIzj0EAwIwgYIxKTAnBgNVBAMMIE1lZXJr -YXQgVm90aW5nIEludGVybWVkaWF0ZSBDQSAxMRMwEQYDVQQIDApTb21lLVN0YXRl -MQswCQYDVQQGEwJJTDEVMBMGA1UECgwMSURDIEhlcnpsaXlhMRwwGgYDVQQLDBNN -ZWVya2F0IFZvdGluZyBUZWFtMB4XDTE1MTExMTE2MTM1NFoXDTI1MTEwODE2MTM1 -NFowbjEaMBgGA1UEAwwRUG9sbGluZyBTdGF0aW9uIDExEzARBgNVBAgMClNvbWUt -U3RhdGUxCzAJBgNVBAYTAklMMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV -BAsMDk1lZXJrYXQgVm90aW5nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAECQ9ZFugb -DZEWPbbJWroKTiTmqCl+Lgc+EkPepT2j1pGrTVYEHIcDXgf4XY2cDbsBOwO1wMK+ -vRxZoV0YbDNNOaOCATYwggEyMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLamS8o2 -hFNd0vWy/irEBNWVNwFXMB8GA1UdIwQYMBaAFBeyv0c75eT6PNumHo9TZ2B9vtcp -MAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATBEBgNVHR8EPTA7MDmg -N6A1hjNodHRwOi8vY3JsLmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRp -YXRlMS5jcmwwegYIKwYBBQUHAQEEbjBsMEEGCCsGAQUFBzAChjVodHRwOi8vcGtp -LmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRpYXRlLWNhLmNydDAnBggr -BgEFBQcwAYYbaHR0cDovL29jc3AuZmFjdGNlbnRlci5vcmcvMAoGCCqGSM49BAMC -A0gAMEUCIQD6QbhNNmB3AVVqhmXuiLA7WF6raShw6n0g/VloVGQebQIgEvxYclpO -MMynt5wH6X65rtn4Q1EGaDMvNbFweCDsldk= ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDFjCCArygAwIBAgICEAAwCgYIKoZIzj0EAwIwgYIxKTAnBgNVBAMMIE1lZXJr +YXQgVm90aW5nIEludGVybWVkaWF0ZSBDQSAxMRMwEQYDVQQIDApTb21lLVN0YXRl +MQswCQYDVQQGEwJJTDEVMBMGA1UECgwMSURDIEhlcnpsaXlhMRwwGgYDVQQLDBNN +ZWVya2F0IFZvdGluZyBUZWFtMB4XDTE1MTExMTE2MTM1NFoXDTI1MTEwODE2MTM1 +NFowbjEaMBgGA1UEAwwRUG9sbGluZyBTdGF0aW9uIDExEzARBgNVBAgMClNvbWUt +U3RhdGUxCzAJBgNVBAYTAklMMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV +BAsMDk1lZXJrYXQgVm90aW5nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAECQ9ZFugb +DZEWPbbJWroKTiTmqCl+Lgc+EkPepT2j1pGrTVYEHIcDXgf4XY2cDbsBOwO1wMK+ +vRxZoV0YbDNNOaOCATYwggEyMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLamS8o2 +hFNd0vWy/irEBNWVNwFXMB8GA1UdIwQYMBaAFBeyv0c75eT6PNumHo9TZ2B9vtcp +MAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATBEBgNVHR8EPTA7MDmg +N6A1hjNodHRwOi8vY3JsLmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRp +YXRlMS5jcmwwegYIKwYBBQUHAQEEbjBsMEEGCCsGAQUFBzAChjVodHRwOi8vcGtp +LmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRpYXRlLWNhLmNydDAnBggr +BgEFBQcwAYYbaHR0cDovL29jc3AuZmFjdGNlbnRlci5vcmcvMAoGCCqGSM49BAMC +A0gAMEUCIQD6QbhNNmB3AVVqhmXuiLA7WF6raShw6n0g/VloVGQebQIgEvxYclpO +MMynt5wH6X65rtn4Q1EGaDMvNbFweCDsldk= +-----END CERTIFICATE----- diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/1001.pem b/meerkat-common/src/test/resources/certs/intermediate-ca-1/1001.pem index a211365..e618261 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/1001.pem +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/1001.pem @@ -1,19 +1,19 @@ ------BEGIN CERTIFICATE----- -MIIDFjCCArygAwIBAgICEAEwCgYIKoZIzj0EAwIwgYIxKTAnBgNVBAMMIE1lZXJr -YXQgVm90aW5nIEludGVybWVkaWF0ZSBDQSAxMRMwEQYDVQQIDApTb21lLVN0YXRl -MQswCQYDVQQGEwJJTDEVMBMGA1UECgwMSURDIEhlcnpsaXlhMRwwGgYDVQQLDBNN -ZWVya2F0IFZvdGluZyBUZWFtMB4XDTE1MTExMTE2MjAzM1oXDTI1MTEwODE2MjAz -M1owbjEaMBgGA1UEAwwRUG9sbGluZyBTdGF0aW9uIDIxEzARBgNVBAgMClNvbWUt -U3RhdGUxCzAJBgNVBAYTAklMMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV -BAsMDk1lZXJrYXQgVm90aW5nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEe9f5FRsg -N4XcXCwQU9zT0Bekdy4aJZi0TaoZ7x6syVrsu3r/l27sbbfQDdGo8nhQ8vDntW1E -onZy8kIvp1wtH6OCATYwggEyMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKCdquYj -DGHqAHt+4PIDlw0h2UvuMB8GA1UdIwQYMBaAFBeyv0c75eT6PNumHo9TZ2B9vtcp -MAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATBEBgNVHR8EPTA7MDmg -N6A1hjNodHRwOi8vY3JsLmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRp -YXRlMS5jcmwwegYIKwYBBQUHAQEEbjBsMEEGCCsGAQUFBzAChjVodHRwOi8vcGtp -LmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRpYXRlLWNhLmNydDAnBggr -BgEFBQcwAYYbaHR0cDovL29jc3AuZmFjdGNlbnRlci5vcmcvMAoGCCqGSM49BAMC -A0gAMEUCIQDpo5B0vvEJSax3YzOMfE8l0pfDUIKLdBWJVGeq0VLtIgIgVr0+4/0e -n+R+l1OVOLh2GirloOgbv5Ch5BQ2pQNAG2Y= ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDFjCCArygAwIBAgICEAEwCgYIKoZIzj0EAwIwgYIxKTAnBgNVBAMMIE1lZXJr +YXQgVm90aW5nIEludGVybWVkaWF0ZSBDQSAxMRMwEQYDVQQIDApTb21lLVN0YXRl +MQswCQYDVQQGEwJJTDEVMBMGA1UECgwMSURDIEhlcnpsaXlhMRwwGgYDVQQLDBNN +ZWVya2F0IFZvdGluZyBUZWFtMB4XDTE1MTExMTE2MjAzM1oXDTI1MTEwODE2MjAz +M1owbjEaMBgGA1UEAwwRUG9sbGluZyBTdGF0aW9uIDIxEzARBgNVBAgMClNvbWUt +U3RhdGUxCzAJBgNVBAYTAklMMRUwEwYDVQQKDAxJREMgSGVyemxpeWExFzAVBgNV +BAsMDk1lZXJrYXQgVm90aW5nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEe9f5FRsg +N4XcXCwQU9zT0Bekdy4aJZi0TaoZ7x6syVrsu3r/l27sbbfQDdGo8nhQ8vDntW1E +onZy8kIvp1wtH6OCATYwggEyMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKCdquYj +DGHqAHt+4PIDlw0h2UvuMB8GA1UdIwQYMBaAFBeyv0c75eT6PNumHo9TZ2B9vtcp +MAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATBEBgNVHR8EPTA7MDmg +N6A1hjNodHRwOi8vY3JsLmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRp +YXRlMS5jcmwwegYIKwYBBQUHAQEEbjBsMEEGCCsGAQUFBzAChjVodHRwOi8vcGtp +LmZhY3RjZW50ZXIub3JnL21lZXJrYXQtaW50ZXJtZWRpYXRlLWNhLmNydDAnBggr +BgEFBQcwAYYbaHR0cDovL29jc3AuZmFjdGNlbnRlci5vcmcvMAoGCCqGSM49BAMC +A0gAMEUCIQDpo5B0vvEJSax3YzOMfE8l0pfDUIKLdBWJVGeq0VLtIgIgVr0+4/0e +n+R+l1OVOLh2GirloOgbv5Ch5BQ2pQNAG2Y= +-----END CERTIFICATE----- diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex index 1cd80cf..72b4dde 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex @@ -1,2 +1,2 @@ -V 251108161354Z 1000 unknown /CN=Polling Station 1/ST=Some-State/C=IL/O=IDC Herzliya/OU=Meerkat Voting -V 251108162033Z 1001 unknown /CN=Polling Station 2/ST=Some-State/C=IL/O=IDC Herzliya/OU=Meerkat Voting +V 251108161354Z 1000 unknown /CN=Polling Station 1/ST=Some-State/C=IL/O=IDC Herzliya/OU=Meerkat Voting +V 251108162033Z 1001 unknown /CN=Polling Station 2/ST=Some-State/C=IL/O=IDC Herzliya/OU=Meerkat Voting diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.attr b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.attr index 3a7e39e..f77ac48 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.attr +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.attr @@ -1 +1 @@ -unique_subject = no +unique_subject = no diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.attr.old b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.attr.old index 3a7e39e..f77ac48 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.attr.old +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.attr.old @@ -1 +1 @@ -unique_subject = no +unique_subject = no diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.old b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.old index 7dcd55e..60f290b 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.old +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certindex.old @@ -1 +1 @@ -V 251108161354Z 1000 unknown /CN=Polling Station 1/ST=Some-State/C=IL/O=IDC Herzliya/OU=Meerkat Voting +V 251108161354Z 1000 unknown /CN=Polling Station 1/ST=Some-State/C=IL/O=IDC Herzliya/OU=Meerkat Voting diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certserial b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certserial index 7d802a3..a9b8266 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certserial +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certserial @@ -1 +1 @@ -1002 +1002 diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certserial.old b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certserial.old index dd11724..ecc680e 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/certserial.old +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/certserial.old @@ -1 +1 @@ -1001 +1001 diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/crlnumber b/meerkat-common/src/test/resources/certs/intermediate-ca-1/crlnumber index 83b33d2..d5b0eb5 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/crlnumber +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/crlnumber @@ -1 +1 @@ -1000 +1000 diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1-private-key.pem b/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1-private-key.pem index cf0a641..5c678f2 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1-private-key.pem +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1-private-key.pem @@ -1,5 +1,5 @@ ------BEGIN PRIVATE KEY----- -MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgJvMhOfcQfdK/42QlBbri -IYXLM/gVHq/yppOykDqB3s6hRANCAAQoShAtCGW5c9pk/4/sKN1qjCgDKngqJpba -kku6cIDqXDr+aHsl+/KdSHd46OI3fEynl+/Pc85wRsaY6Z7b1PdS ------END PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgJvMhOfcQfdK/42QlBbri +IYXLM/gVHq/yppOykDqB3s6hRANCAAQoShAtCGW5c9pk/4/sKN1qjCgDKngqJpba +kku6cIDqXDr+aHsl+/KdSHd46OI3fEynl+/Pc85wRsaY6Z7b1PdS +-----END PRIVATE KEY----- diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1.crt b/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1.crt index 751d8e7..dda53dc 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1.crt +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1.crt @@ -1,21 +1,21 @@ ------BEGIN CERTIFICATE----- -MIIDfDCCAyGgAwIBAgICEAAwCgYIKoZIzj0EAwIwgbAxCzAJBgNVBAYTAklMMRMw -EQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhIZXJ6bGl5YTEUMBIGA1UECgwL -SURDIEhlcmxpeWExHzAdBgNVBAsMFk1lZXJrYXQgVm90aW5nIFByb2plY3QxGDAW -BgNVBAMMD1Rlc3RpbmcgUm9vdCBDQTEoMCYGCSqGSIb3DQEJARYZdGVzdGluZy1j -YUBmYWN0Y2VudGVyLm9yZzAeFw0xNTExMTExNjA4MDJaFw0yNTExMDgxNjA4MDJa -MIGCMSkwJwYDVQQDDCBNZWVya2F0IFZvdGluZyBJbnRlcm1lZGlhdGUgQ0EgMTET -MBEGA1UECAwKU29tZS1TdGF0ZTELMAkGA1UEBhMCSUwxFTATBgNVBAoMDElEQyBI -ZXJ6bGl5YTEcMBoGA1UECwwTTWVlcmthdCBWb3RpbmcgVGVhbTBWMBAGByqGSM49 -AgEGBSuBBAAKA0IABChKEC0IZblz2mT/j+wo3WqMKAMqeComltqSS7pwgOpcOv5o -eyX78p1Id3jo4jd8TKeX789zznBGxpjpntvU91KjggFYMIIBVDAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBQXsr9HO+Xk+jzbph6PU2dgfb7XKTAfBgNVHSMEGDAW -gBSJD9L1fLmX4A9CBoLsYXn3OPy1ojALBgNVHQ8EBAMCAaYwEwYDVR0lBAwwCgYI -KwYBBQUHAwEwPgYDVR0fBDcwNTAzoDGgL4YtaHR0cDovL2NybC5mYWN0Y2VudGVy -Lm9yZy9tZWVya2F0LXJvb3QtY2EuY3JsMCsGA1UdEQQkMCKCIE1lZXJrYXQgVm90 -aW5nIEludGVybWVkaWF0ZSBDQSAxMHIGCCsGAQUFBwEBBGYwZDA5BggrBgEFBQcw -AoYtaHR0cDovL3BraS5mYWN0Y2VudGVyLm9yZy9tZWVya2F0LXJvb3QtY2EuY3J0 -MCcGCCsGAQUFBzABhhtodHRwOi8vb2NzcC5mYWN0Y2VudGVyLm9yZy8wCgYIKoZI -zj0EAwIDSQAwRgIhALEMHq2ssC9rLXiG8v6NcZetwwxdu3B3LW9s0KeGoNIEAiEA -skA56tMnhiZe38msyanRyRrAHyBI2fGs6GP3UBrg2P8= ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDfDCCAyGgAwIBAgICEAAwCgYIKoZIzj0EAwIwgbAxCzAJBgNVBAYTAklMMRMw +EQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhIZXJ6bGl5YTEUMBIGA1UECgwL +SURDIEhlcmxpeWExHzAdBgNVBAsMFk1lZXJrYXQgVm90aW5nIFByb2plY3QxGDAW +BgNVBAMMD1Rlc3RpbmcgUm9vdCBDQTEoMCYGCSqGSIb3DQEJARYZdGVzdGluZy1j +YUBmYWN0Y2VudGVyLm9yZzAeFw0xNTExMTExNjA4MDJaFw0yNTExMDgxNjA4MDJa +MIGCMSkwJwYDVQQDDCBNZWVya2F0IFZvdGluZyBJbnRlcm1lZGlhdGUgQ0EgMTET +MBEGA1UECAwKU29tZS1TdGF0ZTELMAkGA1UEBhMCSUwxFTATBgNVBAoMDElEQyBI +ZXJ6bGl5YTEcMBoGA1UECwwTTWVlcmthdCBWb3RpbmcgVGVhbTBWMBAGByqGSM49 +AgEGBSuBBAAKA0IABChKEC0IZblz2mT/j+wo3WqMKAMqeComltqSS7pwgOpcOv5o +eyX78p1Id3jo4jd8TKeX789zznBGxpjpntvU91KjggFYMIIBVDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQXsr9HO+Xk+jzbph6PU2dgfb7XKTAfBgNVHSMEGDAW +gBSJD9L1fLmX4A9CBoLsYXn3OPy1ojALBgNVHQ8EBAMCAaYwEwYDVR0lBAwwCgYI +KwYBBQUHAwEwPgYDVR0fBDcwNTAzoDGgL4YtaHR0cDovL2NybC5mYWN0Y2VudGVy +Lm9yZy9tZWVya2F0LXJvb3QtY2EuY3JsMCsGA1UdEQQkMCKCIE1lZXJrYXQgVm90 +aW5nIEludGVybWVkaWF0ZSBDQSAxMHIGCCsGAQUFBwEBBGYwZDA5BggrBgEFBQcw +AoYtaHR0cDovL3BraS5mYWN0Y2VudGVyLm9yZy9tZWVya2F0LXJvb3QtY2EuY3J0 +MCcGCCsGAQUFBzABhhtodHRwOi8vb2NzcC5mYWN0Y2VudGVyLm9yZy8wCgYIKoZI +zj0EAwIDSQAwRgIhALEMHq2ssC9rLXiG8v6NcZetwwxdu3B3LW9s0KeGoNIEAiEA +skA56tMnhiZe38msyanRyRrAHyBI2fGs6GP3UBrg2P8= +-----END CERTIFICATE----- diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1.csr b/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1.csr index fce3021..55f66c1 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1.csr +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/intermediate-ca-1.csr @@ -1,10 +1,10 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBTTCB9QIBADCBlTELMAkGA1UEBhMCSUwxEzARBgNVBAgMClNvbWUtU3RhdGUx -ETAPBgNVBAcMCEhlcnpsaXlhMRUwEwYDVQQKDAxJREMgSGVyemxpeWExHDAaBgNV -BAsME01lZXJrYXQgVm90aW5nIFRlYW0xKTAnBgNVBAMMIE1lZXJrYXQgVm90aW5n -IEludGVybWVkaWF0ZSBDQSAxMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEKEoQLQhl -uXPaZP+P7CjdaowoAyp4KiaW2pJLunCA6lw6/mh7JfvynUh3eOjiN3xMp5fvz3PO -cEbGmOme29T3UqAAMAoGCCqGSM49BAMCA0cAMEQCIFlyJO5NFqnMUu5hOlQa872E -yy0V3zkqeN6Aly+LtEQqAiAfHwbi1lkJOZT2tOX8gfJzcac2jKmbgIhmITNq7uma -Wg== ------END CERTIFICATE REQUEST----- +-----BEGIN CERTIFICATE REQUEST----- +MIIBTTCB9QIBADCBlTELMAkGA1UEBhMCSUwxEzARBgNVBAgMClNvbWUtU3RhdGUx +ETAPBgNVBAcMCEhlcnpsaXlhMRUwEwYDVQQKDAxJREMgSGVyemxpeWExHDAaBgNV +BAsME01lZXJrYXQgVm90aW5nIFRlYW0xKTAnBgNVBAMMIE1lZXJrYXQgVm90aW5n +IEludGVybWVkaWF0ZSBDQSAxMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEKEoQLQhl +uXPaZP+P7CjdaowoAyp4KiaW2pJLunCA6lw6/mh7JfvynUh3eOjiN3xMp5fvz3PO +cEbGmOme29T3UqAAMAoGCCqGSM49BAMCA0cAMEQCIFlyJO5NFqnMUu5hOlQa872E +yy0V3zkqeN6Aly+LtEQqAiAfHwbi1lkJOZT2tOX8gfJzcac2jKmbgIhmITNq7uma +Wg== +-----END CERTIFICATE REQUEST----- diff --git a/meerkat-common/src/test/resources/certs/intermediate-ca-1/openssl-intermediate-ca.conf b/meerkat-common/src/test/resources/certs/intermediate-ca-1/openssl-intermediate-ca.conf index 090ca1a..aa44c33 100644 --- a/meerkat-common/src/test/resources/certs/intermediate-ca-1/openssl-intermediate-ca.conf +++ b/meerkat-common/src/test/resources/certs/intermediate-ca-1/openssl-intermediate-ca.conf @@ -1,46 +1,46 @@ -[ ca ] -default_ca = myca - -[ crl_ext ] -issuerAltName=issuer:copy -authorityKeyIdentifier=keyid:always - - [ myca ] - dir = ./ - new_certs_dir = $dir - unique_subject = no - certificate = $dir/intermediate-ca-1.crt - database = $dir/certindex - private_key = $dir/intermediate-ca-1-private-key.pem - serial = $dir/certserial - default_days = 3650 - default_md = sha256 - policy = myca_policy - x509_extensions = myca_extensions - crlnumber = $dir/crlnumber - default_crl_days = 3650 - - [ myca_policy ] - commonName = supplied - stateOrProvinceName = optional - countryName = optional - emailAddress = optional - organizationName = supplied - organizationalUnitName = optional - - [ myca_extensions ] - basicConstraints = critical,CA:FALSE - keyUsage = critical,any - subjectKeyIdentifier = hash - authorityKeyIdentifier = keyid:always,issuer - keyUsage = digitalSignature,keyEncipherment - extendedKeyUsage = serverAuth - crlDistributionPoints = @crl_section - authorityInfoAccess = @ocsp_section - - [crl_section] - URI.0 = http://crl.factcenter.org/meerkat-intermediate1.crl - - [ocsp_section] - caIssuers;URI.0 = http://pki.factcenter.org/meerkat-intermediate-ca.crt - OCSP;URI.0 = http://ocsp.factcenter.org/ +[ ca ] +default_ca = myca + +[ crl_ext ] +issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + + [ myca ] + dir = ./ + new_certs_dir = $dir + unique_subject = no + certificate = $dir/intermediate-ca-1.crt + database = $dir/certindex + private_key = $dir/intermediate-ca-1-private-key.pem + serial = $dir/certserial + default_days = 3650 + default_md = sha256 + policy = myca_policy + x509_extensions = myca_extensions + crlnumber = $dir/crlnumber + default_crl_days = 3650 + + [ myca_policy ] + commonName = supplied + stateOrProvinceName = optional + countryName = optional + emailAddress = optional + organizationName = supplied + organizationalUnitName = optional + + [ myca_extensions ] + basicConstraints = critical,CA:FALSE + keyUsage = critical,any + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid:always,issuer + keyUsage = digitalSignature,keyEncipherment + extendedKeyUsage = serverAuth + crlDistributionPoints = @crl_section + authorityInfoAccess = @ocsp_section + + [crl_section] + URI.0 = http://crl.factcenter.org/meerkat-intermediate1.crl + + [ocsp_section] + caIssuers;URI.0 = http://pki.factcenter.org/meerkat-intermediate-ca.crt + OCSP;URI.0 = http://ocsp.factcenter.org/ diff --git a/meerkat-common/src/test/resources/certs/root-ca/1000.pem b/meerkat-common/src/test/resources/certs/root-ca/1000.pem index 751d8e7..dda53dc 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/1000.pem +++ b/meerkat-common/src/test/resources/certs/root-ca/1000.pem @@ -1,21 +1,21 @@ ------BEGIN CERTIFICATE----- -MIIDfDCCAyGgAwIBAgICEAAwCgYIKoZIzj0EAwIwgbAxCzAJBgNVBAYTAklMMRMw -EQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhIZXJ6bGl5YTEUMBIGA1UECgwL -SURDIEhlcmxpeWExHzAdBgNVBAsMFk1lZXJrYXQgVm90aW5nIFByb2plY3QxGDAW -BgNVBAMMD1Rlc3RpbmcgUm9vdCBDQTEoMCYGCSqGSIb3DQEJARYZdGVzdGluZy1j -YUBmYWN0Y2VudGVyLm9yZzAeFw0xNTExMTExNjA4MDJaFw0yNTExMDgxNjA4MDJa -MIGCMSkwJwYDVQQDDCBNZWVya2F0IFZvdGluZyBJbnRlcm1lZGlhdGUgQ0EgMTET -MBEGA1UECAwKU29tZS1TdGF0ZTELMAkGA1UEBhMCSUwxFTATBgNVBAoMDElEQyBI -ZXJ6bGl5YTEcMBoGA1UECwwTTWVlcmthdCBWb3RpbmcgVGVhbTBWMBAGByqGSM49 -AgEGBSuBBAAKA0IABChKEC0IZblz2mT/j+wo3WqMKAMqeComltqSS7pwgOpcOv5o -eyX78p1Id3jo4jd8TKeX789zznBGxpjpntvU91KjggFYMIIBVDAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBQXsr9HO+Xk+jzbph6PU2dgfb7XKTAfBgNVHSMEGDAW -gBSJD9L1fLmX4A9CBoLsYXn3OPy1ojALBgNVHQ8EBAMCAaYwEwYDVR0lBAwwCgYI -KwYBBQUHAwEwPgYDVR0fBDcwNTAzoDGgL4YtaHR0cDovL2NybC5mYWN0Y2VudGVy -Lm9yZy9tZWVya2F0LXJvb3QtY2EuY3JsMCsGA1UdEQQkMCKCIE1lZXJrYXQgVm90 -aW5nIEludGVybWVkaWF0ZSBDQSAxMHIGCCsGAQUFBwEBBGYwZDA5BggrBgEFBQcw -AoYtaHR0cDovL3BraS5mYWN0Y2VudGVyLm9yZy9tZWVya2F0LXJvb3QtY2EuY3J0 -MCcGCCsGAQUFBzABhhtodHRwOi8vb2NzcC5mYWN0Y2VudGVyLm9yZy8wCgYIKoZI -zj0EAwIDSQAwRgIhALEMHq2ssC9rLXiG8v6NcZetwwxdu3B3LW9s0KeGoNIEAiEA -skA56tMnhiZe38msyanRyRrAHyBI2fGs6GP3UBrg2P8= ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDfDCCAyGgAwIBAgICEAAwCgYIKoZIzj0EAwIwgbAxCzAJBgNVBAYTAklMMRMw +EQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhIZXJ6bGl5YTEUMBIGA1UECgwL +SURDIEhlcmxpeWExHzAdBgNVBAsMFk1lZXJrYXQgVm90aW5nIFByb2plY3QxGDAW +BgNVBAMMD1Rlc3RpbmcgUm9vdCBDQTEoMCYGCSqGSIb3DQEJARYZdGVzdGluZy1j +YUBmYWN0Y2VudGVyLm9yZzAeFw0xNTExMTExNjA4MDJaFw0yNTExMDgxNjA4MDJa +MIGCMSkwJwYDVQQDDCBNZWVya2F0IFZvdGluZyBJbnRlcm1lZGlhdGUgQ0EgMTET +MBEGA1UECAwKU29tZS1TdGF0ZTELMAkGA1UEBhMCSUwxFTATBgNVBAoMDElEQyBI +ZXJ6bGl5YTEcMBoGA1UECwwTTWVlcmthdCBWb3RpbmcgVGVhbTBWMBAGByqGSM49 +AgEGBSuBBAAKA0IABChKEC0IZblz2mT/j+wo3WqMKAMqeComltqSS7pwgOpcOv5o +eyX78p1Id3jo4jd8TKeX789zznBGxpjpntvU91KjggFYMIIBVDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQXsr9HO+Xk+jzbph6PU2dgfb7XKTAfBgNVHSMEGDAW +gBSJD9L1fLmX4A9CBoLsYXn3OPy1ojALBgNVHQ8EBAMCAaYwEwYDVR0lBAwwCgYI +KwYBBQUHAwEwPgYDVR0fBDcwNTAzoDGgL4YtaHR0cDovL2NybC5mYWN0Y2VudGVy +Lm9yZy9tZWVya2F0LXJvb3QtY2EuY3JsMCsGA1UdEQQkMCKCIE1lZXJrYXQgVm90 +aW5nIEludGVybWVkaWF0ZSBDQSAxMHIGCCsGAQUFBwEBBGYwZDA5BggrBgEFBQcw +AoYtaHR0cDovL3BraS5mYWN0Y2VudGVyLm9yZy9tZWVya2F0LXJvb3QtY2EuY3J0 +MCcGCCsGAQUFBzABhhtodHRwOi8vb2NzcC5mYWN0Y2VudGVyLm9yZy8wCgYIKoZI +zj0EAwIDSQAwRgIhALEMHq2ssC9rLXiG8v6NcZetwwxdu3B3LW9s0KeGoNIEAiEA +skA56tMnhiZe38msyanRyRrAHyBI2fGs6GP3UBrg2P8= +-----END CERTIFICATE----- diff --git a/meerkat-common/src/test/resources/certs/root-ca/certindex b/meerkat-common/src/test/resources/certs/root-ca/certindex index e4550ca..ec8e90d 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/certindex +++ b/meerkat-common/src/test/resources/certs/root-ca/certindex @@ -1 +1 @@ -V 251108160802Z 1000 unknown /CN=Meerkat Voting Intermediate CA 1/ST=Some-State/C=IL/O=IDC Herzliya/OU=Meerkat Voting Team +V 251108160802Z 1000 unknown /CN=Meerkat Voting Intermediate CA 1/ST=Some-State/C=IL/O=IDC Herzliya/OU=Meerkat Voting Team diff --git a/meerkat-common/src/test/resources/certs/root-ca/certindex.attr b/meerkat-common/src/test/resources/certs/root-ca/certindex.attr index 3a7e39e..f77ac48 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/certindex.attr +++ b/meerkat-common/src/test/resources/certs/root-ca/certindex.attr @@ -1 +1 @@ -unique_subject = no +unique_subject = no diff --git a/meerkat-common/src/test/resources/certs/root-ca/certserial b/meerkat-common/src/test/resources/certs/root-ca/certserial index dd11724..ecc680e 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/certserial +++ b/meerkat-common/src/test/resources/certs/root-ca/certserial @@ -1 +1 @@ -1001 +1001 diff --git a/meerkat-common/src/test/resources/certs/root-ca/certserial.old b/meerkat-common/src/test/resources/certs/root-ca/certserial.old index 83b33d2..d5b0eb5 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/certserial.old +++ b/meerkat-common/src/test/resources/certs/root-ca/certserial.old @@ -1 +1 @@ -1000 +1000 diff --git a/meerkat-common/src/test/resources/certs/root-ca/crlnumber b/meerkat-common/src/test/resources/certs/root-ca/crlnumber index 83b33d2..d5b0eb5 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/crlnumber +++ b/meerkat-common/src/test/resources/certs/root-ca/crlnumber @@ -1 +1 @@ -1000 +1000 diff --git a/meerkat-common/src/test/resources/certs/root-ca/openssl-ca.conf b/meerkat-common/src/test/resources/certs/root-ca/openssl-ca.conf index 39e8b00..e94692e 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/openssl-ca.conf +++ b/meerkat-common/src/test/resources/certs/root-ca/openssl-ca.conf @@ -1,61 +1,61 @@ -[ ca ] -default_ca = myca - -[ crl_ext ] -issuerAltName=issuer:copy -authorityKeyIdentifier=keyid:always - - [ myca ] - dir = ./ - new_certs_dir = $dir - unique_subject = no - certificate = $dir/root-ca.crt - database = $dir/certindex - private_key = $dir/root-ca-private-key.pem - serial = $dir/certserial - default_days = 3650 - default_md = sha256 - policy = myca_policy - x509_extensions = myca_extensions - crlnumber = $dir/crlnumber - default_crl_days = 3650 - - [ myca_policy ] - commonName = supplied - stateOrProvinceName = optional - countryName = optional - emailAddress = optional - organizationName = supplied - organizationalUnitName = optional - - [ myca_extensions ] - basicConstraints = critical,CA:TRUE - keyUsage = critical,any - subjectKeyIdentifier = hash - authorityKeyIdentifier = keyid:always,issuer - keyUsage = digitalSignature,keyEncipherment,cRLSign,keyCertSign - extendedKeyUsage = serverAuth - crlDistributionPoints = @crl_section - subjectAltName = @alt_names - authorityInfoAccess = @ocsp_section - - [ v3_ca ] - basicConstraints = critical,CA:TRUE,pathlen:0 - keyUsage = critical,any - subjectKeyIdentifier = hash - authorityKeyIdentifier = keyid:always,issuer - keyUsage = digitalSignature,keyEncipherment,cRLSign,keyCertSign - extendedKeyUsage = serverAuth - crlDistributionPoints = @crl_section - subjectAltName = @alt_names - authorityInfoAccess = @ocsp_section - - [alt_names] - DNS.0 = Meerkat Voting Intermediate CA 1 - - [crl_section] - URI.0 = http://crl.factcenter.org/meerkat-root-ca.crl - - [ocsp_section] - caIssuers;URI.0 = http://pki.factcenter.org/meerkat-root-ca.crt - OCSP;URI.0 = http://ocsp.factcenter.org/ +[ ca ] +default_ca = myca + +[ crl_ext ] +issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + + [ myca ] + dir = ./ + new_certs_dir = $dir + unique_subject = no + certificate = $dir/root-ca.crt + database = $dir/certindex + private_key = $dir/root-ca-private-key.pem + serial = $dir/certserial + default_days = 3650 + default_md = sha256 + policy = myca_policy + x509_extensions = myca_extensions + crlnumber = $dir/crlnumber + default_crl_days = 3650 + + [ myca_policy ] + commonName = supplied + stateOrProvinceName = optional + countryName = optional + emailAddress = optional + organizationName = supplied + organizationalUnitName = optional + + [ myca_extensions ] + basicConstraints = critical,CA:TRUE + keyUsage = critical,any + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid:always,issuer + keyUsage = digitalSignature,keyEncipherment,cRLSign,keyCertSign + extendedKeyUsage = serverAuth + crlDistributionPoints = @crl_section + subjectAltName = @alt_names + authorityInfoAccess = @ocsp_section + + [ v3_ca ] + basicConstraints = critical,CA:TRUE,pathlen:0 + keyUsage = critical,any + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid:always,issuer + keyUsage = digitalSignature,keyEncipherment,cRLSign,keyCertSign + extendedKeyUsage = serverAuth + crlDistributionPoints = @crl_section + subjectAltName = @alt_names + authorityInfoAccess = @ocsp_section + + [alt_names] + DNS.0 = Meerkat Voting Intermediate CA 1 + + [crl_section] + URI.0 = http://crl.factcenter.org/meerkat-root-ca.crl + + [ocsp_section] + caIssuers;URI.0 = http://pki.factcenter.org/meerkat-root-ca.crt + OCSP;URI.0 = http://ocsp.factcenter.org/ diff --git a/meerkat-common/src/test/resources/certs/root-ca/root-ca-private-key-with-password-secret.pem b/meerkat-common/src/test/resources/certs/root-ca/root-ca-private-key-with-password-secret.pem index a0c442d..2801557 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/root-ca-private-key-with-password-secret.pem +++ b/meerkat-common/src/test/resources/certs/root-ca/root-ca-private-key-with-password-secret.pem @@ -1,8 +1,8 @@ ------BEGIN EC PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-256-CBC,B8CA131346FD6C9568A6C80935F2AF14 - -8q1seEln39/tQTo5KqN+qNRhd0fQ0oC71dYpfTHsP0NlNmjMtwKo2niFwzjxnSyP -vpJjGzUlnq30ucbeJA7CDm/1cmYAU5gGQ7gldgpi2TQVS+EBjqi/Y5P9AlrgLv6K -tKe4AvkqQcpi4ZvlUL9xmNaM9jEH4syopR9YClEMfa8= ------END EC PRIVATE KEY----- +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,B8CA131346FD6C9568A6C80935F2AF14 + +8q1seEln39/tQTo5KqN+qNRhd0fQ0oC71dYpfTHsP0NlNmjMtwKo2niFwzjxnSyP +vpJjGzUlnq30ucbeJA7CDm/1cmYAU5gGQ7gldgpi2TQVS+EBjqi/Y5P9AlrgLv6K +tKe4AvkqQcpi4ZvlUL9xmNaM9jEH4syopR9YClEMfa8= +-----END EC PRIVATE KEY----- diff --git a/meerkat-common/src/test/resources/certs/root-ca/root-ca-private-key.pem b/meerkat-common/src/test/resources/certs/root-ca/root-ca-private-key.pem index 7966a28..7176b2a 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/root-ca-private-key.pem +++ b/meerkat-common/src/test/resources/certs/root-ca/root-ca-private-key.pem @@ -1,5 +1,5 @@ ------BEGIN EC PRIVATE KEY----- -MHQCAQEEIEi9y6pSKu1kDZcIfQQAnojl1iFxm32W0DVCp2P6HRrkoAcGBSuBBAAK -oUQDQgAEoijIYF12bpA0tcjyQnWZGQ4lzdBGR+hK/5al/M+zFgFwvWHoWf6yJsSB -ymviB5yUaH+cE+/3LXlGbpRzYKLBYQ== ------END EC PRIVATE KEY----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIEi9y6pSKu1kDZcIfQQAnojl1iFxm32W0DVCp2P6HRrkoAcGBSuBBAAK +oUQDQgAEoijIYF12bpA0tcjyQnWZGQ4lzdBGR+hK/5al/M+zFgFwvWHoWf6yJsSB +ymviB5yUaH+cE+/3LXlGbpRzYKLBYQ== +-----END EC PRIVATE KEY----- diff --git a/meerkat-common/src/test/resources/certs/root-ca/root-ca.crt b/meerkat-common/src/test/resources/certs/root-ca/root-ca.crt index d0bd1fa..e884528 100644 --- a/meerkat-common/src/test/resources/certs/root-ca/root-ca.crt +++ b/meerkat-common/src/test/resources/certs/root-ca/root-ca.crt @@ -1,17 +1,17 @@ ------BEGIN CERTIFICATE----- -MIICpTCCAkygAwIBAgIJAJoVb07aGgNaMAoGCCqGSM49BAMCMIGwMQswCQYDVQQG -EwJJTDETMBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UEBwwISGVyemxpeWExFDAS -BgNVBAoMC0lEQyBIZXJsaXlhMR8wHQYDVQQLDBZNZWVya2F0IFZvdGluZyBQcm9q -ZWN0MRgwFgYDVQQDDA9UZXN0aW5nIFJvb3QgQ0ExKDAmBgkqhkiG9w0BCQEWGXRl -c3RpbmctY2FAZmFjdGNlbnRlci5vcmcwHhcNMTUxMTExMTUzODE4WhcNMjUxMTA4 -MTUzODE4WjCBsDELMAkGA1UEBhMCSUwxEzARBgNVBAgMClNvbWUtU3RhdGUxETAP -BgNVBAcMCEhlcnpsaXlhMRQwEgYDVQQKDAtJREMgSGVybGl5YTEfMB0GA1UECwwW -TWVlcmthdCBWb3RpbmcgUHJvamVjdDEYMBYGA1UEAwwPVGVzdGluZyBSb290IENB -MSgwJgYJKoZIhvcNAQkBFhl0ZXN0aW5nLWNhQGZhY3RjZW50ZXIub3JnMFYwEAYH -KoZIzj0CAQYFK4EEAAoDQgAEoijIYF12bpA0tcjyQnWZGQ4lzdBGR+hK/5al/M+z -FgFwvWHoWf6yJsSBymviB5yUaH+cE+/3LXlGbpRzYKLBYaNQME4wHQYDVR0OBBYE -FIkP0vV8uZfgD0IGguxhefc4/LWiMB8GA1UdIwQYMBaAFIkP0vV8uZfgD0IGguxh -efc4/LWiMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgNftHrW30Git8 -VFQKyMCkasauSpEHpAGdcRAhRHqUQMUCIDxw++trz/Iv8818xVB1ARr9EQAmH0aC -7MHETGuiBC7L ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICpTCCAkygAwIBAgIJAJoVb07aGgNaMAoGCCqGSM49BAMCMIGwMQswCQYDVQQG +EwJJTDETMBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UEBwwISGVyemxpeWExFDAS +BgNVBAoMC0lEQyBIZXJsaXlhMR8wHQYDVQQLDBZNZWVya2F0IFZvdGluZyBQcm9q +ZWN0MRgwFgYDVQQDDA9UZXN0aW5nIFJvb3QgQ0ExKDAmBgkqhkiG9w0BCQEWGXRl +c3RpbmctY2FAZmFjdGNlbnRlci5vcmcwHhcNMTUxMTExMTUzODE4WhcNMjUxMTA4 +MTUzODE4WjCBsDELMAkGA1UEBhMCSUwxEzARBgNVBAgMClNvbWUtU3RhdGUxETAP +BgNVBAcMCEhlcnpsaXlhMRQwEgYDVQQKDAtJREMgSGVybGl5YTEfMB0GA1UECwwW +TWVlcmthdCBWb3RpbmcgUHJvamVjdDEYMBYGA1UEAwwPVGVzdGluZyBSb290IENB +MSgwJgYJKoZIhvcNAQkBFhl0ZXN0aW5nLWNhQGZhY3RjZW50ZXIub3JnMFYwEAYH +KoZIzj0CAQYFK4EEAAoDQgAEoijIYF12bpA0tcjyQnWZGQ4lzdBGR+hK/5al/M+z +FgFwvWHoWf6yJsSBymviB5yUaH+cE+/3LXlGbpRzYKLBYaNQME4wHQYDVR0OBBYE +FIkP0vV8uZfgD0IGguxhefc4/LWiMB8GA1UdIwQYMBaAFIkP0vV8uZfgD0IGguxh +efc4/LWiMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgNftHrW30Git8 +VFQKyMCkasauSpEHpAGdcRAhRHqUQMUCIDxw++trz/Iv8818xVB1ARr9EQAmH0aC +7MHETGuiBC7L +-----END CERTIFICATE----- diff --git a/meerkat-common/src/test/resources/certs/secp256k1.pem b/meerkat-common/src/test/resources/certs/secp256k1.pem index 32d952e..d707107 100644 --- a/meerkat-common/src/test/resources/certs/secp256k1.pem +++ b/meerkat-common/src/test/resources/certs/secp256k1.pem @@ -1,3 +1,3 @@ ------BEGIN EC PARAMETERS----- -BgUrgQQACg== ------END EC PARAMETERS----- +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- diff --git a/polling-station/build.gradle b/polling-station/build.gradle index 34fe58d..4635668 100644 --- a/polling-station/build.gradle +++ b/polling-station/build.gradle @@ -1,194 +1,202 @@ - -plugins { - id "us.kirchmeier.capsule" version "1.0.1" - id 'com.google.protobuf' version '0.7.0' -} - -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' - -apply plugin: 'maven-publish' - -// Uncomment the lines below to define an application -// (this will also allow you to build a "fatCapsule" which includes -// the entire application, including all dependencies in a single jar) -//apply plugin: 'application' -//mainClassName='your.main.ApplicationClass' - - -// Is this a snapshot version? -ext { isSnapshot = false } - -ext { - groupId = 'org.factcenter.meerkat' - nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" - - // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) - // Should be set in ${HOME}/.gradle/gradle.properties - nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" - nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" -} - -description = "Meerkat polling-station application" - -// Your project version -version = "0.0" - -version += "${isSnapshot ? '-SNAPSHOT' : ''}" - - -dependencies { - // Meerkat common - compile project(':meerkat-common') - - // Logging - compile 'org.slf4j:slf4j-api:1.7.7' - runtime 'ch.qos.logback:logback-classic:1.1.2' - runtime 'ch.qos.logback:logback-core:1.1.2' - - // Google protobufs - compile 'com.google.protobuf:protobuf-java:3.+' - - testCompile 'junit:junit:4.+' - - runtime 'org.codehaus.groovy:groovy:2.4.+' -} - - -/*==== You probably don't have to edit below this line =======*/ - -protobuf { - // Configure the protoc executable - protoc { - // Download from repositories - artifact = 'com.google.protobuf:protoc:3.+' - } -} - - -idea { - module { - project.sourceSets.each { sourceSet -> - - def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" - - // add protobuf generated sources to generated source dir. - if ("test".equals(sourceSet.name)) { - testSourceDirs += file(srcDir) - } else { - sourceDirs += file(srcDir) - } - generatedSourceDirs += file(srcDir) - - } - - // Don't exclude build directory - excludeDirs -= file(buildDir) - } -} - -/*=================================== - * "Fat" Build targets - *===================================*/ - - -if (project.hasProperty('mainClassName') && (mainClassName != null)) { - - task mavenCapsule(type: MavenCapsule) { - description = "Generate a capsule jar that automatically downloads and caches dependencies when run." - applicationClass mainClassName - destinationDir = buildDir - } - - task fatCapsule(type: FatCapsule) { - description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" - - destinationDir = buildDir - - def fatMain = hasProperty('fatmain') ? fatmain : mainClassName - - applicationClass fatMain - - def testJar = hasProperty('test') - - if (hasProperty('fatmain')) { - appendix = "fat-${fatMain}" - } else { - appendix = "fat" - } - - if (testJar) { - from sourceSets.test.output - } - } -} - -/*=================================== - * Repositories - *===================================*/ - -repositories { - - // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) - maven { - url nexusRepository - - if (isSnapshot) { - credentials { username - password - - username nexusUser - password nexusPassword - } - } - } - - // Use local maven repository - mavenLocal() - - // Use 'maven central' for other dependencies. - mavenCentral() -} - -task "info" << { - println "Project: ${project.name}" -println "Description: ${project.description}" - println "--------------------------" - println "GroupId: $groupId" - println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" - println "" -} -info.description 'Print some information about project parameters' - - -/*=================================== - * Publishing - *===================================*/ - -publishing { - publications { - mavenJava(MavenPublication) { - groupId project.groupId - pom.withXml { - asNode().appendNode('description', project.description) - } - from project.components.java - - } - } - repositories { - maven { - url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" - credentials { username - password - - username nexusUser - password nexusPassword - } - } - } -} - - - + +plugins { + id "us.kirchmeier.capsule" version "1.0.1" + id 'com.google.protobuf' version '0.7.0' +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' + +apply plugin: 'maven-publish' + +// Uncomment the lines below to define an application +// (this will also allow you to build a "fatCapsule" which includes +// the entire application, including all dependencies in a single jar) +//apply plugin: 'application' +//mainClassName='your.main.ApplicationClass' + + +// Is this a snapshot version? +ext { isSnapshot = false } + +ext { + groupId = 'org.factcenter.meerkat' + nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" + + // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) + // Should be set in ${HOME}/.gradle/gradle.properties + nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" + nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" +} + +description = "Meerkat polling-station application" + +// Your project version +version = "0.0" + +version += "${isSnapshot ? '-SNAPSHOT' : ''}" + + +dependencies { + // Meerkat common + compile project(':meerkat-common') + compile project(':restful-api-common') + + // Jersey for RESTful API + compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.5.+' + + // Servlets + compile 'org.eclipse.jetty:jetty-server:9.3.+' + compile 'org.eclipse.jetty:jetty-servlet:9.3.+' + + // Logging + compile 'org.slf4j:slf4j-api:1.7.7' + runtime 'ch.qos.logback:logback-classic:1.1.2' + runtime 'ch.qos.logback:logback-core:1.1.2' + + // Google protobufs + compile 'com.google.protobuf:protobuf-java:3.+' + + testCompile 'junit:junit:4.+' + + runtime 'org.codehaus.groovy:groovy:2.4.+' +} + + +/*==== You probably don't have to edit below this line =======*/ + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.+' + } +} + + +idea { + module { + project.sourceSets.each { sourceSet -> + + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) + } +} + +/*=================================== + * "Fat" Build targets + *===================================*/ + + +if (project.hasProperty('mainClassName') && (mainClassName != null)) { + + task mavenCapsule(type: MavenCapsule) { + description = "Generate a capsule jar that automatically downloads and caches dependencies when run." + applicationClass mainClassName + destinationDir = buildDir + } + + task fatCapsule(type: FatCapsule) { + description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" + + destinationDir = buildDir + + def fatMain = hasProperty('fatmain') ? fatmain : mainClassName + + applicationClass fatMain + + def testJar = hasProperty('test') + + if (hasProperty('fatmain')) { + appendix = "fat-${fatMain}" + } else { + appendix = "fat" + } + + if (testJar) { + from sourceSets.test.output + } + } +} + +/*=================================== + * Repositories + *===================================*/ + +repositories { + + // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) + maven { + url nexusRepository + + if (isSnapshot) { + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } + + // Use local maven repository + mavenLocal() + + // Use 'maven central' for other dependencies. + mavenCentral() +} + +task "info" << { + println "Project: ${project.name}" +println "Description: ${project.description}" + println "--------------------------" + println "GroupId: $groupId" + println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" + println "" +} +info.description 'Print some information about project parameters' + + +/*=================================== + * Publishing + *===================================*/ + +publishing { + publications { + mavenJava(MavenPublication) { + groupId project.groupId + pom.withXml { + asNode().appendNode('description', project.description) + } + from project.components.java + + } + } + repositories { + maven { + url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } +} + + + diff --git a/polling-station/src/main/java/meerkat/pollingstation/PollingStationScannerWebApp.java b/polling-station/src/main/java/meerkat/pollingstation/PollingStationScannerWebApp.java new file mode 100644 index 0000000..327c774 --- /dev/null +++ b/polling-station/src/main/java/meerkat/pollingstation/PollingStationScannerWebApp.java @@ -0,0 +1,88 @@ +package meerkat.pollingstation; + +/** + * Created by Arbel on 5/31/2016. + */ + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.BoolValue; +import meerkat.protobuf.PollingStation; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletContext; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import java.io.IOException; + +import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_ERROR_PATH; +import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_SCAN_PATH; +import static meerkat.rest.Constants.MEDIATYPE_PROTOBUF; + +/** + * Implements a Web-App interface for {@link meerkat.pollingstation.PollingStationScanner.Producer} + * This class depends on {@link meerkat.pollingstation.PollingStationWebScanner} and works in conjunction with it + */ +@Path("/") +public class PollingStationScannerWebApp implements PollingStationScanner.Producer { + + @Context + ServletContext servletContext; + + Iterable> callbacks; + + /** + * This method is called by the Jetty engine when instantiating the servlet + */ + @PostConstruct + public void init() throws Exception{ + callbacks = (Iterable>) servletContext.getAttribute(PollingStationWebScanner.CALLBACKS_ATTRIBUTE_NAME); + } + + @POST + @Path(POLLING_STATION_WEB_SCANNER_SCAN_PATH) + @Consumes(MEDIATYPE_PROTOBUF) + @Produces(MEDIATYPE_PROTOBUF) + @Override + public BoolValue newScan(PollingStation.ScannedData scannedData) { + + boolean handled = false; + + for (FutureCallback callback : callbacks){ + + callback.onSuccess(scannedData); + handled = true; + + } + + return BoolValue.newBuilder() + .setValue(handled) + .build(); + + } + + @POST + @Path(POLLING_STATION_WEB_SCANNER_ERROR_PATH) + @Consumes(MEDIATYPE_PROTOBUF) + @Produces(MEDIATYPE_PROTOBUF) + @Override + public BoolValue reportScanError(PollingStation.ErrorMsg errorMsg) { + + boolean handled = false; + + for (FutureCallback callback : callbacks){ + + callback.onFailure(new IOException(errorMsg.getMsg())); + handled = true; + + } + + return BoolValue.newBuilder() + .setValue(handled) + .build(); + + } + +} diff --git a/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java b/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java new file mode 100644 index 0000000..a4e290a --- /dev/null +++ b/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java @@ -0,0 +1,60 @@ +package meerkat.pollingstation; + +import java.util.List; +import java.util.LinkedList; + +import com.google.common.util.concurrent.FutureCallback; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.*; + +import org.glassfish.jersey.servlet.ServletContainer; +import org.glassfish.jersey.server.ResourceConfig; + +import meerkat.protobuf.PollingStation.ScannedData; +import meerkat.rest.*; + +/** + * Created by Arbel on 05/05/2016. + */ + +public class PollingStationWebScanner implements PollingStationScanner.Consumer{ + + public final static String CALLBACKS_ATTRIBUTE_NAME = "callbacks"; + + private final Server server; + private final List> callbacks; + + public PollingStationWebScanner(int port, String subAddress) { + + callbacks = new LinkedList<>(); + + server = new Server(port); + + ServletContextHandler servletContextHandler = new ServletContextHandler(server, subAddress); + servletContextHandler.setAttribute(CALLBACKS_ATTRIBUTE_NAME, (Iterable>) callbacks); + + ResourceConfig resourceConfig = new ResourceConfig(PollingStationScannerWebApp.class); + resourceConfig.register(ProtobufMessageBodyReader.class); + resourceConfig.register(ProtobufMessageBodyWriter.class); + + ServletHolder servletHolder = new ServletHolder(new ServletContainer(resourceConfig)); + + servletContextHandler.addServlet(servletHolder, "/*"); + } + + @Override + public void start() throws Exception { + server.start(); + } + + @Override + public void stop() throws Exception { + server.stop(); + } + + @Override + public void subscribe(FutureCallback scanCallback) { + callbacks.add(scanCallback); + } + +} diff --git a/polling-station/src/test/java/meerkat/pollingstation/PollingStationWebScannerTest.java b/polling-station/src/test/java/meerkat/pollingstation/PollingStationWebScannerTest.java new file mode 100644 index 0000000..7da106c --- /dev/null +++ b/polling-station/src/test/java/meerkat/pollingstation/PollingStationWebScannerTest.java @@ -0,0 +1,162 @@ +package meerkat.pollingstation; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.ByteString; +import meerkat.protobuf.PollingStation.*; +import meerkat.rest.Constants; + +import meerkat.rest.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +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.concurrent.Semaphore; + +import static meerkat.pollingstation.PollingStationConstants.*; + +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Created by Arbel on 25/05/2016. + */ +public class PollingStationWebScannerTest { + + private PollingStationScanner.Consumer scanner; + private static final String ADDRESS = "http://localhost"; + private static final String SUB_ADDRESS = ""; + private static final int PORT = 8080; + + private Semaphore semaphore; + private Throwable thrown; + private boolean dataIsAsExpected; + + private class ScanHandler implements FutureCallback { + + private final ScannedData expectedData; + + public ScanHandler(ScannedData expectedData) { + this.expectedData = expectedData; + } + + @Override + public void onSuccess(ScannedData result) { + dataIsAsExpected = result.getData().equals(expectedData.getData()); + semaphore.release(); + } + + @Override + public void onFailure(Throwable t) { + dataIsAsExpected = false; + thrown = t; + semaphore.release(); + } + } + + private class ErrorHandler implements FutureCallback { + + private final String expectedErrorMessage; + + public ErrorHandler(String expectedErrorMessage) { + this.expectedErrorMessage = expectedErrorMessage; + } + + @Override + public void onSuccess(ScannedData result) { + dataIsAsExpected = false; + semaphore.release(); + } + + @Override + public void onFailure(Throwable t) { + dataIsAsExpected = t.getMessage().equals(expectedErrorMessage); + semaphore.release(); + } + } + + @Before + public void init() { + + System.err.println("Setting up Scanner WebApp!"); + + scanner = new PollingStationWebScanner(PORT, SUB_ADDRESS); + + semaphore = new Semaphore(0); + thrown = null; + + try { + scanner.start(); + } catch (Exception e) { + assertThat("Could not start server: " + e.getMessage(), false); + } + + } + + @Test + public void testSuccessfulScan() throws InterruptedException { + + Client client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + WebTarget webTarget = client.target(ADDRESS + ":" + PORT) + .path(SUB_ADDRESS).path(POLLING_STATION_WEB_SCANNER_SCAN_PATH); + + byte[] data = {(byte) 1, (byte) 2}; + + ScannedData scannedData = ScannedData.newBuilder() + .setData(ByteString.copyFrom(data)) + .build(); + + scanner.subscribe(new ScanHandler(scannedData)); + + Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(scannedData, Constants.MEDIATYPE_PROTOBUF)); + response.close(); + + semaphore.acquire(); + assertThat("Scanner has thrown an error", thrown == null); + assertThat("Scanned data received was incorrect", dataIsAsExpected); + + } + + @Test + public void testErroneousScan() throws InterruptedException { + + Client client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + WebTarget webTarget = client.target(ADDRESS + ":" + PORT) + .path(SUB_ADDRESS).path(POLLING_STATION_WEB_SCANNER_ERROR_PATH); + + ErrorMsg errorMsg = ErrorMsg.newBuilder() + .setMsg("!Error Message!") + .build(); + + scanner.subscribe(new ErrorHandler(errorMsg.getMsg())); + + Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(errorMsg, Constants.MEDIATYPE_PROTOBUF)); + response.close(); + + semaphore.acquire(); + assertThat("Scanner error received was incorrect", dataIsAsExpected); + + } + + @After + public void close() { + + System.err.println("Scanner WebApp shutting down..."); + + try { + scanner.stop(); + } catch (Exception e) { + assertThat("Could not stop server: " + e.getMessage(), false); + } + + } + +} \ No newline at end of file diff --git a/restful-api-common/.gitignore b/restful-api-common/.gitignore index ae3c172..3e2fcc7 100644 --- a/restful-api-common/.gitignore +++ b/restful-api-common/.gitignore @@ -1 +1 @@ -/bin/ +/bin/ diff --git a/restful-api-common/build.gradle b/restful-api-common/build.gradle index 3c0ad1e..483790e 100644 --- a/restful-api-common/build.gradle +++ b/restful-api-common/build.gradle @@ -1,160 +1,160 @@ - -plugins { - id "us.kirchmeier.capsule" version "1.0.1" - id 'com.google.protobuf' version '0.7.0' -} - -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' - -// Uncomment both lines below to define an application (must set mainClassName -//apply plugin: 'application' -//mainClassName='your.main.ApplicationClass' - -apply plugin: 'maven-publish' - -// Is this a snapshot version? -ext { isSnapshot = false } - -ext { - groupId = 'org.factcenter.meerkat' - nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" - - // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) - // Should be set in ${HOME}/.gradle/gradle.properties - nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" - nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" -} - -description = "Common classes for implementing Meerkat's RESTful API" - -// Your project version -version = "0.0.1" - -version += "${isSnapshot ? '-SNAPSHOT' : ''}" - - -dependencies { - // Meerkat common - compile project(':meerkat-common') - - // Jersey for RESTful API - compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.22.+' - - // Logging - compile 'org.slf4j:slf4j-api:1.7.7' - runtime 'ch.qos.logback:logback-classic:1.1.2' - runtime 'ch.qos.logback:logback-core:1.1.2' - - // Google protobufs - compile 'com.google.protobuf:protobuf-java:3.+' - - testCompile 'junit:junit:4.+' - - runtime 'org.codehaus.groovy:groovy:2.4.+' -} - - -/*==== You probably don't have to edit below this line =======*/ - -protobuf { - // Configure the protoc executable - protoc { - // Download from repositories - artifact = 'com.google.protobuf:protoc:3.+' - } -} - - -idea { - module { - project.sourceSets.each { sourceSet -> - - def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" - - // add protobuf generated sources to generated source dir. - if ("test".equals(sourceSet.name)) { - testSourceDirs += file(srcDir) - } else { - sourceDirs += file(srcDir) - } - generatedSourceDirs += file(srcDir) - - } - - // Don't exclude build directory - excludeDirs -= file(buildDir) - } -} - - - -/*=================================== - * Repositories - *===================================*/ - -repositories { - - // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) - maven { - url nexusRepository - - if (isSnapshot) { - credentials { username - password - - username nexusUser - password nexusPassword - } - } - } - - // Use local maven repository - mavenLocal() - - // Use 'maven central' for other dependencies. - mavenCentral() -} - -task "info" << { - println "Project: ${project.name}" -println "Description: ${project.description}" - println "--------------------------" - println "GroupId: $groupId" - println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" - println "" -} -info.description 'Print some information about project parameters' - - -/*=================================== - * Publishing - *===================================*/ - -publishing { - publications { - mavenJava(MavenPublication) { - groupId project.groupId - pom.withXml { - asNode().appendNode('description', project.description) - } - from project.components.java - - } - } - repositories { - maven { - url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" - credentials { username - password - - username nexusUser - password nexusPassword - } - } - } -} - - - + +plugins { + id "us.kirchmeier.capsule" version "1.0.1" + id 'com.google.protobuf' version '0.7.0' +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' + +// Uncomment both lines below to define an application (must set mainClassName +//apply plugin: 'application' +//mainClassName='your.main.ApplicationClass' + +apply plugin: 'maven-publish' + +// Is this a snapshot version? +ext { isSnapshot = false } + +ext { + groupId = 'org.factcenter.meerkat' + nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" + + // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) + // Should be set in ${HOME}/.gradle/gradle.properties + nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" + nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" +} + +description = "Common classes for implementing Meerkat's RESTful API" + +// Your project version +version = "0.0.1" + +version += "${isSnapshot ? '-SNAPSHOT' : ''}" + + +dependencies { + // Meerkat common + compile project(':meerkat-common') + + // Jersey for RESTful API + compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.22.+' + + // Logging + compile 'org.slf4j:slf4j-api:1.7.7' + runtime 'ch.qos.logback:logback-classic:1.1.2' + runtime 'ch.qos.logback:logback-core:1.1.2' + + // Google protobufs + compile 'com.google.protobuf:protobuf-java:3.+' + + testCompile 'junit:junit:4.+' + + runtime 'org.codehaus.groovy:groovy:2.4.+' +} + + +/*==== You probably don't have to edit below this line =======*/ + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.+' + } +} + + +idea { + module { + project.sourceSets.each { sourceSet -> + + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) + } +} + + + +/*=================================== + * Repositories + *===================================*/ + +repositories { + + // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) + maven { + url nexusRepository + + if (isSnapshot) { + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } + + // Use local maven repository + mavenLocal() + + // Use 'maven central' for other dependencies. + mavenCentral() +} + +task "info" << { + println "Project: ${project.name}" +println "Description: ${project.description}" + println "--------------------------" + println "GroupId: $groupId" + println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" + println "" +} +info.description 'Print some information about project parameters' + + +/*=================================== + * Publishing + *===================================*/ + +publishing { + publications { + mavenJava(MavenPublication) { + groupId project.groupId + pom.withXml { + asNode().appendNode('description', project.description) + } + from project.components.java + + } + } + repositories { + maven { + url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } +} + + + diff --git a/restful-api-common/src/main/java/meerkat/rest/Constants.java b/restful-api-common/src/main/java/meerkat/rest/Constants.java index 73ed7d1..e3e469a 100644 --- a/restful-api-common/src/main/java/meerkat/rest/Constants.java +++ b/restful-api-common/src/main/java/meerkat/rest/Constants.java @@ -1,12 +1,8 @@ -package meerkat.rest; - -/** - * Created by talm on 10/11/15. - */ -public interface Constants { - public static final String MEDIATYPE_PROTOBUF = "application/x-protobuf"; - - public static final String BULLETIN_BOARD_SERVER_PATH = "/bbserver"; - public static final String READ_MESSAGES_PATH = "/readmessages"; - public static final String POST_MESSAGE_PATH = "/postmessage"; -} +package meerkat.rest; + +/** + * Created by talm on 10/11/15. + */ +public interface Constants { + public static final String MEDIATYPE_PROTOBUF = "application/x-protobuf"; +} diff --git a/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyReader.java b/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyReader.java index d09e213..a139e17 100644 --- a/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyReader.java +++ b/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyReader.java @@ -1,41 +1,41 @@ -package meerkat.rest; - -import com.google.protobuf.GeneratedMessage; -import com.google.protobuf.Message; - - -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyReader; -import javax.ws.rs.ext.Provider; -import java.io.IOException; -import java.io.InputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.Type; - -import static meerkat.rest.Constants.*; - -@Provider -@Consumes(MEDIATYPE_PROTOBUF) -public class ProtobufMessageBodyReader implements MessageBodyReader { - @Override - public boolean isReadable(Class type, Type genericType, Annotation[] annotations, - MediaType mediaType) { - return Message.class.isAssignableFrom(type); - } - - @Override - public Message readFrom(Class type, Type genericType, Annotation[] annotations, - MediaType mediaType, MultivaluedMap httpHeaders, - InputStream entityStream) throws IOException, WebApplicationException { - try { - Method newBuilder = type.getMethod("newBuilder"); - GeneratedMessage.Builder builder = (GeneratedMessage.Builder) newBuilder.invoke(type); - return builder.mergeFrom(entityStream).build(); - } catch (Exception e) { - throw new WebApplicationException(e); - } - } -} +package meerkat.rest; + +import com.google.protobuf.GeneratedMessage; +import com.google.protobuf.Message; + + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import static meerkat.rest.Constants.*; + +@Provider +@Consumes(MEDIATYPE_PROTOBUF) +public class ProtobufMessageBodyReader implements MessageBodyReader { + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, + MediaType mediaType) { + return Message.class.isAssignableFrom(type); + } + + @Override + public Message readFrom(Class type, Type genericType, Annotation[] annotations, + MediaType mediaType, MultivaluedMap httpHeaders, + InputStream entityStream) throws IOException, WebApplicationException { + try { + Method newBuilder = type.getMethod("newBuilder"); + GeneratedMessage.Builder builder = (GeneratedMessage.Builder) newBuilder.invoke(type); + return builder.mergeFrom(entityStream).build(); + } catch (Exception e) { + throw new WebApplicationException(e); + } + } +} diff --git a/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyWriter.java b/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyWriter.java index b8ea503..2397b4b 100644 --- a/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyWriter.java +++ b/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyWriter.java @@ -1,41 +1,41 @@ -package meerkat.rest; - -import com.google.protobuf.Message; - -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyWriter; -import javax.ws.rs.ext.Provider; - -import java.io.IOException; -import java.io.OutputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; - -import static meerkat.rest.Constants.*; - -@Provider -@Produces(MEDIATYPE_PROTOBUF) -public class ProtobufMessageBodyWriter implements MessageBodyWriter { - @Override - public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, - MediaType mediaType) { - return Message.class.isAssignableFrom(type); - } - - @Override - public long getSize(Message message, Class type, Type genericType, Annotation[] annotations, - MediaType mediaType) { - return -1; - } - - @Override - public void writeTo(Message message, Class type, Type genericType, Annotation[] annotations, - MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) - throws IOException, WebApplicationException { - message.writeTo(entityStream); - } -} - - +package meerkat.rest; + +import com.google.protobuf.Message; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import static meerkat.rest.Constants.*; + +@Provider +@Produces(MEDIATYPE_PROTOBUF) +public class ProtobufMessageBodyWriter implements MessageBodyWriter { + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, + MediaType mediaType) { + return Message.class.isAssignableFrom(type); + } + + @Override + public long getSize(Message message, Class type, Type genericType, Annotation[] annotations, + MediaType mediaType) { + return -1; + } + + @Override + public void writeTo(Message message, Class type, Type genericType, Annotation[] annotations, + MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + message.writeTo(entityStream); + } +} + + diff --git a/settings.gradle b/settings.gradle index 99f4c5e..236a0d1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,8 @@ -include 'meerkat-common' -include 'voting-booth' -include 'bulletin-board-server' -include 'polling-station' -include 'restful-api-common' -include 'bulletin-board-client' - +include 'meerkat-common' +include 'voting-booth' +include 'bulletin-board-server' +include 'polling-station' +include 'restful-api-common' +include 'bulletin-board-client' +include 'distributed-key-generation' + diff --git a/voting-booth/build.gradle b/voting-booth/build.gradle index 70d7340..7619b48 100644 --- a/voting-booth/build.gradle +++ b/voting-booth/build.gradle @@ -189,5 +189,3 @@ publishing { } } - -