├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── configuration ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── resources ├── GDH.png ├── logo.png └── plantuml └── src ├── main └── java │ └── com │ └── gdh │ ├── crypto │ ├── CipherAgent.java │ └── CipherAgentImpl.java │ ├── main │ ├── Configuration.java │ ├── Constants.java │ ├── ExchangeState.java │ ├── GDHVertex.java │ ├── Group.java │ ├── Node.java │ └── PrimaryVertex.java │ └── parser │ ├── JsonMessageParser.java │ ├── MessageConstructor.java │ └── MessageParser.java └── test └── java └── com └── gdh └── test ├── AsyncKeyExchangeTest.java ├── ConfigurationTest.java ├── EncDecTest.java ├── ExceptionTest.java ├── ForgedMessagesKeyExchangeTest.java ├── GroupNodeTest.java ├── KeyExchangeTest.java ├── LoggerTest.java ├── MultipleGroupKeyExchangeTest.java ├── NoKeyOnWireTest.java ├── NonSyncDeploymentKeyExchange.java └── VertxTestSuite.java /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | install: true 4 | 5 | addons: 6 | sonarcloud: 7 | organization: "maxamel-github" 8 | token: $SONAR_TOKEN 9 | 10 | jdk: 11 | - oraclejdk8 12 | - openjdk8 13 | 14 | script: 15 | - ./gradlew sonarqube 16 | 17 | cache: 18 | directories: 19 | - '$HOME/.m2/repository' 20 | - '$HOME/.sonar/cache' 21 | - '$HOME/.gradle' 22 | - '.gradle' 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Max Amelchenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/maxamel/GDH.svg)](https://travis-ci.org/maxamel/GDH) 2 | [![Coverage Status](https://sonarcloud.io/api/project_badges/measure?project=GDH&metric=coverage)](https://sonarcloud.io/api/project_badges/measure?project=GDH&metric=coverage) 3 | [![Mentioned in Awesome Vert.x](https://awesome.re/mentioned-badge.svg)](https://github.com/vert-x3/vertx-awesome) 4 | [![Known Vulnerabilities](https://snyk.io/test/github/maxamel/GDH/badge.svg)](https://snyk.io/test/github/maxamel/GDH) 5 | 6 | [![Quality Gate](https://sonarcloud.io/api/project_badges/quality_gate?project=GDH)](https://sonarcloud.io/api/project_badges/quality_gate?project=GDH)
7 | 8 | 9 | 10 | 11 | # GDH : Generalized Diffie-Hellman Key Exchange Platform 12 | 13 | A Diffie-Hellman key exchange library for multiple parties built on top of the asynchronous, event-driven [Vert.x](http://vertx.io/) framework. 14 | 15 | # Overview 16 | 17 | [Diffie-Hellman](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange) has been the de-facto standard for key exchange for many years. Two parties who want to communicate on an insecure channel, can use it to generate symmetric keys, and encrypt the messages between them. 18 | Diffie-Hellman (or derivatives of it, e.g. Elliptic Curve Diffie-Hellman) is commonly used in many authentication protocols and confidential tunneling schemes such as SSL/TLS, SSHv2, SNMPv3, and many more. 19 | The most common and general scenario for the use of Diffie-Hellman is two parties that want to exchange messages over an insecure network. 20 | Common use-cases are client to web-server or peer-to-peer file-sharing communication. 21 | However, the case where multiple parties need to share a secret key is rarely addressed. Such cases may arise in complex distributed 22 | systems where participants are located on different machines, and need to communicate with each other directly, rather than through one 23 | central entity. Instead of generating a secret key for each pair of participants, it is possible to generate a single secret key shared by all participants, in a manner which is resistable to eavesdropping attacks. This is where Generalized Diffie-Hellman comes in. 24 | 25 | The following sequence diagram illustrates how the key exchange is performed. At first, two large numbers are distributed among the participants in plaintext. These numbers are the cyclic group generator (g) and a large prime(N). Then the participants come up with their secret numbers (a,b,c) which they do not reveal to anyone. They then begin a series of transactions at the end of which, they can each calculate the same secret key, without it ever being transmitted on the wire. In old-style Diffie-Hellman we would have 3 different keys produced, one per each couple of participants. 26 | This scheme can be performed for any number of participants. The number of messages needed for N participants to complete a key exchange is N(N-1). 27 | 28 |

29 | 30 |

31 | 32 | Due to the [discrete logarithm](https://en.wikipedia.org/wiki/Discrete_logarithm) problem, it is impossible for any third party listening in on the communication to compute the final key. 33 | 34 | # Security 35 | 36 | It has been known for a few years now that the NSA has the ability to [break](https://arstechnica.com/information-technology/2015/10/how-the-nsa-can-break-trillions-of-encrypted-web-and-vpn-connections/) Diffie-Hellman encrypted traffic which uses 1024 bit primes. Because these primes need to be carefully picked out, only a handful of such primes are used in practice. That's why it's beneficial for the NSA to invest in breaking those primes - it allows them to decrypt a big percentage of the world's traffic. This is why GDH uses 2048 bit primes and generates 256 bit symmetric keys, which are considered safe for now. 37 | 38 | # Prerequisites 39 | 40 | Written in Java 8. 41 | 42 | Built with Gradle. 43 | 44 | # Installation 45 | 46 | Get the code and build: 47 | 48 | ``` 49 | git clone https://github.com/maxamel/GDH.git 50 | cd GDH 51 | gradle clean build 52 | ``` 53 | 54 | Get the generated Jar from the build directory of the project. Add it to the build path of your project and that's it. 55 | 56 | # Usage 57 | 58 | The basic usage of the library is spinning up verticles and initiating a key exchange between them. 59 | Once you have the key you can start encrypting/decrypting messages safely between the verticles. Note this library only 60 | provides a key exchange platform and utility methods for encryption/decryption. The network layer (e.g messaging protocol) 61 | must be implemented by the user. 62 | 63 | The basic object used for deploying and undeploying verticles is the PrimaryVertex. 64 | 65 | ```java 66 | PrimaryVertex pv = new PrimaryVertex(); 67 | ``` 68 | 69 | The verticle object participating in the key exchange is the GDHVertex. 70 | Let's define our first GDHVertex and call it activeVertex as it will be the one who initiates key exchanges. 71 | All other verticles will be passive. The following example will be run between two verticles on localhost, 72 | but can be run with multiple participants in a distributed environment. 73 | ```java 74 | GDHVertex activeVertex = new GDHVertex(); 75 | ``` 76 | Define a Configuration for the verticle: 77 | ```java 78 | Configuration config = new Configuration(); 79 | // add parameters to the Configuration 80 | config.setIP("localhost").setPort("5000").setRetries(5).setLogLevel(Level.OFF); 81 | // assign the configuration to the verticle 82 | activeVertex.setConfiguration(config); 83 | ``` 84 | 85 | Now let's define another verticle to participate in the key exchange. 86 | ```java 87 | GDHVertex passiveVertex = new GDHVertex(); 88 | Configuration config2 = new Configuration(); 89 | config2.setIP("localhost").setPort("5001").setExchangeTimeout(5000).setLogLevel(Level.OFF); 90 | passiveVertex.setConfiguration(config2); 91 | ``` 92 | 93 | Once we have all participants defined, we can go ahead and form a group with the Configuration of one of the verticles. 94 | The id of the group is determined by its nodes, so if you construct 2 groups with the same nodes it will essentially be the 95 | same group. 96 | ```java 97 | Group g = new Group(config, 98 | new Node("localhost","5000"), 99 | new Node("localhost","5001")); 100 | ``` 101 | 102 | Now it's all set up and you can run the verticles and initiate a key exchange. 103 | The most important rule when developing with Vert.x (or any asynchronous platform) is DO NOT BLOCK THE EVENT LOOP! 104 | So remember not to perform blocking operations inside the asynchronous calls. 105 | ```java 106 | pv.run(passiveVertex,deployment1 -> { 107 | if (deployment1.succeeded()) { 108 | pv.run(activeVertex,deployment2 -> { 109 | if (deployment2.succeeded()) { 110 | activeVertex.exchange(g.getGroupId(), exchange -> { 111 | if (exchange.succeeded()) { 112 | // the key is available in this context and also later as a Future object 113 | System.out.println("Got new key: " + exchange.result()); 114 | } 115 | else { 116 | System.out.println("Error exchanging!"); 117 | } 118 | } 119 | } 120 | else { 121 | System.out.println("Error deploying!"); 122 | } 123 | } 124 | } 125 | else { 126 | System.out.println("Error deploying!"); 127 | } 128 | } 129 | ``` 130 | 131 | You can also use blocking code for key exchange: 132 | ```java 133 | pv.run(passiveVertex,deployment1 -> { 134 | if (deployment1.succeeded()) { 135 | pv.run(activeVertex,deployment2 -> { 136 | if (deployment2.succeeded()) { 137 | // get the key as a Future. Do not block inside the asynchronous call 138 | CompletableFuture futureKey = activeVertex.exchange(g.getGroupId()); 139 | } 140 | else { 141 | System.out.println("Error deploying!"); 142 | } 143 | } 144 | } 145 | else { 146 | System.out.println("Error deploying!"); 147 | } 148 | } 149 | ``` 150 | You can even use blocking code for the deployments. The verticle which initiates key exchanges should still be deployed 151 | using an asynchronous call (Otherwise you have to busy wait on it with a while loop!). All other nodes will participate in 152 | the exchange once they are up and running. 153 | ```java 154 | pv.run(activeVertex,deployment1 -> { 155 | if (deployment1.succeeded()) { 156 | pv.run(passiveVertex); 157 | // get the key as a Future. Do not block inside the asynchronous call 158 | CompletableFuture futureKey = activeVertex.exchange(g.getGroupId()); 159 | } 160 | else { 161 | System.out.println("Error deploying!"); 162 | } 163 | } 164 | ``` 165 | 166 | At any point you can access the exchanged key as a CompletableFuture object from any verticle. 167 | This object is a representation of the key. The actual key might not be available at this moment in time, 168 | but will be made available as soon as the exchange finishes. Here are just a handful of options you have 169 | with the CompletableFuture: 170 | ```java 171 | CompletableFuture key = passiveVertex.getKey(g.getGroupId()); 172 | 173 | // Wait for the key exchange to complete and get the final key 174 | BigInteger fin = key.get(); 175 | 176 | // Wait for the key for a bounded time and throw Exception if this time is exceeded 177 | BigInteger fin = key.get(1000, TimeUnit.MILLISECONDS); 178 | 179 | // Get the key immediately. If it's not available return the default value given as a parameter (null) 180 | BigInteger fin = key.getNow(null); 181 | ``` 182 | 183 | Don't forget to kill the verticles when you're finished with them. As in the deployment, you can use either 184 | asynchronous calls or blocking code: 185 | ```java 186 | pv.kill(activeVertex,undeployment1 -> { 187 | if (undeployment1.succeeded()) { 188 | System.out.println("First undeployment successful!"); 189 | pv.kill(passiveVertex,undeployment2 -> { 190 | if (undeployment2.succeeded()) { 191 | System.out.println("Second undeployment successful!"); 192 | } 193 | else { 194 | System.out.println("Error undeploying!"); 195 | } 196 | } 197 | } 198 | else { 199 | System.out.println("Error undeploying!"); 200 | } 201 | } 202 | ``` 203 | 204 | Let's say you have a distributed system, where each machine is running GDH, and you have no way of choosing or enforcing the GDHVertex which initiates the key exchange, i.e. every machine runs the same code. 205 | Hese's a sample code which can run in such an environment and enforce only one key exchange initiator. 206 | 207 | ```java 208 | GDHVertex vertex = new GDHVertex(); 209 | 210 | Configuration config = new Configuration(); 211 | config.setIP("localhost").setPort("5000").setRetries(8).setLogLevel(Level.DEBUG); 212 | vertex.setConfiguration(config); 213 | 214 | Group g = new Group(config, 215 | new Node("172.52.44.120","5000"), 216 | new Node("172.52.44.121","5000"), 217 | new Node("172.52.44.122","5000")); 218 | 219 | vertex.addGroup(g); 220 | 221 | PrimaryVertex pv = new PrimaryVertex(); 222 | pv.run(vertex); 223 | 224 | if (vertex.getNode().equals(g.getActiveNode())) vertex.exchange(g.getGroupId()); 225 | CompletableFuture key = vertex.getKey(g.getGroupId()); // <---- key here 226 | ``` 227 | 228 | # Code Quality 229 | 230 | This project is analyzed on [Sonarcloud](https://sonarcloud.io/dashboard?id=GDH) and on [Snyk](https://snyk.io/org/maxamel/project/fe48906b-d65e-4e19-97af-b1fe986d536f). 231 | Every build the code runs through a couple of static code analyzers (PMD and findbugs) to ensure code quality is maintained. 232 | Each push to the repository triggers a cloud build via TravisCI, which in turn pushes the code into Sonarcloud to find coding bugs and into Snyk to find security vulnerabilities in the dependencies. If anything goes wrong during any of these steps the build fails. 233 | 234 | # Testing 235 | 236 | The code is tested by both unit tests and integration tests. The integration testing involves actual spinning up of verticles, performing exchanges and checking the correctness and security of the transactions. Testing must cover at least 80% of the code, otherwise the quality gate of Sonarcloud fails. 237 | 238 | # Logging 239 | 240 | GDH logs messages at different points during the exchange. This allows easy debugging and also lets users follow the exchange and helps understand the protocol. Logs are also used in tests. For example, verifying the final key after the exchange is NOT transmitted over the wire, or counting the number of messages required to complete a key exchange. So if you change the logging messages, make sure this hasn't affected any tests. 241 | 242 | # License 243 | 244 | Published under the MIT License. This basically means the software is free and anyone can use it however they wish. 245 | No liability or warranty. 246 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "org.sonarqube" version "2.5" 3 | } 4 | 5 | apply plugin: 'java' 6 | apply plugin: 'jacoco' 7 | apply plugin: 'pmd' 8 | pmd { 9 | toolVersion='5.5.2' 10 | ruleSets=["java-basic","java-imports","java-strings","java-clone","java-design"] 11 | } 12 | apply plugin: 'findbugs' 13 | findbugs { 14 | sourceSets = [sourceSets.main] 15 | toolVersion = "3.0.1" 16 | } 17 | 18 | libsDirName = 'build/' 19 | version = '0.9.2.2' 20 | archivesBaseName = 'GDH' 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { 25 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 26 | } 27 | } 28 | 29 | test { 30 | exclude '**/*Suite.class' 31 | beforeTest { descriptor -> 32 | logger.lifecycle("Running test: " + descriptor) 33 | } 34 | } 35 | 36 | sourceSets.main.resources.srcDirs = [ "resources/" ] 37 | 38 | dependencies { 39 | compile 'io.vertx:vertx-core:3.5.1' 40 | testCompile 'io.vertx:vertx-unit:3.5.1' 41 | testCompile 'junit:junit:4.12' 42 | compile 'io.reactivex:rxjava:1.3.0' 43 | compile 'com.google.code.findbugs:annotations:3.0.1' 44 | compile 'log4j:log4j:1.2.17' 45 | } 46 | 47 | clean { 48 | delete "libs/" 49 | delete "build/" 50 | delete "bin/" 51 | delete project.name+"-"+project.version+".jar" 52 | } 53 | 54 | task packageJavadoc(type: Jar, dependsOn: 'javadoc') { 55 | from javadoc.destinationDir 56 | classifier = 'javadoc' 57 | } 58 | task packageSources(type: Jar, dependsOn: 'classes') { 59 | from sourceSets.main.allSource 60 | classifier = 'sources' 61 | } 62 | artifacts { 63 | archives packageJavadoc 64 | archives packageSources 65 | } 66 | jar { 67 | dependsOn configurations.runtime 68 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } 69 | manifest { 70 | attributes 'Implementation-Title': project.name, 71 | 'Implementation-Version': project.version 72 | } 73 | exclude "META-INF/*.SF" 74 | exclude "META-INF/*.DSA" 75 | exclude "META-INF/*.RSA" 76 | } 77 | 78 | task testSuite(type: Test) { 79 | include '**VertxTestSuite.class' 80 | } -------------------------------------------------------------------------------- /configuration: -------------------------------------------------------------------------------- 1 | { 2 | "ip":"localhost", 3 | "port":"1079", 4 | "prime":"AD107E1E9123A9D0D660FAA79559C51FA20D64E5683B9FD1B54B1597B61D0A75E6FA141DF95A56DBAF9A3C407BA1DF15EB3D688A309C180E1DE6B85A1274A0A66D3F8152AD6AC2129037C9EDEFDA4DF8D91E8FEF55B7394B7AD5B7D0B6C12207C9F98D11ED34DBF6C6BA0B2C8BBC27BE6A00E0A0B9C49708B3BF8A317091883681286130BC8985DB1602E714415D9330278273C7DE31EFDC7310F7121FD5A07415987D9ADC0A486DCDF93ACC44328387315D75E198C641A480CD86A1B9E587E8BE60E69CC928B2B9C52172E413042E9B23F10B0E16E79763C9B53DCF4BA80A29E3FB73C16B8E75B97EF363E2FFA31F71CF9DE5384E71B81C0AC4DFFE0C10E64F", 5 | "generator":"AC4032EF4F2D9AE39DF30B5C8FFDAC506CDEBE7B89998CAF74866A08CFE4FFE3A6824A4E10B9A6F0DD921F01A70C4AFAAB739D7700C29F52C57DB17C620A8652BE5E9001A8D66AD7C17669101999024AF4D027275AC1348BB8A762D0521BC98AE247150422EA1ED409939D54DA7460CDB5F6C6B250717CBEF180EB34118E98D119529A45D6F834566E3025E316A330EFBB77A86F0C1AB15B051AE3D428C8F8ACB70A8137150B8EEB10E183EDD19963DDD9E263E4770589EF6AA21E7F5F2FF381B539CCE3409D13CD566AFBB48D6C019181E1BCFE94B30269EDFE72FE9B6AA4BD7B5A0F1C71CFFF4C19C418E1F6EC017981BC087F2A7065B384B890D3191F2BFA", 6 | "retries":5, 7 | "groups": 8 | [ 9 | [ 10 | { 11 | "ip":"localhost", 12 | "port":"1080" 13 | }, 14 | { 15 | "ip":"localhost", 16 | "port":"1081" 17 | } 18 | ] 19 | ] 20 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxamel/GDH/9d800af33e48d1c4a0a60b40e779d68eafeb8dda/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 28 21:26:29 CEST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /resources/GDH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxamel/GDH/9d800af33e48d1c4a0a60b40e779d68eafeb8dda/resources/GDH.png -------------------------------------------------------------------------------- /resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxamel/GDH/9d800af33e48d1c4a0a60b40e779d68eafeb8dda/resources/logo.png -------------------------------------------------------------------------------- /resources/plantuml: -------------------------------------------------------------------------------- 1 | @startuml 2 | !pragma teoz true 3 | skinparam backgroundColor #EEEBDC 4 | skinparam handwritten true 5 | skinparam monochrome reverse 6 | 7 | legend top center 8 | Public variables: 9 | Large generator g 10 | Large prime N 11 | end legend 12 | 13 | actor Alice 14 | actor Bob 15 | actor Carol 16 | Alice ->o Alice: a 17 | & Bob ->o Bob: b 18 | & Carol ->o Carol: c 19 | Alice -> Bob: g^a mod N 20 | Bob -> Carol: g^b mod N 21 | Carol -> Alice: g^c mod N 22 | Alice -> Bob: g^(ac) mod N 23 | Bob -> Carol: g^(ab) mod N 24 | Carol -> Alice: g^(bc) mod N 25 | Alice ->o Alice: g^(abc) mod N 26 | & Bob ->o Bob: g^(abc) mod N 27 | & Carol ->o Carol: g^(abc) mod N 28 | @enduml -------------------------------------------------------------------------------- /src/main/java/com/gdh/crypto/CipherAgent.java: -------------------------------------------------------------------------------- 1 | package com.gdh.crypto; 2 | 3 | import java.io.IOException; 4 | import java.security.InvalidAlgorithmParameterException; 5 | import java.security.InvalidKeyException; 6 | 7 | import javax.crypto.SecretKey; 8 | 9 | public interface CipherAgent { 10 | 11 | // encryption method receiving a value to encrypt, the initial vector and a 12 | // key 13 | public byte[] encrypt(String value, byte[] iv, SecretKey key) 14 | throws InvalidKeyException, InvalidAlgorithmParameterException, IOException; 15 | 16 | // decryption method receiving a value to decrypt, the initial vector and a 17 | // key 18 | public String decrypt(byte[] encryptedBytes, byte[] iv, SecretKey key) 19 | throws InvalidKeyException, InvalidAlgorithmParameterException, IOException; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/crypto/CipherAgentImpl.java: -------------------------------------------------------------------------------- 1 | package com.gdh.crypto; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.nio.charset.StandardCharsets; 7 | import java.security.InvalidAlgorithmParameterException; 8 | import java.security.InvalidKeyException; 9 | import java.security.NoSuchAlgorithmException; 10 | 11 | import javax.crypto.Cipher; 12 | import javax.crypto.CipherInputStream; 13 | import javax.crypto.CipherOutputStream; 14 | import javax.crypto.NoSuchPaddingException; 15 | import javax.crypto.SecretKey; 16 | import javax.crypto.spec.IvParameterSpec; 17 | 18 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 19 | import com.gdh.main.Constants; 20 | 21 | /** 22 | * 23 | * CipherAgentImpl is a utility class for encrypting and decrypting messages. 24 | * The key used is symmetric, 25 | * 26 | * which means the same key is used for both encryption and decryption. 27 | * 28 | * It is not part of the key exchange process. Once a key is obtained from 29 | * GDHVertex this class can be used 30 | * 31 | * as part of a protocol to exchange messages between the participating parties. 32 | * 33 | * @author Max Amelchenko 34 | * 35 | */ 36 | public class CipherAgentImpl implements CipherAgent { 37 | private final Cipher encryptCipher; 38 | private final Cipher decryptCipher; 39 | 40 | public CipherAgentImpl(String instance) throws NoSuchAlgorithmException, NoSuchPaddingException { 41 | encryptCipher = Cipher.getInstance(instance); 42 | decryptCipher = Cipher.getInstance(instance); 43 | } 44 | 45 | /** 46 | * Encrypt a value with a key and initial vector 47 | * 48 | * @param value 49 | * the String value to encrypt 50 | * @param iv 51 | * the initial vector. Must be a random 128 bit value and the 52 | * same one used in decryption 53 | * @param key 54 | * the key for encryption 55 | * 56 | * @return the encrypted bytes 57 | */ 58 | public byte[] encrypt(String value, byte[] iv, SecretKey key) 59 | throws InvalidKeyException, InvalidAlgorithmParameterException, IOException { 60 | byte[] encryptedBytes = null; 61 | IvParameterSpec ivspec = new IvParameterSpec(iv); 62 | encryptCipher.init(Cipher.ENCRYPT_MODE, key, ivspec); 63 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 64 | CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher); 65 | cipherOutputStream.write(value.getBytes(StandardCharsets.UTF_8)); 66 | cipherOutputStream.flush(); 67 | cipherOutputStream.close(); 68 | encryptedBytes = outputStream.toByteArray(); 69 | 70 | return encryptedBytes; 71 | } 72 | 73 | /** 74 | * Decrypt an encrypted byte array with a key and initial vector 75 | * 76 | * @param encryptedBytes 77 | * the byte array to decrypt 78 | * @param iv 79 | * the initial vector. Must be a random 128 bit value and the 80 | * same one used in encryption 81 | * @param key 82 | * the key for decryption 83 | * 84 | * @return the decrypted string 85 | */ 86 | @SuppressFBWarnings("UC_USELESS_OBJECT") 87 | public String decrypt(byte[] encryptedBytes, byte[] iv, SecretKey key) 88 | throws InvalidKeyException, InvalidAlgorithmParameterException, IOException { 89 | byte[] buf = new byte[Constants.CIPHER_SIZE]; 90 | ByteArrayOutputStream outputStream = null; 91 | IvParameterSpec ivspec = new IvParameterSpec(iv); 92 | decryptCipher.init(Cipher.DECRYPT_MODE, key, ivspec); 93 | outputStream = new ByteArrayOutputStream(); 94 | ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes); 95 | CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher); 96 | int bytesRead = 0; 97 | while ((bytesRead = cipherInputStream.read(buf)) >= 0) { 98 | outputStream.write(buf, 0, bytesRead); 99 | } 100 | cipherInputStream.close(); 101 | 102 | return outputStream.toString(StandardCharsets.UTF_8.name()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/main/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.gdh.main; 2 | 3 | import java.io.OutputStreamWriter; 4 | import java.io.PrintWriter; 5 | import java.nio.charset.StandardCharsets; 6 | import java.security.SecureRandom; 7 | 8 | import org.apache.log4j.Appender; 9 | import org.apache.log4j.ConsoleAppender; 10 | import org.apache.log4j.Level; 11 | import org.apache.log4j.Logger; 12 | import org.apache.log4j.PatternLayout; 13 | 14 | import io.vertx.core.Vertx; 15 | import io.vertx.core.buffer.Buffer; 16 | import io.vertx.core.json.JsonObject; 17 | 18 | /** 19 | * 20 | * 21 | * Configuration is the object which holds all the configurable parameters of a 22 | * GDHVertex. Implements the Builder pattern. 23 | * 24 | * Diffie-Hellman requires a safe prime and a cyclic group generator, both 25 | * publicly known to everyone. 26 | * 27 | * The Configuration object has these two numbers built-in, taken from 28 | * https://tools.ietf.org/html/rfc5114. 29 | * 30 | * Note these numbers must fulfill several rules in order to be safe. If you are 31 | * not using the default numbers take extreme care when choosing new ones', as your key exchange might be 32 | * vulnerable to all kinds of attacks. 33 | * 34 | * @author Max Amelchenko 35 | */ 36 | public class Configuration { 37 | private String IP = "localhost"; 38 | private String port = "1090"; 39 | private int retries = Constants.DEFAULT_RETRY; 40 | private int exchangeTimeoutMillis = Constants.EXCHANGE_TIMEOUT; 41 | private String prime = "AD107E1E9123A9D0D660FAA79559C51FA20D64E5683B9FD1B54B1597B61D0A75E6FA141DF95A56DBAF9A3C" 42 | + "407BA1DF15EB3D688A309C180E1DE6B85A1274A0A66D3F8152AD6AC2129037C9EDEFDA4DF8D91E8FEF55B7" 43 | + "394B7AD5B7D0B6C12207C9F98D11ED34DBF6C6BA0B2C8BBC27BE6A00E0A0B9C49708B3BF8A317091883681" 44 | + "286130BC8985DB1602E714415D9330278273C7DE31EFDC7310F7121FD5A07415987D9ADC0A486DCDF93ACC" 45 | + "44328387315D75E198C641A480CD86A1B9E587E8BE60E69CC928B2B9C52172E413042E9B23F10B0E16E797" 46 | + "63C9B53DCF4BA80A29E3FB73C16B8E75B97EF363E2FFA31F71CF9DE5384E71B81C0AC4DFFE0C10E64F"; 47 | private String generator = "AC4032EF4F2D9AE39DF30B5C8FFDAC506CDEBE7B89998CAF74866A08CFE4FFE3A6824A4E10B9A6F0DD" 48 | + "921F01A70C4AFAAB739D7700C29F52C57DB17C620A8652BE5E9001A8D66AD7C17669101999024AF4D02727" 49 | + "5AC1348BB8A762D0521BC98AE247150422EA1ED409939D54DA7460CDB5F6C6B250717CBEF180EB34118E98" 50 | + "D119529A45D6F834566E3025E316A330EFBB77A86F0C1AB15B051AE3D428C8F8ACB70A8137150B8EEB10E1" 51 | + "83EDD19963DDD9E263E4770589EF6AA21E7F5F2FF381B539CCE3409D13CD566AFBB48D6C019181E1BCFE94" 52 | + "B30269EDFE72FE9B6AA4BD7B5A0F1C71CFFF4C19C418E1F6EC017981BC087F2A7065B384B890D3191F2BFA"; 53 | 54 | private final Logger log4jLogger; 55 | 56 | public Configuration() { 57 | SecureRandom random = new SecureRandom(); 58 | this.log4jLogger = Logger.getLogger("Logger"+ random.nextInt()); 59 | 60 | ConsoleAppender appender = new ConsoleAppender(); 61 | appender.setWriter(new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8))); 62 | appender.setLayout(new PatternLayout(PatternLayout.DEFAULT_CONVERSION_PATTERN)); 63 | this.log4jLogger.addAppender(appender); 64 | 65 | this.log4jLogger.setLevel(Level.OFF); 66 | } 67 | 68 | /** 69 | * 70 | * @param IP 71 | * the ip the GDHVertex will be listening on 72 | * @return the updated configuration 73 | */ 74 | public Configuration setIP(String IP) { 75 | assert (IP.matches("(([0-1]?[0-9]{1,2}\\.)|" + "(2[0-4][0-9]\\.)|" + "(25[0-5]\\.)){3}(([0-1]?[0-9]{1,2})|" 76 | + "(2[0-4][0-9])|(25[0-5]))") || IP.contains("localhost")); 77 | this.IP = IP; 78 | return this; 79 | } 80 | 81 | /** 82 | * 83 | * @param port 84 | * the port the GDHVertex will be listening on 85 | * @return the updated configuration 86 | */ 87 | public Configuration setPort(String port) { 88 | assert port.matches("[1-9]\\d*"); 89 | this.port = port; 90 | return this; 91 | } 92 | 93 | /** 94 | * 95 | * @param retries 96 | * the number of times a GDHVertex will attempt to send a message 97 | * if delivery fails. In case the number of retries is reached 98 | * without an ack received, the key exchange is aborted. 99 | * @return the updated configuration 100 | */ 101 | public Configuration setRetries(int retries) { 102 | this.retries = retries; 103 | return this; 104 | } 105 | 106 | /** 107 | * 108 | * @param exchangeTimeoutMillis 109 | * the time limit of the Diffie-Hellman key exchange. 110 | * If the exchange takes longer than that an exception is thrown. 111 | * @return the updated configuration 112 | */ 113 | public Configuration setExchangeTimeoutMillis(int exchangeTimeoutMillis) { 114 | this.exchangeTimeoutMillis = exchangeTimeoutMillis; 115 | return this; 116 | } 117 | 118 | /** 119 | * @param generator 120 | * the cyclic group generator of this Configuration. 121 | * Needs to be large enough (2048 bits is considered safe) 122 | * @return the updated configuration 123 | */ 124 | public Configuration setGenerator(String generator) { 125 | assert generator.matches("[0-9A-F]*"); 126 | this.generator = generator; 127 | return this; 128 | } 129 | 130 | /** 131 | * @param prime 132 | * the prime number of this Configuration 133 | * Needs to be large enough (2048 bits is considered safe) 134 | * @return the updated configuration 135 | */ 136 | public Configuration setPrime(String prime) { 137 | assert prime.matches("[0-9A-F]*"); 138 | this.prime = prime; 139 | return this; 140 | } 141 | 142 | /** 143 | * 144 | * @param level 145 | * the log level to set. Default value is OFF. 146 | * @return the updated configuration 147 | */ 148 | public Configuration setLogLevel(Level level) { 149 | this.log4jLogger.setLevel(level); 150 | return this; 151 | } 152 | 153 | public String getIP() { 154 | return IP; 155 | } 156 | 157 | public String getPort() { 158 | return port; 159 | } 160 | 161 | public int getRetries() { 162 | return retries; 163 | } 164 | 165 | public Node getNode() { 166 | return new Node(IP, port); 167 | } 168 | 169 | public String getPrime() { 170 | return prime; 171 | } 172 | 173 | public String getGenerator() { 174 | return generator; 175 | } 176 | 177 | public Logger getLogger() { 178 | return log4jLogger; 179 | } 180 | 181 | public int getExchangeTimeoutMillis() { 182 | return exchangeTimeoutMillis; 183 | } 184 | 185 | /** 186 | * Setting the appender of the logger. Only one appender at a time is 187 | * allowed. 188 | * 189 | * @param app 190 | * the Appender to be set 191 | * 192 | * @return the updated configuration 193 | */ 194 | public Configuration setAppender(Appender app) { 195 | log4jLogger.removeAllAppenders(); 196 | log4jLogger.addAppender(app); 197 | return this; 198 | } 199 | 200 | /** 201 | * 202 | * @param path 203 | * the path to the configuration file 204 | * @return the Configuration object read from the file 205 | */ 206 | public static Configuration readConfigFile(String path) { 207 | Configuration conf = new Configuration(); 208 | Vertx vertx = Vertx.vertx(); 209 | vertx.fileSystem().readFile(path, res -> { 210 | Buffer buf = res.result(); 211 | JsonObject json = new JsonObject(buf); 212 | String IP = json.getString(Constants.IP); 213 | String port = json.getString(Constants.PORT); 214 | String generator = json.getString(Constants.GENERATOR); 215 | String prime = json.getString(Constants.PRIME); 216 | int retries = json.getInteger(Constants.RETRIES); 217 | conf.setIP(IP).setPort(port).setGenerator(generator).setPrime(prime).setRetries(retries); 218 | }); 219 | 220 | return conf; 221 | } 222 | 223 | @Override 224 | public int hashCode() { 225 | final int primal = 31; 226 | int result = 1; 227 | result = primal * result + ((IP == null) ? 0 : IP.hashCode()); 228 | result = primal * result + ((generator == null) ? 0 : generator.hashCode()); 229 | result = primal * result + ((port == null) ? 0 : port.hashCode()); 230 | result = primal * result + ((this.prime == null) ? 0 : this.prime.hashCode()); 231 | return result; 232 | } 233 | 234 | @Override 235 | public boolean equals(Object obj) { 236 | if (this == obj) 237 | return true; 238 | if (obj == null) 239 | return false; 240 | if (getClass() != obj.getClass()) 241 | return false; 242 | Configuration other = (Configuration) obj; 243 | if (IP == null) { 244 | if (other.IP != null) 245 | return false; 246 | } else if (!IP.equals(other.IP)) 247 | return false; 248 | if (generator == null) { 249 | if (other.generator != null) 250 | return false; 251 | } else if (!generator.equals(other.generator)) 252 | return false; 253 | if (port == null) { 254 | if (other.port != null) 255 | return false; 256 | } else if (!port.equals(other.port)) 257 | return false; 258 | if (prime == null) { 259 | if (other.prime != null) 260 | return false; 261 | } else if (!prime.equals(other.prime)) 262 | return false; 263 | return true; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/main/Constants.java: -------------------------------------------------------------------------------- 1 | package com.gdh.main; 2 | 3 | public final class Constants { 4 | 5 | public static final String GROUPID = "groupId"; 6 | public static final String MEMBERS = "members"; 7 | public static final String GENERATOR = "generator"; 8 | public static final String PRIME = "prime"; 9 | 10 | public static final String RETRIES = "retries"; 11 | 12 | public static final String IP = "ip"; 13 | public static final String PORT = "port"; 14 | 15 | public static final String ROUND = "round"; 16 | public static final String PARTIAL_KEY = "partial_key"; 17 | public static final String GROUPS = "groups"; 18 | 19 | public static final String ACK = "ack"; 20 | 21 | public static final String EXCEPTIONTIMEOUTEXCEEDED = "Timeout exceeded "; 22 | public static final String EXCEPTIONRETRIESEXCEEDED = "Retries exceeded "; 23 | 24 | public static final int BUFFER_SIZE = 2500; 25 | 26 | public static final int CIPHER_SIZE = 1024; 27 | 28 | public static final int EXCHANGE_TIMEOUT = 60000; 29 | 30 | public static final int SEND_RETRY_TIMEOUT = 2000; 31 | 32 | public static final int DEFAULT_RETRY = 5; 33 | 34 | public static final int BITS_256 = 32; 35 | 36 | public static final String NEGO_CALL = " called negotiation for group "; 37 | 38 | public static final String LOG_OUT = "Outgoing"; 39 | 40 | public static final String LOG_IN = "Incoming"; 41 | 42 | private Constants() { 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/main/ExchangeState.java: -------------------------------------------------------------------------------- 1 | package com.gdh.main; 2 | 3 | import java.math.BigInteger; 4 | import java.security.SecureRandom; 5 | import java.util.Random; 6 | import java.util.concurrent.CompletableFuture; 7 | 8 | import io.vertx.core.AsyncResult; 9 | import io.vertx.core.Future; 10 | import io.vertx.core.Handler; 11 | 12 | public class ExchangeState { 13 | private final int groupId; 14 | 15 | private BigInteger partial_key; 16 | 17 | private BigInteger secret; 18 | 19 | private int round; 20 | 21 | private boolean isDone; 22 | 23 | private final CompletableFuture key = new CompletableFuture<>(); 24 | 25 | private Handler> aHandler; 26 | 27 | public ExchangeState(int groupId, BigInteger gen) { 28 | this.groupId = groupId; 29 | this.partial_key = gen; 30 | initSecret(); 31 | } 32 | 33 | public ExchangeState(int groupId, BigInteger partial_key, int round) { 34 | this.groupId = groupId; 35 | this.partial_key = partial_key; 36 | this.round = round; 37 | initSecret(); 38 | } 39 | 40 | private void initSecret() { 41 | byte[] sec = new byte[Constants.BITS_256]; 42 | Random random = new SecureRandom(); 43 | random.nextBytes(sec); 44 | secret = (new BigInteger(sec)).abs(); 45 | } 46 | 47 | public int getGroupId() { 48 | return groupId; 49 | } 50 | 51 | public BigInteger getPartial_key() { 52 | return partial_key; 53 | } 54 | 55 | public int getRound() { 56 | return round; 57 | } 58 | 59 | public BigInteger getSecret() { 60 | return secret; 61 | } 62 | 63 | public void incRound() { 64 | round++; 65 | } 66 | 67 | public void decRound() { 68 | round--; 69 | } 70 | 71 | public void setPartial_key(BigInteger partial_key) { 72 | this.partial_key = partial_key; 73 | } 74 | 75 | public void done() { 76 | isDone = true; 77 | key.complete(partial_key); 78 | if (aHandler != null) 79 | aHandler.handle(Future.succeededFuture(partial_key)); 80 | } 81 | 82 | public boolean isDone() { 83 | return isDone; 84 | } 85 | 86 | public CompletableFuture getKey() { 87 | return key; 88 | } 89 | 90 | public void registerHandler(Handler> aHandler) { 91 | this.aHandler = aHandler; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/main/GDHVertex.java: -------------------------------------------------------------------------------- 1 | package com.gdh.main; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.concurrent.CompletableFuture; 6 | import java.util.concurrent.TimeoutException; 7 | 8 | import io.vertx.core.AbstractVerticle; 9 | import io.vertx.core.AsyncResult; 10 | import io.vertx.core.Future; 11 | import io.vertx.core.Handler; 12 | import io.vertx.core.buffer.Buffer; 13 | import io.vertx.core.json.JsonObject; 14 | 15 | import io.vertx.core.net.NetClient; 16 | import io.vertx.core.net.NetClientOptions; 17 | import io.vertx.core.net.NetServer; 18 | import io.vertx.core.net.NetServerOptions; 19 | import io.vertx.core.net.NetSocket; 20 | import com.gdh.parser.JsonMessageParser; 21 | import com.gdh.parser.MessageConstructor; 22 | import com.gdh.parser.MessageParser; 23 | 24 | import java.math.BigInteger; 25 | 26 | /** 27 | * 28 | * GDHVertex is an object which participates in a Generalized Diffie-Hellman Key 29 | * exchange process. 30 | * 31 | * As an AbstractVerticle, it must be deployed in order to be used. 32 | * 33 | * @author Max Amelchenko 34 | */ 35 | public class GDHVertex extends AbstractVerticle { 36 | private final Map groupMappings = new HashMap<>(); 37 | private final Map stateMappings = new HashMap<>(); 38 | private NetServer server; 39 | private Configuration conf; 40 | 41 | @Override 42 | public void start(Future future) throws Exception { 43 | MessageParser parser = new JsonMessageParser(groupMappings, stateMappings); 44 | assert (conf != null); 45 | 46 | NetServerOptions options = new NetServerOptions(); 47 | options.setReceiveBufferSize(Constants.BUFFER_SIZE); 48 | server = vertx.createNetServer(options); 49 | Handler handler = (NetSocket netSocket) -> { 50 | netSocket.handler((Buffer buffer) -> { 51 | // parsing message 52 | String msg = buffer.getString(0, buffer.length()); 53 | conf.getLogger().debug(getNode().toString() + " " + Constants.LOG_IN + " from: " 54 | + netSocket.remoteAddress() + " " + buffer.length() + " " + msg); 55 | 56 | int groupId = parser.parse(msg); 57 | if (groupId == -1) { 58 | // This node is behind in its info. Come back later... 59 | conf.getLogger().debug(getNode().toString() + " Unkown group " + msg); 60 | Buffer outBuffer = Buffer.buffer(); 61 | outBuffer.appendString(Constants.ACK); 62 | netSocket.write(outBuffer); 63 | return; 64 | } else if (groupId == -2) { 65 | // receiving doubled messages. Come back later... 66 | conf.getLogger().debug(getNode().toString() + " Double message " + msg); 67 | Buffer outBuffer = Buffer.buffer(); 68 | outBuffer.appendString(Constants.ACK); 69 | netSocket.write(outBuffer); 70 | return; 71 | } 72 | Group group = groupMappings.get(groupId); 73 | 74 | Buffer outBuffer = Buffer.buffer(); 75 | outBuffer.appendString(Constants.ACK); 76 | netSocket.write(outBuffer); 77 | 78 | compute(group); 79 | }); 80 | }; 81 | 82 | server.connectHandler(handler); 83 | server.listen(Integer.parseInt(conf.getPort()), res -> { 84 | if (res.succeeded()) { 85 | future.complete(); 86 | conf.getLogger().info(getNode().toString() + " started listening on: " + conf.getPort()); 87 | } else { 88 | future.fail(res.cause()); 89 | conf.getLogger().info(getNode().toString() + " startup failure: " + 90 | conf.getPort() + " " + res.cause().getMessage()); 91 | } 92 | }); 93 | 94 | } 95 | 96 | /** 97 | * Start a key exchange process 98 | * 99 | * @param groupId 100 | * the id of the group for which a key exchange will be initiated 101 | * @return A Future representation of the key 102 | */ 103 | public CompletableFuture exchange(int groupId) { 104 | conf.getLogger().info(getNode().toString() + Constants.NEGO_CALL + groupId); 105 | Group g = groupMappings.get(groupId); 106 | CompletableFuture res = broadcast(g); 107 | CompletableFuture future = res.thenCompose(s -> compute(g)); 108 | vertx.setTimer(conf.getExchangeTimeoutMillis(), id -> { 109 | future.completeExceptionally( 110 | new TimeoutException(Constants.EXCEPTIONTIMEOUTEXCEEDED + conf.getExchangeTimeoutMillis())); 111 | }); 112 | return future; 113 | } 114 | 115 | /** 116 | * Start a key exchange process 117 | * 118 | * @param groupId 119 | * the id of the group for which a key exchange will be initiated 120 | * @param aHandler 121 | * the handler which succeeds or fails in accordance to the key 122 | * exchange outcome 123 | * @return A Future representation of the key 124 | */ 125 | public CompletableFuture exchange(int groupId, Handler> aHandler) { 126 | conf.getLogger().info(getNode().toString() + Constants.NEGO_CALL + groupId); 127 | Group g = groupMappings.get(groupId); 128 | ExchangeState state = stateMappings.get(groupId); 129 | state.registerHandler(aHandler); 130 | CompletableFuture res = broadcast(g); 131 | CompletableFuture future = res.thenCompose(s -> compute(g)); 132 | long timer[] = new long[1]; 133 | timer[0] = vertx.setTimer(conf.getExchangeTimeoutMillis(), id -> { 134 | if (future.isDone() && !future.isCompletedExceptionally()) { 135 | aHandler.handle(Future.succeededFuture()); 136 | vertx.cancelTimer(timer[0]); 137 | } else { 138 | aHandler.handle(Future.failedFuture(Constants.EXCEPTIONTIMEOUTEXCEEDED + Constants.EXCHANGE_TIMEOUT)); 139 | future.completeExceptionally( 140 | new TimeoutException(Constants.EXCEPTIONTIMEOUTEXCEEDED + conf.getExchangeTimeoutMillis())); 141 | vertx.cancelTimer(timer[0]); 142 | } 143 | }); 144 | future.exceptionally(e -> { 145 | aHandler.handle(Future.failedFuture(e.getMessage())); 146 | vertx.cancelTimer(timer[0]); 147 | return future.join(); 148 | }); 149 | 150 | return future; 151 | } 152 | 153 | public void setConfiguration(Configuration conf) { 154 | this.conf = conf; 155 | } 156 | 157 | public void addGroup(Group g) { 158 | groupMappings.put(g.getGroupId(), g); 159 | stateMappings.put(g.getGroupId(), new ExchangeState(g.getGroupId(), g.getGenerator())); 160 | } 161 | 162 | /** 163 | * Get a Node object from this GDHVertex. The parameters of the Node depend 164 | * on the Configuration of this GDHVertex. 165 | * 166 | * @return a Node object of this GDHVertex. 167 | */ 168 | public Node getNode() { 169 | return new Node(conf.getIP(), conf.getPort()); 170 | } 171 | 172 | private CompletableFuture compute(Group g) { 173 | ExchangeState state = stateMappings.get(g.getGroupId()); 174 | CompletableFuture future = new CompletableFuture<>(); 175 | if (g.getTreeNodes().size() == state.getRound() + 1) { 176 | conf.getLogger().debug(getNode().toString() + " Finishing Round: " + state.getRound()); 177 | BigInteger partial_key = state.getPartial_key().modPow(state.getSecret(), g.getPrime()); 178 | state.setPartial_key(partial_key); 179 | state.done(); 180 | future.complete(true); 181 | } else { 182 | Node n = g.getNext(conf.getNode()); 183 | BigInteger partial_key = state.getPartial_key().modPow(state.getSecret(), g.getPrime()); 184 | state.incRound(); 185 | state.setPartial_key(partial_key); 186 | conf.getLogger().debug(getNode().toString() + " Computing key: " + partial_key); 187 | future = sendMessage(n, MessageConstructor.roundInfo(state)); 188 | } 189 | return future.thenCompose(s -> state.getKey()); 190 | } 191 | 192 | /** 193 | * Get the Diffie-Hellman key of a group. The actual value of the key may 194 | * not be available right away, as it is dependent on the key exchange 195 | * process. Once this process finishes the key will be available. 196 | * CompletableFuture has many methods to deal with the asynchronous nature 197 | * of the result, such as blocking to wait for the result, returning 198 | * immediately with a default result, scheduling tasks for after the result 199 | * is available, and more. 200 | * 201 | * @param groupId 202 | * the id of the group 203 | * @return a Future representation of the key 204 | */ 205 | public CompletableFuture getKey(int groupId) { 206 | return stateMappings.get(groupId).getKey(); 207 | } 208 | 209 | private CompletableFuture broadcast(Group group) { 210 | CompletableFuture results[] = new CompletableFuture[group.getTreeNodes().size() - 1]; 211 | int i = 0; 212 | for (Node n : group.getTreeNodes()) { 213 | if (!n.equals(getNode())) { 214 | results[i] = sendMessage(n, MessageConstructor.groupInfo(group)); 215 | i++; 216 | } 217 | } 218 | return CompletableFuture.allOf(results); 219 | } 220 | 221 | private CompletableFuture sendMessage(Node n, JsonObject msg) { 222 | NetClientOptions options = new NetClientOptions(); 223 | options.setSendBufferSize(Constants.BUFFER_SIZE); 224 | 225 | NetClient tcpClient = vertx.createNetClient(options); 226 | 227 | CompletableFuture future = new CompletableFuture<>(); 228 | Long[] timingAndRetries = new Long[2]; 229 | for (int t = 0; t < timingAndRetries.length; t++) 230 | timingAndRetries[t] = Long.valueOf("0"); 231 | 232 | timingAndRetries[0] = vertx.setPeriodic(Constants.SEND_RETRY_TIMEOUT, ((Long myLong) -> { 233 | tcpClient.connect(Integer.parseInt(n.getPort()), n.getIP(), ((AsyncResult result) -> { 234 | NetSocket socket = result.result(); 235 | if (socket != null) { 236 | socket.handler((Buffer buffer) -> { 237 | String reply = buffer.getString(0, buffer.length()); 238 | if (reply.equals(Constants.ACK)) { 239 | conf.getLogger().debug(getNode().toString() + " Got an ack from " + n.toString()); 240 | future.complete(true); 241 | vertx.cancelTimer(timingAndRetries[0]); 242 | socket.close(); 243 | tcpClient.close(); 244 | } 245 | 246 | }); 247 | conf.getLogger().debug(getNode().toString() + " " + Constants.LOG_OUT + " to: " + 248 | n.toString() + " " + msg.toString()); 249 | socket.write(msg.toString()); 250 | } 251 | timingAndRetries[1]++; 252 | if (timingAndRetries[1] == conf.getRetries()) { 253 | // No more retries left. Exit... 254 | conf.getLogger().error(getNode().toString() + " Retry parameter exceeded " + conf.getRetries()); 255 | future.completeExceptionally( 256 | new TimeoutException(Constants.EXCEPTIONRETRIESEXCEEDED + conf.getRetries())); 257 | if (socket != null) 258 | socket.close(); 259 | vertx.cancelTimer(timingAndRetries[0]); 260 | tcpClient.close(); 261 | server.close(); 262 | } 263 | })); 264 | })); 265 | return future; 266 | } 267 | 268 | @Override 269 | public void stop(Future future) throws Exception { 270 | server.close(res -> { 271 | if (res.succeeded()) { 272 | future.complete(); 273 | conf.getLogger().info(getNode().toString() + " stopped listening on: " + conf.getPort()); 274 | } else { 275 | future.fail(res.cause()); 276 | conf.getLogger().info(getNode().toString() + " stoppage failure: " + conf.getPort() + res.cause().getMessage()); 277 | } 278 | }); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/main/Group.java: -------------------------------------------------------------------------------- 1 | package com.gdh.main; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.Iterator; 7 | import java.util.SortedSet; 8 | import java.util.TreeSet; 9 | 10 | /** 11 | * 12 | * Group is an object which represents a group of participants(Nodes) in a 13 | * Diffie-Hellman key exchange. 14 | * 15 | * It is generally identified by its groupId which is generated from the hash 16 | * code of its Nodes. 17 | * 18 | * The Nodes are organized in a TreeSet to create a total lexicographical order. 19 | * 20 | * @author Max Amelchenko 21 | */ 22 | public class Group { 23 | private int groupId; 24 | private TreeSet treeNodes; // keep order among nodes 25 | private BigInteger generator; 26 | private BigInteger prime; 27 | 28 | public Group(Configuration conf, Node... nodes) { 29 | initGroup(conf, Arrays.asList(nodes)); 30 | } 31 | 32 | public Group(Configuration conf, Collection nodes) { 33 | initGroup(conf, nodes); 34 | } 35 | 36 | public Group(String gen, String prime, Collection nodes) { 37 | Configuration conf = new Configuration(); 38 | conf.setGenerator(gen).setPrime(prime); 39 | initGroup(conf, nodes); 40 | } 41 | 42 | /** 43 | * Initiate a group with a Configuration and a collection of Nodes 44 | * 45 | * @param conf 46 | * The Configuration of this Group 47 | * @param nodes 48 | * The participants of this Group 49 | */ 50 | private void initGroup(Configuration conf, Collection nodes) { 51 | treeNodes = new TreeSet<>(); 52 | for (Node n : nodes) 53 | treeNodes.add(n); 54 | 55 | generator = new BigInteger(conf.getGenerator(), 16); 56 | prime = new BigInteger(conf.getPrime(), 16); 57 | groupId = hashCode(); 58 | } 59 | 60 | public int getGroupId() { 61 | return groupId; 62 | } 63 | 64 | /** 65 | * Get a node that represents a GDHVertex which will serve as a key exchange initiator. 66 | * Due to the total order imposed on the group, it will always return the same node, regardless of the calling instance. 67 | * 68 | * @return the smallest lexicographical node in the group 69 | */ 70 | public Node getActiveNode() { 71 | return treeNodes.first(); 72 | } 73 | 74 | public SortedSet getTreeNodes() { 75 | return new TreeSet<>(treeNodes); 76 | } 77 | 78 | @Override 79 | public final int hashCode() { 80 | final int primal = 31; 81 | int result = 1; 82 | result = primal * result + ((treeNodes == null) ? 0 : treeNodes.hashCode()); 83 | return result; 84 | } 85 | 86 | /** 87 | * Checks for the equality of this Group and the obj group. Two Groups are 88 | * considered equal if they are the same object or if they consist of the 89 | * same Nodes. This is done because a Group is essentially the Nodes it 90 | * represents. 91 | */ 92 | @Override 93 | public boolean equals(Object obj) { 94 | if (this == obj) 95 | return true; 96 | if (obj == null) 97 | return false; 98 | if (getClass() != obj.getClass()) 99 | return false; 100 | Group other = (Group) obj; 101 | if (treeNodes == null) { 102 | if (other.treeNodes != null) 103 | return false; 104 | } else if (!treeNodes.equals(other.treeNodes)) 105 | return false; 106 | return true; 107 | } 108 | 109 | public void setGenerator(BigInteger g) { 110 | this.generator = g; 111 | } 112 | 113 | public void setPrime(BigInteger N) { 114 | this.prime = N; 115 | } 116 | 117 | public BigInteger getGenerator() { 118 | return generator; 119 | } 120 | 121 | public BigInteger getPrime() { 122 | return prime; 123 | } 124 | 125 | /** 126 | * Get the Node which succeeds the parameter Node. Node Beta succeeds Node 127 | * Alpha if during the Diffie-Hellman key exchange Node Alpha sends messages 128 | * to Node Beta. There can only be one successor per Node. 129 | * 130 | * @param curr 131 | * The Node for which a successor will be returned 132 | * @return the Node succeeding the Node curr 133 | */ 134 | public Node getNext(Node curr) { 135 | Iterator iter = treeNodes.iterator(); 136 | while (iter.hasNext()) { 137 | Node n = iter.next(); 138 | if (n.equals(curr)) { 139 | if (n.equals(treeNodes.last())) 140 | return treeNodes.first(); 141 | 142 | return iter.next(); 143 | } 144 | } 145 | return null; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/main/Node.java: -------------------------------------------------------------------------------- 1 | package com.gdh.main; 2 | 3 | /** 4 | * 5 | * Node is an object which represents the network layer of a GDHVertex. 6 | * 7 | * It has an IP and port which must be unique in any verticle layout. No two 8 | * verticle instances can listen on 9 | * 10 | * the same IP and port. 11 | * 12 | * @author Max Amelchenko 13 | */ 14 | public class Node implements Comparable { 15 | private final String IP; 16 | private final String port; 17 | 18 | public Node(String IP, String port) { 19 | this.IP = IP; 20 | this.port = port; 21 | } 22 | 23 | public String getIP() { 24 | return IP; 25 | } 26 | 27 | public String getPort() { 28 | return port; 29 | } 30 | 31 | @Override 32 | public int compareTo(Node o) { 33 | if (this.getIP().compareTo(o.getIP()) == 0) 34 | return (this.getPort().compareTo(o.getPort())); 35 | return this.getIP().compareTo(o.getIP()); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | final int prime = 31; 41 | int result = 1; 42 | result = prime * result + ((IP == null) ? 0 : IP.hashCode()); 43 | result = prime * result + ((port == null) ? 0 : port.hashCode()); 44 | return result; 45 | } 46 | 47 | @Override 48 | public boolean equals(Object obj) { 49 | if (this == obj) 50 | return true; 51 | if (obj == null) 52 | return false; 53 | if (getClass() != obj.getClass()) 54 | return false; 55 | Node other = (Node) obj; 56 | if (IP == null) { 57 | if (other.IP != null) 58 | return false; 59 | } else if (!IP.equals(other.IP)) 60 | return false; 61 | if (port == null) { 62 | if (other.port != null) 63 | return false; 64 | } else if (!port.equals(other.port)) 65 | return false; 66 | return true; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "Node [IP=" + IP + ", port=" + port + "]"; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/main/PrimaryVertex.java: -------------------------------------------------------------------------------- 1 | package com.gdh.main; 2 | 3 | import io.vertx.core.AsyncResult; 4 | import io.vertx.core.Future; 5 | import io.vertx.core.Handler; 6 | import io.vertx.core.Vertx; 7 | 8 | public class PrimaryVertex { 9 | private final Vertx vertx = Vertx.vertx(); 10 | 11 | /** 12 | * Deploy a gdh verticle 13 | * 14 | * @param gdh 15 | * the GDHVertex to be deployed 16 | */ 17 | public void run(GDHVertex gdh) { 18 | vertx.deployVerticle(gdh); 19 | } 20 | 21 | /** 22 | * Deploy a gdh verticle and register a handler for the result 23 | * 24 | * @param gdh 25 | * the GDHVertex to be deployed 26 | * @param aHandler 27 | * the handler which handles the deployment 28 | */ 29 | public void run(GDHVertex gdh, Handler> aHandler) { 30 | vertx.deployVerticle(gdh, deployment -> { 31 | if (deployment.succeeded()) { 32 | aHandler.handle(Future.succeededFuture(gdh.deploymentID())); 33 | } else { 34 | aHandler.handle(Future.failedFuture("Deployment Failure!")); 35 | } 36 | }); 37 | } 38 | 39 | /** 40 | * 41 | * Undeploy a gdh verticle 42 | * 43 | * @param gdh 44 | * the GDHVertex to be undeployed 45 | */ 46 | public void kill(GDHVertex gdh) { 47 | vertx.undeploy(gdh.deploymentID()); 48 | } 49 | 50 | /** 51 | * 52 | * Undeploy a gdh verticle and register a handler for the result 53 | * 54 | * @param gdh 55 | * the GDHVertex to be undeployed 56 | * @param aHandler 57 | * the handler which handles the undeployment 58 | */ 59 | public void kill(GDHVertex gdh, Handler> aHandler) { 60 | vertx.undeploy(gdh.deploymentID(), undeployment -> { 61 | if (undeployment.succeeded()) { 62 | aHandler.handle(Future.succeededFuture(gdh.deploymentID())); 63 | } else { 64 | aHandler.handle(Future.failedFuture("Undeployment Failure!")); 65 | } 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/parser/JsonMessageParser.java: -------------------------------------------------------------------------------- 1 | package com.gdh.parser; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | import java.util.TreeSet; 7 | 8 | import io.vertx.core.json.JsonArray; 9 | import io.vertx.core.json.JsonObject; 10 | import com.gdh.main.Constants; 11 | import com.gdh.main.ExchangeState; 12 | import com.gdh.main.Group; 13 | import com.gdh.main.Node; 14 | 15 | /** 16 | * 17 | * JsonMessageParser is a class for parsing json messages 18 | * 19 | * Parses two types of messages: 20 | * 21 | * Messages relaying the public parameters of the group, e.g. large prime (N) and cyclic generator (g) 22 | * 23 | * and messages relaying the partial keys computed during the key exchange. 24 | * 25 | * @author Max Amelchenko 26 | */ 27 | public class JsonMessageParser implements MessageParser { 28 | 29 | private final Map groupMappings; 30 | private final Map stateMappings; 31 | 32 | public JsonMessageParser(Map groupMappings, Map stateMappings) { 33 | this.groupMappings = groupMappings; 34 | this.stateMappings = stateMappings; 35 | } 36 | 37 | /** 38 | * @param msg 39 | * the information to be parsed 40 | * @return the group id of the group contained in the message or error code 41 | */ 42 | @Override 43 | public int parse(String msg) { 44 | if (msg.contains(Constants.ROUND)) 45 | return extractRoundInfo(msg); 46 | return extractGroupInfo(msg); 47 | } 48 | 49 | /** 50 | * 51 | * @param msg 52 | * the group details in json format to be parsed 53 | * @return the group id of the group contained in the message 54 | * -2 if this message has already been received 55 | */ 56 | private int extractGroupInfo(String msg) { 57 | TreeSet set = new TreeSet<>(); 58 | Group group = null; 59 | JsonObject obj = new JsonObject(msg); 60 | String prime = obj.getString(Constants.PRIME); 61 | String generator = obj.getString(Constants.GENERATOR); 62 | JsonArray members = obj.getJsonArray(Constants.MEMBERS); 63 | Iterator iter = members.iterator(); 64 | while (iter.hasNext()) { 65 | JsonObject member = (JsonObject) iter.next(); 66 | String ip = member.getString(Constants.IP); 67 | String port = member.getString(Constants.PORT); 68 | Node n = new Node(ip, port); 69 | set.add(n); 70 | } 71 | group = new Group(generator, prime, set); 72 | if (stateMappings.containsKey(group.getGroupId()) && !stateMappings.get(group.getGroupId()).isDone()) 73 | return -2; // message received twice 74 | group.setGenerator(new BigInteger(generator)); 75 | group.setPrime(new BigInteger(prime)); 76 | groupMappings.put(group.getGroupId(), group); 77 | stateMappings.put(group.getGroupId(), new ExchangeState(group.getGroupId(), group.getGenerator())); 78 | return group.getGroupId(); 79 | } 80 | 81 | /** 82 | * Get the info about the current round of the key exchange. These include 83 | * the id of the group exchanging keys and the partial key computed by this 84 | * Node's counterpart. This partial key will be used by this Node for 85 | * further computation. 86 | * 87 | * @param msg 88 | * the round details in json format to be parsed 89 | * @return the group id of the group contained in the message 90 | * -1 if the state does not exist yet, which means the group info was not received yet 91 | * -2 if this message has already been received 92 | */ 93 | private int extractRoundInfo(String msg) { 94 | JsonObject obj = new JsonObject(msg); 95 | String groupId = obj.getString(Constants.GROUPID); 96 | int ret = Integer.parseInt(groupId); 97 | String partial_key = obj.getString(Constants.PARTIAL_KEY); 98 | ExchangeState state = stateMappings.get(ret); 99 | String round = obj.getString(Constants.ROUND); 100 | if (state == null) // State does not exist 101 | return -1; 102 | if (state.getRound() == Integer.parseInt(round) - 1) // message received twice 103 | return -2; 104 | state.setPartial_key(new BigInteger(partial_key)); 105 | return ret; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/parser/MessageConstructor.java: -------------------------------------------------------------------------------- 1 | package com.gdh.parser; 2 | 3 | import io.vertx.core.json.JsonArray; 4 | import io.vertx.core.json.JsonObject; 5 | import com.gdh.main.Constants; 6 | import com.gdh.main.ExchangeState; 7 | import com.gdh.main.Group; 8 | import com.gdh.main.Node; 9 | 10 | /** 11 | * 12 | * @author Max Amelchenko 13 | * 14 | * MessageConstructor is a class for constructing json messages 15 | * 16 | */ 17 | public final class MessageConstructor { 18 | private MessageConstructor() { 19 | 20 | } 21 | 22 | /** 23 | * Construct a json object from the group parameter 24 | * 25 | * @param g 26 | * the group which needs to be jsonized 27 | * @return a json object representing the group 28 | */ 29 | public static JsonObject groupInfo(Group g) { 30 | JsonObject msg = new JsonObject(); 31 | msg.put(Constants.PRIME, g.getPrime().toString()); 32 | msg.put(Constants.GENERATOR, g.getGenerator().toString()); 33 | 34 | JsonArray members = new JsonArray(); 35 | for (Node n : g.getTreeNodes()) { 36 | JsonObject obj = new JsonObject(); 37 | obj.put(Constants.IP, n.getIP()); 38 | obj.put(Constants.PORT, n.getPort()); 39 | members.add(obj); 40 | } 41 | msg.put(Constants.MEMBERS, members); 42 | return msg; 43 | } 44 | 45 | /** 46 | * Construct a json object from the ExchangeState parameter 47 | * 48 | * @param state 49 | * the ExchangeState which needs to be jsonized 50 | * @return a json object representing ExchangeState 51 | */ 52 | public static JsonObject roundInfo(ExchangeState state) { 53 | JsonObject msg = new JsonObject(); 54 | msg.put(Constants.GROUPID, String.valueOf(state.getGroupId())); 55 | msg.put(Constants.ROUND, String.valueOf(state.getRound())); 56 | msg.put(Constants.PARTIAL_KEY, state.getPartial_key().toString()); 57 | return msg; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/gdh/parser/MessageParser.java: -------------------------------------------------------------------------------- 1 | package com.gdh.parser; 2 | 3 | public interface MessageParser { 4 | public int parse(String msg); 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/AsyncKeyExchangeTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.math.BigInteger; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.concurrent.CompletableFuture; 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.stream.Collectors; 10 | 11 | import org.apache.log4j.Level; 12 | import org.junit.Assert; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | import io.vertx.ext.unit.Async; 17 | import io.vertx.ext.unit.TestContext; 18 | import io.vertx.ext.unit.junit.VertxUnitRunner; 19 | import com.gdh.main.Configuration; 20 | import com.gdh.main.GDHVertex; 21 | import com.gdh.main.Group; 22 | import com.gdh.main.PrimaryVertex; 23 | 24 | @RunWith(VertxUnitRunner.class) 25 | public class AsyncKeyExchangeTest { 26 | 27 | @Test 28 | public void testDoubleKeyExchange(TestContext context) { 29 | int amount = 2; 30 | testAsyncNegotiation(amount, context); 31 | } 32 | 33 | @Test 34 | public void testTripleKeyExchange(TestContext context) { 35 | int amount = 3; 36 | testAsyncNegotiation(amount, context); 37 | } 38 | 39 | @Test 40 | public void testQuadrupleKeyExchange(TestContext context) { 41 | int amount = 4; 42 | testAsyncNegotiation(amount, context); 43 | } 44 | 45 | // real deployment and communication between verticles on localhost 46 | private void testAsyncNegotiation(int amount, TestContext context) { 47 | PrimaryVertex pv = new PrimaryVertex(); 48 | GDHVertex[] verticles = new GDHVertex[amount]; 49 | Configuration[] confs = new Configuration[amount]; 50 | 51 | for (int i = 0; i < amount; i++) { 52 | verticles[i] = new GDHVertex(); 53 | confs[i] = new Configuration(); 54 | String port = (amount * 2) + "09" + i; 55 | confs[i].setIP("localhost").setPort(port).setLogLevel(Level.DEBUG); 56 | verticles[i].setConfiguration(confs[i]); 57 | } 58 | List list = new ArrayList<>(Arrays.asList(verticles)); 59 | 60 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 61 | verticles[0].addGroup(g); 62 | 63 | Async async1 = context.async(amount); 64 | for (int i = 0; i < amount; i++) { 65 | pv.run(verticles[i], res -> { 66 | if (res.failed()) { 67 | res.cause().printStackTrace(); 68 | return; 69 | } else 70 | async1.countDown(); 71 | }); 72 | } 73 | async1.awaitSuccess(); 74 | 75 | Async async2 = context.async(1); 76 | CompletableFuture key = verticles[0].exchange(g.getGroupId(), result -> { 77 | Assert.assertTrue(result.succeeded()); 78 | async2.countDown(); 79 | }); 80 | async2.awaitSuccess(); 81 | 82 | for (int j = 0; j < amount; j++) 83 | try { 84 | Assert.assertTrue(verticles[j].getKey(g.getGroupId()).get().equals(key.get())); 85 | } catch (InterruptedException | ExecutionException e) { 86 | // TODO Auto-generated catch block 87 | e.printStackTrace(); 88 | } 89 | 90 | Async async3 = context.async(amount); 91 | for (int i = 0; i < amount; i++) 92 | pv.kill(verticles[i], res -> { 93 | if (res.failed()) { 94 | res.cause().printStackTrace(); 95 | } else 96 | async3.countDown(); 97 | }); 98 | async3.awaitSuccess(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/ConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import org.apache.log4j.ConsoleAppender; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | import com.gdh.main.Configuration; 8 | 9 | @SuppressWarnings("PMD.AvoidUsingHardCodedIP") 10 | public class ConfigurationTest { 11 | @Test 12 | public void testConfigurationFile() { 13 | Configuration conf = Configuration.readConfigFile("configuration"); 14 | Assert.assertTrue(conf.equals(new Configuration())); 15 | } 16 | 17 | @Test 18 | public void testConfigurationPort() { 19 | Configuration conf = new Configuration(); 20 | conf = conf.setPort("3333"); 21 | Assert.assertTrue(conf.getPort().equals("3333")); 22 | } 23 | 24 | @Test(expected = AssertionError.class) 25 | public void testConfigurationPortFail1() { 26 | Configuration conf = new Configuration(); 27 | conf = conf.setPort("333a"); 28 | } 29 | 30 | @Test(expected = AssertionError.class) 31 | public void testConfigurationPortFail2() { 32 | Configuration conf = new Configuration(); 33 | conf = conf.setPort(""); 34 | } 35 | 36 | @Test 37 | public void testConfigurationRetries() { 38 | Configuration conf = new Configuration(); 39 | conf = conf.setRetries(10); 40 | Assert.assertTrue(conf.getRetries() == 10); 41 | } 42 | 43 | @Test 44 | public void testConfigurationIP() { 45 | Configuration conf = new Configuration(); 46 | conf = conf.setIP("1.1.1.1"); 47 | Assert.assertTrue(conf.getIP().equals("1.1.1.1")); 48 | } 49 | 50 | @Test 51 | public void testConfigurationIPLocalhost() { 52 | Configuration conf = new Configuration(); 53 | conf = conf.setIP("localhost"); 54 | Assert.assertTrue(conf.getIP().equals("localhost")); 55 | } 56 | 57 | @Test(expected = AssertionError.class) 58 | public void testConfigurationIPFail1() { 59 | Configuration conf = new Configuration(); 60 | conf = conf.setIP("1.1.1.a"); 61 | } 62 | 63 | @Test(expected = AssertionError.class) 64 | public void testConfigurationIPFail2() { 65 | Configuration conf = new Configuration(); 66 | conf = conf.setIP("1.1.1"); 67 | } 68 | 69 | @Test(expected = AssertionError.class) 70 | public void testConfigurationIPFail3() { 71 | Configuration conf = new Configuration(); 72 | conf = conf.setIP("1.1.1.1111"); 73 | } 74 | 75 | @Test 76 | public void testConfigurationPrime() { 77 | Configuration conf = new Configuration(); 78 | conf = conf.setPrime("AFB239DE"); 79 | Assert.assertTrue(conf.getPrime().equals("AFB239DE")); 80 | } 81 | 82 | @Test(expected = AssertionError.class) 83 | public void testConfigurationPrimeFail1() { 84 | Configuration conf = new Configuration(); 85 | conf = conf.setPrime("AFB239DE88G"); 86 | } 87 | 88 | @Test(expected = AssertionError.class) 89 | public void testConfigurationPrimeFail2() { 90 | Configuration conf = new Configuration(); 91 | conf = conf.setPrime("AFB239DE88a"); 92 | } 93 | 94 | @Test 95 | public void testEquality1() { 96 | Configuration conf1 = new Configuration(); 97 | Configuration conf2 = new Configuration(); 98 | 99 | conf1 = conf1.setRetries(3); 100 | conf2 = conf2.setRetries(4); 101 | Assert.assertTrue(conf1.hashCode() == conf2.hashCode()); 102 | Assert.assertTrue(conf1.equals(conf2)); 103 | 104 | conf1 = conf1.setIP("1.1.1.1"); 105 | conf2 = conf2.setIP("2.2.2.2"); 106 | Assert.assertFalse(conf1.hashCode() == conf2.hashCode()); 107 | Assert.assertFalse(conf1.equals(conf2)); 108 | } 109 | 110 | @Test 111 | public void testEquality2() { 112 | Configuration conf1 = new Configuration(); 113 | Configuration conf2 = new Configuration(); 114 | 115 | conf1 = conf1.setExchangeTimeoutMillis(3000); 116 | conf2 = conf2.setExchangeTimeoutMillis(4000); 117 | Assert.assertTrue(conf1.hashCode() == conf2.hashCode()); 118 | Assert.assertTrue(conf1.equals(conf2)); 119 | 120 | conf1 = conf1.setPort("1111"); 121 | conf2 = conf2.setPort("2222"); 122 | Assert.assertFalse(conf1.hashCode() == conf2.hashCode()); 123 | Assert.assertFalse(conf1.equals(conf2)); 124 | } 125 | 126 | @Test 127 | public void testEquality3() { 128 | Configuration conf1 = new Configuration(); 129 | Configuration conf2 = new Configuration(); 130 | 131 | conf1 = conf1.setExchangeTimeoutMillis(3000); 132 | conf2 = conf2.setRetries(7); 133 | Assert.assertTrue(conf1.hashCode() == conf2.hashCode()); 134 | Assert.assertTrue(conf1.equals(conf2)); 135 | 136 | conf1 = conf1.setGenerator("111111"); 137 | conf2 = conf2.setGenerator("222222"); 138 | Assert.assertFalse(conf1.hashCode() == conf2.hashCode()); 139 | Assert.assertFalse(conf1.equals(conf2)); 140 | } 141 | 142 | @Test 143 | public void testEquality4() { 144 | Configuration conf1 = new Configuration(); 145 | Configuration conf2 = new Configuration(); 146 | 147 | conf1 = conf1.setAppender(null); 148 | conf2 = conf2.setAppender(new ConsoleAppender()); 149 | Assert.assertTrue(conf1.hashCode() == conf2.hashCode()); 150 | Assert.assertTrue(conf1.equals(conf2)); 151 | 152 | conf1 = conf1.setPrime("111111"); 153 | conf2 = conf2.setPrime("222222"); 154 | Assert.assertFalse(conf1.hashCode() == conf2.hashCode()); 155 | Assert.assertFalse(conf1.equals(conf2)); 156 | } 157 | 158 | @Test 159 | public void testEquality5() { 160 | Configuration conf1 = new Configuration(); 161 | Configuration conf2 = conf1; 162 | 163 | Assert.assertTrue(conf1.equals(conf2)); 164 | 165 | conf2 = null; 166 | Assert.assertFalse(conf1.equals(conf2)); 167 | } 168 | } -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/EncDecTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.Random; 5 | 6 | import javax.crypto.SecretKey; 7 | import javax.crypto.spec.SecretKeySpec; 8 | 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | 12 | import com.gdh.crypto.CipherAgent; 13 | import com.gdh.crypto.CipherAgentImpl; 14 | 15 | public class EncDecTest { 16 | @Test 17 | public void testEncryption1() { 18 | String algo = "AES/CBC/PKCS5Padding"; 19 | byte[] iv = new byte[16]; 20 | Random random = new SecureRandom(); 21 | random.nextBytes(iv); 22 | 23 | byte[] key = new byte[16]; 24 | random.nextBytes(key); 25 | SecretKey seckey = new SecretKeySpec(key, "AES"); 26 | 27 | String dec = null; 28 | try { 29 | CipherAgent agent = new CipherAgentImpl(algo); 30 | byte[] enc = agent.encrypt(algo, iv, seckey); 31 | dec = agent.decrypt(enc, iv, seckey); 32 | } catch (Exception e) { 33 | Assert.fail(e.getMessage()); 34 | } 35 | 36 | Assert.assertTrue(algo.equals(dec)); 37 | } 38 | 39 | @Test 40 | public void testEncryption2() { 41 | String algo = "AES/OFB/PKCS5Padding"; 42 | byte[] iv = new byte[16]; 43 | Random random = new SecureRandom(); 44 | random.nextBytes(iv); 45 | 46 | byte[] key = new byte[16]; 47 | random.nextBytes(key); 48 | SecretKey seckey = new SecretKeySpec(key, "AES"); 49 | 50 | String dec = null; 51 | try { 52 | CipherAgent agent = new CipherAgentImpl(algo); 53 | byte[] enc = agent.encrypt(algo, iv, seckey); 54 | dec = agent.decrypt(enc, iv, seckey); 55 | } catch (Exception e) { 56 | Assert.fail(e.getMessage()); 57 | } 58 | 59 | Assert.assertTrue(algo.equals(dec)); 60 | } 61 | 62 | @Test 63 | public void testEncryption3() { 64 | String algo = "AES/CFB/NoPadding"; 65 | byte[] iv = new byte[16]; 66 | Random random = new SecureRandom(); 67 | random.nextBytes(iv); 68 | 69 | byte[] key = new byte[16]; 70 | random.nextBytes(key); 71 | SecretKey seckey = new SecretKeySpec(key, "AES"); 72 | 73 | String dec = null; 74 | try { 75 | CipherAgent agent = new CipherAgentImpl(algo); 76 | byte[] enc = agent.encrypt(algo, iv, seckey); 77 | dec = agent.decrypt(enc, iv, seckey); 78 | } catch (Exception e) { 79 | Assert.fail(e.getMessage()); 80 | } 81 | Assert.assertTrue(algo.equals(dec)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/ExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.math.BigInteger; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.concurrent.CompletableFuture; 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.stream.Collectors; 10 | 11 | import org.apache.log4j.Level; 12 | import org.junit.Assert; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | import io.vertx.ext.unit.Async; 17 | import io.vertx.ext.unit.TestContext; 18 | import io.vertx.ext.unit.junit.VertxUnitRunner; 19 | import com.gdh.main.Configuration; 20 | import com.gdh.main.Constants; 21 | import com.gdh.main.GDHVertex; 22 | import com.gdh.main.Group; 23 | import com.gdh.main.PrimaryVertex; 24 | 25 | @RunWith(VertxUnitRunner.class) 26 | public class ExceptionTest { 27 | private static final String localhost = "localhost"; 28 | 29 | @Test(expected = ExecutionException.class) 30 | public void testVerticleDownTimeout(TestContext context) throws InterruptedException, ExecutionException { 31 | int amount = 2; 32 | PrimaryVertex pv = new PrimaryVertex(); 33 | GDHVertex[] verticles = new GDHVertex[amount]; 34 | Configuration[] confs = new Configuration[amount]; 35 | 36 | for (int i = 0; i < amount; i++) { 37 | verticles[i] = new GDHVertex(); 38 | confs[i] = new Configuration(); 39 | String port = amount + "08" + i; 40 | confs[i].setIP(localhost).setPort(port).setLogLevel(Level.DEBUG).setExchangeTimeoutMillis(5000); 41 | verticles[i].setConfiguration(confs[i]); 42 | } 43 | List list = new ArrayList<>(Arrays.asList(verticles)); 44 | 45 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 46 | verticles[0].addGroup(g); 47 | Async async = context.async(); 48 | for (int i = 0; i < amount - 1; i++) 49 | pv.run(verticles[i], res -> { 50 | if (res.succeeded()) { 51 | async.countDown(); 52 | } else { 53 | res.cause().printStackTrace(); 54 | return; 55 | } 56 | }); 57 | async.awaitSuccess(); 58 | 59 | try { 60 | BigInteger key = verticles[0].exchange(g.getGroupId()).get(); 61 | for (int j = 0; j < verticles.length; j++) { 62 | Assert.assertEquals(verticles[j].getKey(g.getGroupId()).get(), key); 63 | } 64 | } catch (ExecutionException e) { 65 | Async async2 = context.async(amount - 1); 66 | for (int i = 0; i < amount - 1; i++) 67 | pv.kill(verticles[i], res -> { 68 | if (res.succeeded()) { 69 | async2.countDown(); 70 | } 71 | }); 72 | async2.awaitSuccess(); 73 | throw e; 74 | } 75 | } 76 | 77 | @Test(expected = ExecutionException.class) 78 | public void testVerticleDownRetries(TestContext context) throws InterruptedException, ExecutionException { 79 | int amount = 2; 80 | PrimaryVertex pv = new PrimaryVertex(); 81 | GDHVertex[] verticles = new GDHVertex[amount]; 82 | Configuration[] confs = new Configuration[amount]; 83 | 84 | for (int i = 0; i < amount; i++) { 85 | verticles[i] = new GDHVertex(); 86 | confs[i] = new Configuration(); 87 | String port = amount + "08" + i; 88 | confs[i].setIP(localhost).setPort(port).setLogLevel(Level.DEBUG).setExchangeTimeoutMillis(60000); 89 | verticles[i].setConfiguration(confs[i]); 90 | } 91 | List list = new ArrayList<>(Arrays.asList(verticles)); 92 | 93 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 94 | verticles[0].addGroup(g); 95 | 96 | Async async = context.async(amount - 1); 97 | for (int i = 0; i < amount - 1; i++) 98 | pv.run(verticles[i], res -> { 99 | if (res.succeeded()) { 100 | async.countDown(); 101 | } else { 102 | res.cause().printStackTrace(); 103 | return; 104 | } 105 | }); 106 | async.awaitSuccess(); 107 | 108 | try { 109 | BigInteger key = verticles[0].exchange(g.getGroupId()).get(); 110 | for (int j = 0; j < verticles.length; j++) { 111 | Assert.assertEquals(verticles[j].getKey(g.getGroupId()).get(), key); 112 | } 113 | } catch (ExecutionException e) { 114 | Async async2 = context.async(amount - 1); 115 | for (int i = 0; i < amount - 1; i++) 116 | pv.kill(verticles[i], res -> { 117 | if (res.succeeded()) { 118 | async2.countDown(); 119 | } else { 120 | res.cause().printStackTrace(); 121 | System.out.println("WELL WELL..."); 122 | } 123 | }); 124 | async2.awaitSuccess(); 125 | throw e; 126 | } 127 | } 128 | 129 | @Test 130 | public void testVerticleDownRetriesAsync(TestContext context) throws InterruptedException, ExecutionException { 131 | int amount = 2; 132 | PrimaryVertex pv = new PrimaryVertex(); 133 | GDHVertex[] verticles = new GDHVertex[amount]; 134 | Configuration[] confs = new Configuration[amount]; 135 | 136 | for (int i = 0; i < amount; i++) { 137 | verticles[i] = new GDHVertex(); 138 | confs[i] = new Configuration(); 139 | String port = amount + "08" + i; 140 | confs[i].setIP(localhost).setPort(port).setLogLevel(Level.DEBUG).setExchangeTimeoutMillis(60000); 141 | verticles[i].setConfiguration(confs[i]); 142 | } 143 | List list = new ArrayList<>(Arrays.asList(verticles)); 144 | 145 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 146 | verticles[0].addGroup(g); 147 | 148 | Async async = context.async(); 149 | for (int i = 0; i < amount - 1; i++) 150 | pv.run(verticles[i], res -> { 151 | if (res.succeeded()) { 152 | async.countDown(); 153 | } else { 154 | res.cause().printStackTrace(); 155 | return; 156 | } 157 | }); 158 | async.awaitSuccess(); 159 | 160 | Async async3 = context.async(); 161 | verticles[0].exchange(g.getGroupId(), res -> { 162 | Assert.assertTrue(res.failed()); 163 | Assert.assertTrue(res.cause().getMessage().contains(Constants.EXCEPTIONRETRIESEXCEEDED)); 164 | async3.complete(); 165 | }); 166 | async3.awaitSuccess(); 167 | 168 | Async async2 = context.async(); 169 | for (int i = 0; i < amount - 1; i++) 170 | pv.kill(verticles[i], res -> { 171 | if (res.succeeded()) { 172 | async2.countDown(); 173 | } 174 | }); 175 | async2.awaitSuccess(); 176 | } 177 | 178 | @Test 179 | public void testVerticleDownTimeoutAsync(TestContext context) throws InterruptedException, ExecutionException { 180 | Async async = context.async(); 181 | int amount = 2; 182 | PrimaryVertex pv = new PrimaryVertex(); 183 | GDHVertex[] verticles = new GDHVertex[amount]; 184 | Configuration[] confs = new Configuration[amount]; 185 | 186 | for (int i = 0; i < amount; i++) { 187 | verticles[i] = new GDHVertex(); 188 | confs[i] = new Configuration(); 189 | String port = amount + "08" + i; 190 | confs[i].setIP(localhost).setPort(port).setLogLevel(Level.DEBUG).setExchangeTimeoutMillis(5000); 191 | verticles[i].setConfiguration(confs[i]); 192 | } 193 | List list = new ArrayList<>(Arrays.asList(verticles)); 194 | 195 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 196 | verticles[0].addGroup(g); 197 | 198 | for (int i = 0; i < amount - 1; i++) 199 | pv.run(verticles[i], res -> { 200 | if (res.succeeded()) { 201 | async.countDown(); 202 | } else { 203 | res.cause().printStackTrace(); 204 | return; 205 | } 206 | }); 207 | async.awaitSuccess(); 208 | 209 | Async async3 = context.async(); 210 | verticles[0].exchange(g.getGroupId(), res -> { 211 | Assert.assertTrue(res.failed()); 212 | Assert.assertTrue(res.cause().getMessage().contains(Constants.EXCEPTIONTIMEOUTEXCEEDED)); 213 | async3.complete(); 214 | }); 215 | async3.awaitSuccess(); 216 | 217 | Async async2 = context.async(amount - 1); 218 | for (int i = 0; i < amount - 1; i++) 219 | pv.kill(verticles[i], res -> { 220 | if (res.succeeded()) { 221 | async2.countDown(); 222 | } 223 | }); 224 | async2.awaitSuccess(); 225 | } 226 | 227 | @Test(expected = ExecutionException.class) 228 | public void testVerticleCrash(TestContext context) throws InterruptedException, ExecutionException { 229 | int amount = 2; 230 | PrimaryVertex pv = new PrimaryVertex(); 231 | GDHVertex[] verticles = new GDHVertex[amount]; 232 | Configuration[] confs = new Configuration[amount]; 233 | 234 | for (int i = 0; i < amount; i++) { 235 | verticles[i] = new GDHVertex(); 236 | confs[i] = new Configuration(); 237 | String port = amount + "08" + i; 238 | confs[i].setIP(localhost).setPort(port).setLogLevel(Level.DEBUG).setExchangeTimeoutMillis(5000); 239 | verticles[i].setConfiguration(confs[i]); 240 | } 241 | List list = new ArrayList<>(Arrays.asList(verticles)); 242 | 243 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 244 | verticles[0].addGroup(g); 245 | 246 | Async async = context.async(amount); 247 | for (int i = 0; i < amount; i++) 248 | pv.run(verticles[i], res -> { 249 | if (res.succeeded()) { 250 | async.countDown(); 251 | } 252 | }); 253 | async.awaitSuccess(); 254 | 255 | CompletableFuture bigint = verticles[0].exchange(g.getGroupId()); 256 | Thread.sleep(1000); 257 | pv.kill(verticles[amount - 1]); 258 | 259 | Async async2 = context.async(amount - 1); 260 | try { 261 | bigint.get(); 262 | } catch (ExecutionException e) { 263 | for (int i = 0; i < amount - 1; i++) 264 | pv.kill(verticles[i], res -> { 265 | if (res.succeeded()) { 266 | async2.countDown(); 267 | } 268 | }); 269 | async2.awaitSuccess(); 270 | throw e; 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/ForgedMessagesKeyExchangeTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | import java.math.BigInteger; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.concurrent.CompletableFuture; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.stream.Collectors; 12 | 13 | import org.apache.log4j.Level; 14 | import org.junit.Assert; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | 18 | import io.vertx.core.json.JsonObject; 19 | import io.vertx.ext.unit.Async; 20 | import io.vertx.ext.unit.TestContext; 21 | import io.vertx.ext.unit.junit.VertxUnitRunner; 22 | import com.gdh.main.Configuration; 23 | import com.gdh.main.ExchangeState; 24 | import com.gdh.main.GDHVertex; 25 | import com.gdh.main.Group; 26 | import com.gdh.main.Node; 27 | import com.gdh.main.PrimaryVertex; 28 | import com.gdh.parser.MessageConstructor; 29 | 30 | @RunWith(VertxUnitRunner.class) 31 | public class ForgedMessagesKeyExchangeTest { 32 | 33 | @Test 34 | public void testDoubleKeyExchange(TestContext context) { 35 | int amount = 2; 36 | testNegotiation(amount, context); 37 | } 38 | 39 | @Test 40 | public void testTripleKeyExchange(TestContext context) { 41 | int amount = 3; 42 | testNegotiation(amount, context); 43 | } 44 | 45 | @Test 46 | public void testQuadrupleKeyExchange(TestContext context) { 47 | int amount = 4; 48 | testNegotiation(amount, context); 49 | } 50 | 51 | @Test 52 | public void testQuintupleKeyExchange(TestContext context) { 53 | int amount = 5; 54 | testNegotiation(amount, context); 55 | } 56 | 57 | // real deployment and communication between verticles on localhost 58 | private void testNegotiation(int amount, TestContext context) { 59 | // Vertx vertx = Vertx.vertx(); 60 | PrimaryVertex pv = new PrimaryVertex(); 61 | GDHVertex[] verticles = new GDHVertex[amount]; 62 | Configuration[] confs = new Configuration[amount]; 63 | 64 | for (int i = 0; i < amount; i++) { 65 | verticles[i] = new GDHVertex(); 66 | confs[i] = new Configuration(); 67 | String port = amount + "07" + i; 68 | confs[i].setIP("localhost").setPort(port).setLogLevel(Level.DEBUG); 69 | verticles[i].setConfiguration(confs[i]); 70 | } 71 | List list = new ArrayList<>(Arrays.asList(verticles)); 72 | 73 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 74 | verticles[0].addGroup(g); 75 | 76 | Async async1 = context.async(amount); 77 | for (int i = 0; i < amount; i++) 78 | pv.run(verticles[i], res -> { 79 | if (res.succeeded()) { 80 | async1.countDown(); 81 | } else { 82 | res.cause().printStackTrace(); 83 | return; 84 | } 85 | }); 86 | async1.awaitSuccess(); 87 | 88 | BigInteger key = null; 89 | try { 90 | CompletableFuture bigint = verticles[0].exchange(g.getGroupId()); 91 | // double messages check 92 | Method method1 = verticles[0].getClass().getDeclaredMethod("broadcast", Group.class); 93 | method1.setAccessible(true); 94 | method1.invoke(verticles[0],g); 95 | 96 | // sending message of unknown group 97 | Method method2 = verticles[0].getClass().getDeclaredMethod("sendMessage", Node.class, JsonObject.class); 98 | method2.setAccessible(true); 99 | method2.invoke(verticles[0],verticles[1].getNode(), MessageConstructor.roundInfo(new ExchangeState(45622, BigInteger.TEN))); 100 | 101 | key = bigint.get(); 102 | for (int j = 0; j < verticles.length; j++) { 103 | Assert.assertEquals(verticles[j].getKey(g.getGroupId()).get(), key); 104 | } 105 | } catch (InterruptedException | ExecutionException | SecurityException | 106 | IllegalArgumentException | NoSuchMethodException | IllegalAccessException | 107 | InvocationTargetException e) { 108 | // TODO Auto-generated catch block 109 | e.printStackTrace(); 110 | } 111 | Async async2 = context.async(amount); 112 | for (int i = 0; i < amount; i++) 113 | pv.kill(verticles[i], res -> { 114 | if (res.succeeded()) { 115 | async2.countDown(); 116 | } else { 117 | res.cause().printStackTrace(); 118 | } 119 | }); 120 | async2.awaitSuccess(); 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/GroupNodeTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.util.TreeSet; 4 | 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | import com.gdh.main.Configuration; 9 | import com.gdh.main.Group; 10 | import com.gdh.main.Node; 11 | 12 | @SuppressWarnings("PMD.AvoidUsingHardCodedIP") 13 | public class GroupNodeTest { 14 | private static final String ip1 = "1.1.1.1"; 15 | private static final String port1 = "3000"; 16 | private static final String port2 = "3001"; 17 | 18 | @Test 19 | public void testNodeGroupEquality() { 20 | Node n1 = new Node(ip1, port1); 21 | Node n2 = new Node(null, port1); 22 | Node n3 = new Node(ip1, null); 23 | 24 | Assert.assertFalse(n1.equals(n2)); 25 | Assert.assertFalse(n1.equals(n3)); 26 | 27 | Group g1 = new Group(new Configuration(), n1); 28 | Group g2 = g1; 29 | Group g3 = null; 30 | 31 | Assert.assertTrue(g1.equals(g2)); 32 | Assert.assertFalse(g1.equals(g3)); 33 | } 34 | 35 | @Test 36 | public void testSameGroup() { 37 | Node n1 = new Node(ip1, port1); 38 | Node n2 = new Node(ip1, port2); 39 | Node n3 = new Node("2.2.2.2", port1); 40 | 41 | Group g1 = new Group(new Configuration(), n1, n2, n3); 42 | Group g2 = new Group(new Configuration(), n2, n3, n1); 43 | Assert.assertFalse(n1.equals(n2)); 44 | Assert.assertFalse(n1.equals(n3)); 45 | Assert.assertTrue(g1.hashCode() == g2.hashCode()); 46 | Assert.assertTrue(g1.equals(g2)); 47 | Assert.assertTrue(g1.getActiveNode().equals(g2.getActiveNode())); 48 | } 49 | 50 | @Test 51 | public void testSimilarGroup() { 52 | Node n1 = new Node(ip1, port1); 53 | Node n2 = new Node(ip1, port2); 54 | 55 | Node n3 = new Node(ip1, port1); 56 | Node n4 = new Node(ip1, port2); 57 | 58 | TreeSet set = new TreeSet<>(); 59 | TreeSet set2 = new TreeSet<>(); 60 | set.add(n1); 61 | set.add(n2); 62 | set2.add(n3); 63 | set2.add(n4); 64 | Group g1 = new Group(new Configuration(), n1, n2, n3, n4); 65 | Group g2 = new Group(new Configuration(), n1, n2, n3, n4); 66 | Assert.assertTrue(n1.equals(n3)); 67 | Assert.assertTrue(g1.hashCode() == g2.hashCode()); 68 | Assert.assertTrue(g1.equals(g2)); 69 | } 70 | 71 | @Test 72 | public void testDifferentGroup() { 73 | Node n1 = new Node(ip1, port1); 74 | Node n2 = new Node(ip1, port2); 75 | 76 | Node n3 = new Node(ip1, port1); 77 | Node n4 = new Node(ip1, "3002"); 78 | 79 | TreeSet set = new TreeSet<>(); 80 | TreeSet set2 = new TreeSet<>(); 81 | set.add(n1); 82 | set.add(n2); 83 | set2.add(n3); 84 | set2.add(n4); 85 | Group g1 = new Group(new Configuration(), n1, n2); 86 | Group g2 = new Group(new Configuration(), n3, n4); 87 | Assert.assertTrue(n1.equals(n3)); 88 | Assert.assertTrue(g1.hashCode() != g2.hashCode()); 89 | Assert.assertTrue(!g1.equals(g2)); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/KeyExchangeTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.math.BigInteger; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.concurrent.ExecutionException; 8 | import java.util.stream.Collectors; 9 | 10 | import org.apache.log4j.Level; 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | import io.vertx.ext.unit.Async; 16 | import io.vertx.ext.unit.TestContext; 17 | import io.vertx.ext.unit.junit.VertxUnitRunner; 18 | import com.gdh.main.Configuration; 19 | import com.gdh.main.GDHVertex; 20 | import com.gdh.main.Group; 21 | import com.gdh.main.PrimaryVertex; 22 | 23 | @RunWith(VertxUnitRunner.class) 24 | public class KeyExchangeTest { 25 | 26 | @Test 27 | public void testDoubleKeyExchange(TestContext context) { 28 | int amount = 2; 29 | testNegotiation(amount, context); 30 | } 31 | 32 | @Test 33 | public void testTripleKeyExchange(TestContext context) { 34 | int amount = 3; 35 | testNegotiation(amount, context); 36 | } 37 | 38 | @Test 39 | public void testQuadrupleKeyExchange(TestContext context) { 40 | int amount = 4; 41 | testNegotiation(amount, context); 42 | } 43 | 44 | @Test 45 | public void testQuintupleKeyExchange(TestContext context) { 46 | int amount = 5; 47 | testNegotiation(amount, context); 48 | } 49 | 50 | @Test 51 | public void testSextupleKeyExchange(TestContext context) { 52 | int amount = 5; 53 | testNegotiation(amount, context); 54 | } 55 | 56 | @Test 57 | public void testSeptupleKeyExchange(TestContext context) { 58 | int amount = 5; 59 | testNegotiation(amount, context); 60 | } 61 | 62 | // real deployment and communication between verticles on localhost 63 | private void testNegotiation(int amount, TestContext context) { 64 | // Vertx vertx = Vertx.vertx(); 65 | PrimaryVertex pv = new PrimaryVertex(); 66 | GDHVertex[] verticles = new GDHVertex[amount]; 67 | Configuration[] confs = new Configuration[amount]; 68 | 69 | for (int i = 0; i < amount; i++) { 70 | verticles[i] = new GDHVertex(); 71 | confs[i] = new Configuration(); 72 | String port = amount + "07" + i; 73 | confs[i].setIP("localhost").setPort(port).setLogLevel(Level.DEBUG); 74 | verticles[i].setConfiguration(confs[i]); 75 | } 76 | List list = new ArrayList<>(Arrays.asList(verticles)); 77 | 78 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 79 | verticles[0].addGroup(g); 80 | 81 | Async async1 = context.async(amount); 82 | for (int i = 0; i < amount; i++) 83 | pv.run(verticles[i], res -> { 84 | if (res.succeeded()) { 85 | async1.countDown(); 86 | } else { 87 | res.cause().printStackTrace(); 88 | return; 89 | } 90 | }); 91 | async1.awaitSuccess(); 92 | 93 | BigInteger key = null; 94 | try { 95 | key = verticles[0].exchange(g.getGroupId()).get(); 96 | 97 | for (int j = 0; j < verticles.length; j++) { 98 | Assert.assertEquals(verticles[j].getKey(g.getGroupId()).get(), key); 99 | } 100 | } catch (InterruptedException | ExecutionException e) { 101 | // TODO Auto-generated catch block 102 | e.printStackTrace(); 103 | } 104 | Async async2 = context.async(amount); 105 | for (int i = 0; i < amount; i++) 106 | pv.kill(verticles[i], res -> { 107 | if (res.succeeded()) { 108 | async2.countDown(); 109 | } else { 110 | res.cause().printStackTrace(); 111 | } 112 | }); 113 | async2.awaitSuccess(); 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/LoggerTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.io.StringWriter; 4 | import java.io.Writer; 5 | import java.math.BigInteger; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.concurrent.ExecutionException; 10 | import java.util.stream.Collectors; 11 | 12 | import org.apache.log4j.Level; 13 | import org.apache.log4j.PatternLayout; 14 | import org.apache.log4j.WriterAppender; 15 | import org.junit.Assert; 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | 19 | import io.vertx.ext.unit.Async; 20 | import io.vertx.ext.unit.TestContext; 21 | import io.vertx.ext.unit.junit.VertxUnitRunner; 22 | import com.gdh.main.Configuration; 23 | import com.gdh.main.Constants; 24 | import com.gdh.main.GDHVertex; 25 | import com.gdh.main.Group; 26 | import com.gdh.main.PrimaryVertex; 27 | 28 | @RunWith(VertxUnitRunner.class) 29 | public class LoggerTest { 30 | 31 | @Test 32 | public void testExchangeNoKeyOnWire1(TestContext context) { 33 | int amount = 2; 34 | testNegotiation(amount, context); 35 | } 36 | 37 | @Test 38 | public void testExchangeNoKeyOnWire2(TestContext context) { 39 | int amount = 3; 40 | testNegotiation(amount, context); 41 | } 42 | 43 | // real deployment and communication between verticles on localhost 44 | private void testNegotiation(int amount, TestContext context) { 45 | PrimaryVertex pv = new PrimaryVertex(); 46 | GDHVertex[] verticles = new GDHVertex[amount]; 47 | Configuration[] confs = new Configuration[amount]; 48 | Writer writer = new StringWriter(); 49 | for (int i = 0; i < amount; i++) { 50 | verticles[i] = new GDHVertex(); 51 | confs[i] = new Configuration(); 52 | WriterAppender app = new WriterAppender(new PatternLayout(), writer); 53 | app.setThreshold(Level.DEBUG); 54 | app.activateOptions(); 55 | confs[i].setAppender(app); 56 | String port = amount + "08" + i; 57 | confs[i].setIP("localhost").setPort(port).setLogLevel(Level.DEBUG); 58 | verticles[i].setConfiguration(confs[i]); 59 | } 60 | List list = new ArrayList<>(Arrays.asList(verticles)); 61 | 62 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 63 | verticles[0].addGroup(g); 64 | 65 | Async async1 = context.async(amount); 66 | for (int i = 0; i < amount; i++) 67 | pv.run(verticles[i], res -> { 68 | if (res.succeeded()) { 69 | async1.countDown(); 70 | } else { 71 | res.cause().printStackTrace(); 72 | return; 73 | } 74 | }); 75 | async1.awaitSuccess(); 76 | 77 | BigInteger[] keys = new BigInteger[1]; 78 | try { 79 | keys[0] = verticles[0].exchange(g.getGroupId()).get(); 80 | for (int j = 0; j < verticles.length; j++) { 81 | Assert.assertEquals(verticles[j].getKey(g.getGroupId()).get(), keys[0]); 82 | } 83 | String write = writer.toString(); 84 | int count1 = write.length() - write.replace(Constants.LOG_IN, "0000000").length(); 85 | int count2 = write.length() - write.replace(Constants.LOG_OUT, "0000000").length(); 86 | Assert.assertTrue(count1 >= amount*amount-1); 87 | Assert.assertTrue(count2 >= amount*amount-1); 88 | } catch (InterruptedException | ExecutionException e) { 89 | // TODO Auto-generated catch block 90 | e.printStackTrace(); 91 | } 92 | Async async2 = context.async(amount); 93 | for (int i = 0; i < amount; i++) 94 | pv.kill(verticles[i], res -> { 95 | if (res.succeeded()) { 96 | async2.countDown(); 97 | } else { 98 | res.cause().printStackTrace(); 99 | } 100 | }); 101 | async2.awaitSuccess(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/MultipleGroupKeyExchangeTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.math.BigInteger; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.concurrent.ExecutionException; 7 | 8 | import org.apache.log4j.Level; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | import io.vertx.ext.unit.Async; 14 | import io.vertx.ext.unit.TestContext; 15 | import io.vertx.ext.unit.junit.VertxUnitRunner; 16 | import com.gdh.main.Configuration; 17 | import com.gdh.main.GDHVertex; 18 | import com.gdh.main.Group; 19 | import com.gdh.main.PrimaryVertex; 20 | 21 | @RunWith(VertxUnitRunner.class) 22 | public class MultipleGroupKeyExchangeTest { 23 | @Test 24 | public void testDoubleGroupKeyExchange(TestContext context) { 25 | int amount = 3; 26 | testNegotiation(amount, context); 27 | } 28 | 29 | @Test 30 | public void testTripleGroupKeyExchange(TestContext context) { 31 | int amount = 4; 32 | testNegotiation(amount, context); 33 | } 34 | 35 | @Test 36 | public void testQuadrupleGroupKeyExchange(TestContext context) { 37 | int amount = 5; 38 | testNegotiation(amount, context); 39 | } 40 | 41 | // real deployment and communication between verticles 42 | private void testNegotiation(int amount, TestContext context) { 43 | PrimaryVertex pv = new PrimaryVertex(); 44 | GDHVertex[] verticles = new GDHVertex[amount]; 45 | Configuration[] confs = new Configuration[amount]; 46 | 47 | for (int i = 0; i < amount; i++) { 48 | verticles[i] = new GDHVertex(); 49 | confs[i] = new Configuration(); 50 | String port = amount + "09" + i; 51 | confs[i].setIP("localhost").setPort(port).setLogLevel(Level.DEBUG); 52 | verticles[i].setConfiguration(confs[i]); 53 | } 54 | 55 | Group[] groups = new Group[amount - 1]; 56 | BigInteger[] keys = new BigInteger[amount - 1]; 57 | for (int i = 0; i < amount - 1; i++) { 58 | groups[i] = new Group(confs[0], verticles[0].getNode(), verticles[i + 1].getNode()); 59 | verticles[0].addGroup(groups[i]); 60 | } 61 | Async async1 = context.async(amount); 62 | for (int i = 0; i < amount; i++) 63 | pv.run(verticles[i], res -> { 64 | if (res.succeeded()) { 65 | async1.countDown(); 66 | } else { 67 | res.cause().printStackTrace(); 68 | return; 69 | } 70 | }); 71 | async1.awaitSuccess(); 72 | 73 | try { 74 | for (int i = 0; i < amount - 1; i++) 75 | keys[i] = verticles[0].exchange(groups[i].getGroupId()).get(); 76 | 77 | for (int i = 0; i < amount - 1; i++) 78 | Assert.assertEquals(verticles[i + 1].getKey(groups[i].getGroupId()).get(), 79 | verticles[0].getKey(groups[i].getGroupId()).get()); 80 | 81 | Map mapOfKeys = new HashMap<>(); 82 | for (int i = 0; i < amount - 1; i++) { 83 | BigInteger key = verticles[i + 1].getKey(groups[i].getGroupId()).get(); 84 | if (mapOfKeys.containsKey(key)) 85 | mapOfKeys.put(key, mapOfKeys.get(key) + 1); 86 | else 87 | mapOfKeys.put(key, 1); 88 | } 89 | 90 | for (Integer count : mapOfKeys.values()) 91 | Assert.assertTrue(count == 1); 92 | 93 | } catch (InterruptedException | ExecutionException e) { 94 | // TODO Auto-generated catch block 95 | e.printStackTrace(); 96 | } 97 | Async async2 = context.async(amount); 98 | for (int i = 0; i < amount; i++) 99 | pv.kill(verticles[i], res -> { 100 | if (res.succeeded()) { 101 | async2.countDown(); 102 | } else { 103 | res.cause().printStackTrace(); 104 | } 105 | }); 106 | async2.awaitSuccess(); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/NoKeyOnWireTest.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.io.StringWriter; 4 | import java.io.Writer; 5 | import java.math.BigInteger; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.concurrent.ExecutionException; 10 | import java.util.stream.Collectors; 11 | 12 | import org.apache.log4j.Level; 13 | import org.apache.log4j.PatternLayout; 14 | import org.apache.log4j.WriterAppender; 15 | import org.junit.Assert; 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | 19 | import io.vertx.ext.unit.Async; 20 | import io.vertx.ext.unit.TestContext; 21 | import io.vertx.ext.unit.junit.VertxUnitRunner; 22 | import com.gdh.main.Configuration; 23 | import com.gdh.main.GDHVertex; 24 | import com.gdh.main.Group; 25 | import com.gdh.main.PrimaryVertex; 26 | 27 | @RunWith(VertxUnitRunner.class) 28 | public class NoKeyOnWireTest { 29 | 30 | @Test 31 | public void testExchangeNoKeyOnWire(TestContext context) { 32 | int amount = 2; 33 | testNegotiation(amount, context); 34 | } 35 | 36 | // real deployment and communication between verticles on localhost 37 | private void testNegotiation(int amount, TestContext context) { 38 | PrimaryVertex pv = new PrimaryVertex(); 39 | GDHVertex[] verticles = new GDHVertex[amount]; 40 | Configuration[] confs = new Configuration[amount]; 41 | Writer writer = new StringWriter(); 42 | for (int i = 0; i < amount; i++) { 43 | verticles[i] = new GDHVertex(); 44 | confs[i] = new Configuration(); 45 | WriterAppender app = new WriterAppender(new PatternLayout(), writer); 46 | app.setThreshold(Level.DEBUG); 47 | app.activateOptions(); 48 | confs[i].setAppender(app); 49 | String port = amount + "08" + i; 50 | confs[i].setIP("localhost").setPort(port).setLogLevel(Level.DEBUG); 51 | verticles[i].setConfiguration(confs[i]); 52 | } 53 | List list = new ArrayList<>(Arrays.asList(verticles)); 54 | 55 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 56 | verticles[0].addGroup(g); 57 | 58 | Async async1 = context.async(amount); 59 | for (int i = 0; i < amount; i++) 60 | pv.run(verticles[i], res -> { 61 | if (res.succeeded()) { 62 | async1.countDown(); 63 | } else { 64 | res.cause().printStackTrace(); 65 | return; 66 | } 67 | }); 68 | async1.awaitSuccess(); 69 | 70 | BigInteger[] keys = new BigInteger[2]; 71 | try { 72 | keys[0] = verticles[0].exchange(g.getGroupId()).get(); 73 | Assert.assertFalse(!writer.toString().isEmpty() && writer.toString().contains(keys[0].toString())); 74 | } catch (InterruptedException | ExecutionException e) { 75 | // TODO Auto-generated catch block 76 | e.printStackTrace(); 77 | } 78 | Async async2 = context.async(amount); 79 | for (int i = 0; i < amount; i++) 80 | pv.kill(verticles[i], res -> { 81 | if (res.succeeded()) { 82 | async2.countDown(); 83 | } else { 84 | res.cause().printStackTrace(); 85 | } 86 | }); 87 | async2.awaitSuccess(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/NonSyncDeploymentKeyExchange.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.concurrent.ExecutionException; 7 | import java.util.stream.Collectors; 8 | 9 | import org.apache.log4j.Level; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | 14 | import io.vertx.ext.unit.Async; 15 | import io.vertx.ext.unit.TestContext; 16 | import io.vertx.ext.unit.junit.VertxUnitRunner; 17 | import com.gdh.main.Configuration; 18 | import com.gdh.main.GDHVertex; 19 | import com.gdh.main.Group; 20 | import com.gdh.main.PrimaryVertex; 21 | 22 | @RunWith(VertxUnitRunner.class) 23 | public class NonSyncDeploymentKeyExchange { 24 | 25 | @Test 26 | public void testDoubleKeyExchangeUnfinished(TestContext context) { 27 | int amount = 2; 28 | testUnfinishedDeployment(amount, context); 29 | } 30 | 31 | @Test 32 | public void testTripleKeyExchangeUnfinished(TestContext context) { 33 | int amount = 3; 34 | testUnfinishedDeployment(amount, context); 35 | } 36 | 37 | @Test 38 | public void testQuadrupleKeyExchangeUnfinished(TestContext context) { 39 | int amount = 4; 40 | testUnfinishedDeployment(amount, context); 41 | } 42 | 43 | @Test 44 | public void testQuintupleKeyExchangeUnfinished(TestContext context) { 45 | int amount = 5; 46 | testUnfinishedDeployment(amount, context); 47 | } 48 | 49 | @Test 50 | public void testDoubleKeyExchangeDelayed(TestContext context) { 51 | int amount = 2; 52 | testDelayedDeployment(amount, context); 53 | } 54 | 55 | @Test 56 | public void testTripleKeyExchangeDelayed(TestContext context) { 57 | int amount = 3; 58 | testDelayedDeployment(amount, context); 59 | } 60 | 61 | @Test 62 | public void testQuadrupleKeyExchangeDelayed(TestContext context) { 63 | int amount = 4; 64 | testDelayedDeployment(amount, context); 65 | } 66 | 67 | @Test 68 | public void testQuintupleKeyExchangeDelayed(TestContext context) { 69 | int amount = 5; 70 | testDelayedDeployment(amount, context); 71 | } 72 | 73 | // real deployment and communication between verticles on localhost 74 | private void testUnfinishedDeployment(int amount, TestContext context) { 75 | PrimaryVertex pv = new PrimaryVertex(); 76 | GDHVertex[] verticles = new GDHVertex[amount]; 77 | Configuration[] confs = new Configuration[amount]; 78 | 79 | for (int i = 0; i < amount; i++) { 80 | verticles[i] = new GDHVertex(); 81 | confs[i] = new Configuration(); 82 | String port = (amount * 2) + "09" + i; 83 | confs[i].setIP("localhost").setPort(port).setLogLevel(Level.DEBUG); 84 | verticles[i].setConfiguration(confs[i]); 85 | } 86 | List list = new ArrayList<>(Arrays.asList(verticles)); 87 | 88 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 89 | verticles[0].addGroup(g); 90 | 91 | Async async = context.async(); 92 | pv.run(verticles[0], res -> { 93 | if (res.succeeded()) { 94 | for (int i = 1; i < amount; i++) { 95 | pv.run(verticles[i]); 96 | } 97 | verticles[0].exchange(g.getGroupId()); 98 | async.complete(); 99 | } else { 100 | System.out.println("Cannot initiate startup and exchange!"); 101 | Assert.fail(); 102 | } 103 | }); 104 | async.awaitSuccess(); 105 | 106 | for (int j = 0; j < amount - 1; j++) 107 | try { 108 | Assert.assertTrue(verticles[j].getKey(g.getGroupId()).get() 109 | .equals(verticles[j + 1].getKey(g.getGroupId()).get())); 110 | } catch (InterruptedException | ExecutionException e) { 111 | // TODO Auto-generated catch block 112 | e.printStackTrace(); 113 | } 114 | 115 | Async async3 = context.async(amount); 116 | for (int i = 0; i < amount; i++) 117 | pv.kill(verticles[i], res -> { 118 | if (res.failed()) { 119 | res.cause().printStackTrace(); 120 | } else 121 | async3.countDown(); 122 | }); 123 | async3.awaitSuccess(); 124 | } 125 | 126 | // real deployment and communication between verticles on localhost 127 | private void testDelayedDeployment(int amount, TestContext context) { 128 | PrimaryVertex pv = new PrimaryVertex(); 129 | GDHVertex[] verticles = new GDHVertex[amount]; 130 | Configuration[] confs = new Configuration[amount]; 131 | 132 | for (int i = 0; i < amount; i++) { 133 | verticles[i] = new GDHVertex(); 134 | confs[i] = new Configuration(); 135 | String port = (amount * 2) + "09" + i; 136 | confs[i].setIP("localhost").setPort(port).setLogLevel(Level.DEBUG); 137 | verticles[i].setConfiguration(confs[i]); 138 | } 139 | List list = new ArrayList<>(Arrays.asList(verticles)); 140 | 141 | Group g = new Group(confs[0], list.stream().map(y -> y.getNode()).collect(Collectors.toList())); 142 | verticles[0].addGroup(g); 143 | 144 | Async async = context.async(); 145 | pv.run(verticles[0], res -> { 146 | if (res.succeeded()) { 147 | verticles[0].exchange(g.getGroupId()); 148 | try { 149 | Thread.sleep(1000); 150 | } catch (InterruptedException e) { 151 | // TODO Auto-generated catch block 152 | e.printStackTrace(); 153 | } 154 | for (int i = 1; i < amount; i++) { 155 | pv.run(verticles[i]); 156 | } 157 | async.complete(); 158 | } else { 159 | System.out.println("Cannot initiate startup and exchange!"); 160 | Assert.fail(); 161 | } 162 | }); 163 | async.awaitSuccess(); 164 | 165 | for (int j = 0; j < amount - 1; j++) 166 | try { 167 | Assert.assertTrue(verticles[j].getKey(g.getGroupId()).get() 168 | .equals(verticles[j + 1].getKey(g.getGroupId()).get())); 169 | } catch (InterruptedException | ExecutionException e) { 170 | // TODO Auto-generated catch block 171 | e.printStackTrace(); 172 | } 173 | 174 | Async async3 = context.async(amount); 175 | for (int i = 0; i < amount; i++) 176 | pv.kill(verticles[i], res -> { 177 | if (res.failed()) { 178 | res.cause().printStackTrace(); 179 | } else 180 | async3.countDown(); 181 | }); 182 | async3.awaitSuccess(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/test/java/com/gdh/test/VertxTestSuite.java: -------------------------------------------------------------------------------- 1 | package com.gdh.test; 2 | 3 | import java.io.IOException; 4 | 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.runner.RunWith; 8 | import org.junit.runners.Suite; 9 | 10 | import io.vertx.core.Vertx; 11 | import io.vertx.ext.unit.TestContext; 12 | 13 | @RunWith(Suite.class) 14 | @Suite.SuiteClasses({ 15 | GroupNodeTest.class, 16 | ConfigurationTest.class, 17 | MultipleGroupKeyExchangeTest.class, 18 | AsyncKeyExchangeTest.class, 19 | NoKeyOnWireTest.class, 20 | ExceptionTest.class, 21 | KeyExchangeTest.class, 22 | ForgedMessagesKeyExchangeTest.class, 23 | NonSyncDeploymentKeyExchange.class, 24 | LoggerTest.class 25 | }) 26 | 27 | public class VertxTestSuite { 28 | private Vertx vertx; 29 | 30 | @Before 31 | public void setUp(TestContext context) throws IOException { 32 | vertx = Vertx.vertx(); 33 | 34 | } 35 | 36 | @After 37 | public void tearDown(TestContext context) { 38 | vertx.close(context.asyncAssertSuccess()); 39 | } 40 | } 41 | --------------------------------------------------------------------------------