├── .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 | [](https://travis-ci.org/maxamel/GDH)
2 | [](https://sonarcloud.io/api/project_badges/measure?project=GDH&metric=coverage)
3 | [](https://github.com/vert-x3/vertx-awesome)
4 | [](https://snyk.io/test/github/maxamel/GDH)
5 |
6 | [](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 |
--------------------------------------------------------------------------------