Improved tests and fixed some
This commit is contained in:
parent
ed4fd1002b
commit
0fadb40c6c
@ -25,7 +25,7 @@ artifacts {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'org.slf4j:slf4j-api:1.7.12'
|
compile 'org.slf4j:slf4j-api:1.7.12'
|
||||||
testCompile 'junit:junit:4.11'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
testCompile project(':cryptography-bc')
|
testCompile project(':cryptography-bc')
|
||||||
|
@ -24,7 +24,6 @@ import ch.dissem.bitmessage.utils.UnixTime;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The 'version' command advertises this node's latest supported protocol version upon initiation.
|
* The 'version' command advertises this node's latest supported protocol version upon initiation.
|
||||||
|
@ -40,7 +40,7 @@ import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|||||||
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
|
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
|
||||||
*/
|
*/
|
||||||
public class Factory {
|
public class Factory {
|
||||||
public static final Logger LOG = LoggerFactory.getLogger(Factory.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Factory.class);
|
||||||
|
|
||||||
public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException {
|
public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException {
|
||||||
try {
|
try {
|
||||||
|
@ -52,23 +52,21 @@ public class V3MessageReader {
|
|||||||
state = ReaderState.HEADER;
|
state = ReaderState.HEADER;
|
||||||
case HEADER:
|
case HEADER:
|
||||||
if (buffer.remaining() < 20) {
|
if (buffer.remaining() < 20) {
|
||||||
buffer.compact();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
command = getCommand(buffer);
|
command = getCommand(buffer);
|
||||||
length = (int) Decode.uint32(buffer);
|
length = (int) Decode.uint32(buffer);
|
||||||
if (length > MAX_PAYLOAD_SIZE) {
|
if (length > MAX_PAYLOAD_SIZE) {
|
||||||
throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected.");
|
throw new NodeException("Payload of " + length + " bytes received, no more than " +
|
||||||
|
MAX_PAYLOAD_SIZE + " was expected.");
|
||||||
}
|
}
|
||||||
checksum = new byte[4];
|
checksum = new byte[4];
|
||||||
buffer.get(checksum);
|
buffer.get(checksum);
|
||||||
state = ReaderState.DATA;
|
state = ReaderState.DATA;
|
||||||
if (buffer.remaining() < length) {
|
|
||||||
// We need to compact the buffer to make sure the message fits even if it's really big.
|
|
||||||
buffer.compact();
|
|
||||||
}
|
|
||||||
case DATA:
|
case DATA:
|
||||||
if (buffer.remaining() < length) return;
|
if (buffer.remaining() < length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!testChecksum(buffer)) {
|
if (!testChecksum(buffer)) {
|
||||||
throw new NodeException("Checksum failed for message '" + command + "'");
|
throw new NodeException("Checksum failed for message '" + command + "'");
|
||||||
}
|
}
|
||||||
@ -95,34 +93,35 @@ public class V3MessageReader {
|
|||||||
private boolean findMagicBytes(ByteBuffer buffer) {
|
private boolean findMagicBytes(ByteBuffer buffer) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (buffer.hasRemaining()) {
|
while (buffer.hasRemaining()) {
|
||||||
if (buffer.get() == MAGIC_BYTES[i]) {
|
if (i == 0) {
|
||||||
buffer.mark();
|
buffer.mark();
|
||||||
|
}
|
||||||
|
if (buffer.get() == MAGIC_BYTES[i]) {
|
||||||
i++;
|
i++;
|
||||||
if (i == MAGIC_BYTES.length) return true;
|
if (i == MAGIC_BYTES.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
buffer.compact();
|
|
||||||
} else {
|
|
||||||
buffer.clear();
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getCommand(ByteBuffer buffer) {
|
private static String getCommand(ByteBuffer buffer) {
|
||||||
int start = buffer.position();
|
int start = buffer.position();
|
||||||
int i = 0;
|
int l = 0;
|
||||||
while (i < 12 && buffer.get() != 0) i++;
|
while (l < 12 && buffer.get() != 0) l++;
|
||||||
int end = start + i;
|
int i = l + 1;
|
||||||
while (i < 12) {
|
while (i < 12) {
|
||||||
if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command");
|
if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command");
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return new String(buffer.array(), start, end, "ASCII");
|
return new String(buffer.array(), start, l, "ASCII");
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new ApplicationException(e);
|
throw new ApplicationException(e);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ import static ch.dissem.bitmessage.utils.Numbers.max;
|
|||||||
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder {
|
public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder {
|
||||||
public static final Logger LOG = LoggerFactory.getLogger(Cryptography.class);
|
protected static final Logger LOG = LoggerFactory.getLogger(Cryptography.class);
|
||||||
private static final SecureRandom RANDOM = new SecureRandom();
|
private static final SecureRandom RANDOM = new SecureRandom();
|
||||||
private static final BigInteger TWO = BigInteger.valueOf(2);
|
private static final BigInteger TWO = BigInteger.valueOf(2);
|
||||||
private static final BigInteger TWO_POW_64 = TWO.pow(64);
|
private static final BigInteger TWO_POW_64 = TWO.pow(64);
|
||||||
|
@ -13,6 +13,6 @@ uploadArchives {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
|
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
|
||||||
testCompile 'junit:junit:4.11'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,5 @@ uploadArchives {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile 'com.madgag.spongycastle:prov:1.52.0.0'
|
compile 'com.madgag.spongycastle:prov:1.52.0.0'
|
||||||
testCompile 'junit:junit:4.11'
|
testCompile 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,6 @@ dependencies {
|
|||||||
compile 'args4j:args4j:2.32'
|
compile 'args4j:args4j:2.32'
|
||||||
compile 'com.h2database:h2:1.4.190'
|
compile 'com.h2database:h2:1.4.190'
|
||||||
compile 'org.apache.commons:commons-lang3:3.4'
|
compile 'org.apache.commons:commons-lang3:3.4'
|
||||||
testCompile 'junit:junit:4.11'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ public class SystemTest {
|
|||||||
bob.shutdown();
|
bob.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(timeout = 60_000)
|
||||||
public void ensureAliceCanSendMessageToBob() throws Exception {
|
public void ensureAliceCanSendMessageToBob() throws Exception {
|
||||||
String originalMessage = UUID.randomUUID().toString();
|
String originalMessage = UUID.randomUUID().toString();
|
||||||
alice.send(aliceIdentity, new BitmessageAddress(bobIdentity.getAddress()), "Subject", originalMessage);
|
alice.send(aliceIdentity, new BitmessageAddress(bobIdentity.getAddress()), "Subject", originalMessage);
|
||||||
@ -102,7 +102,7 @@ public class SystemTest {
|
|||||||
.markAsAcknowledged(any());
|
.markAsAcknowledged(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(timeout = 30_000)
|
||||||
public void ensureBobCanReceiveBroadcastFromAlice() throws Exception {
|
public void ensureBobCanReceiveBroadcastFromAlice() throws Exception {
|
||||||
String originalMessage = UUID.randomUUID().toString();
|
String originalMessage = UUID.randomUUID().toString();
|
||||||
bob.addSubscribtion(new BitmessageAddress(aliceIdentity.getAddress()));
|
bob.addSubscribtion(new BitmessageAddress(aliceIdentity.getAddress()));
|
||||||
|
@ -28,7 +28,7 @@ uploadArchives {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
testCompile 'junit:junit:4.11'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.slf4j:slf4j-simple:1.7.12'
|
testCompile 'org.slf4j:slf4j-simple:1.7.12'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
|
@ -12,7 +12,7 @@ uploadArchives {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
testCompile 'junit:junit:4.11'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.slf4j:slf4j-simple:1.7.12'
|
testCompile 'org.slf4j:slf4j-simple:1.7.12'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
|
@ -35,6 +35,7 @@ import java.util.concurrent.ConcurrentLinkedDeque;
|
|||||||
|
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
|
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
|
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
|
||||||
|
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
|
||||||
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
|
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
|
||||||
import static ch.dissem.bitmessage.networking.AbstractConnection.State.*;
|
import static ch.dissem.bitmessage.networking.AbstractConnection.State.*;
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
||||||
@ -62,6 +63,8 @@ public abstract class AbstractConnection {
|
|||||||
protected long peerNonce;
|
protected long peerNonce;
|
||||||
protected int version;
|
protected int version;
|
||||||
protected long[] streams;
|
protected long[] streams;
|
||||||
|
private boolean verackSent;
|
||||||
|
private boolean verackReceived;
|
||||||
|
|
||||||
public AbstractConnection(InternalContext context, Mode mode,
|
public AbstractConnection(InternalContext context, Mode mode,
|
||||||
NetworkAddress node,
|
NetworkAddress node,
|
||||||
@ -198,7 +201,7 @@ public abstract class AbstractConnection {
|
|||||||
return ivCache.containsKey(iv);
|
return ivCache.containsKey(iv);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void cleanupIvCache() {
|
private void cleanupIvCache() {
|
||||||
Long fiveMinutesAgo = UnixTime.now(-5 * MINUTE);
|
Long fiveMinutesAgo = UnixTime.now(-5 * MINUTE);
|
||||||
for (Map.Entry<InventoryVector, Long> entry : ivCache.entrySet()) {
|
for (Map.Entry<InventoryVector, Long> entry : ivCache.entrySet()) {
|
||||||
if (entry.getValue() < fiveMinutesAgo) {
|
if (entry.getValue() < fiveMinutesAgo) {
|
||||||
@ -213,16 +216,10 @@ public abstract class AbstractConnection {
|
|||||||
handleVersion((Version) payload);
|
handleVersion((Version) payload);
|
||||||
break;
|
break;
|
||||||
case VERACK:
|
case VERACK:
|
||||||
switch (mode) {
|
if (verackSent) {
|
||||||
case SERVER:
|
activateConnection();
|
||||||
activateConnection();
|
|
||||||
break;
|
|
||||||
case CLIENT:
|
|
||||||
case SYNC:
|
|
||||||
default:
|
|
||||||
// NO OP
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
verackReceived = true;
|
||||||
break;
|
break;
|
||||||
case CUSTOM:
|
case CUSTOM:
|
||||||
MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) payload);
|
MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) payload);
|
||||||
@ -237,7 +234,7 @@ public abstract class AbstractConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void activateConnection() {
|
private void activateConnection() {
|
||||||
LOG.info("Successfully established connection with node " + node);
|
LOG.info("Successfully established connection with node " + node);
|
||||||
state = ACTIVE;
|
state = ACTIVE;
|
||||||
node.setTime(UnixTime.now());
|
node.setTime(UnixTime.now());
|
||||||
@ -272,17 +269,13 @@ public abstract class AbstractConnection {
|
|||||||
|
|
||||||
this.version = version.getVersion();
|
this.version = version.getVersion();
|
||||||
this.streams = version.getStreams();
|
this.streams = version.getStreams();
|
||||||
|
verackSent = true;
|
||||||
send(new VerAck());
|
send(new VerAck());
|
||||||
switch (mode) {
|
if (mode == SERVER) {
|
||||||
case SERVER:
|
send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build());
|
||||||
send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build());
|
}
|
||||||
break;
|
if (verackReceived) {
|
||||||
case CLIENT:
|
activateConnection();
|
||||||
case SYNC:
|
|
||||||
activateConnection();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// NO OP
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Received unsupported version " + version.getVersion() + ", disconnecting.");
|
LOG.info("Received unsupported version " + version.getVersion() + ", disconnecting.");
|
||||||
|
@ -19,6 +19,7 @@ package ch.dissem.bitmessage.networking.nio;
|
|||||||
import ch.dissem.bitmessage.InternalContext;
|
import ch.dissem.bitmessage.InternalContext;
|
||||||
import ch.dissem.bitmessage.entity.MessagePayload;
|
import ch.dissem.bitmessage.entity.MessagePayload;
|
||||||
import ch.dissem.bitmessage.entity.NetworkMessage;
|
import ch.dissem.bitmessage.entity.NetworkMessage;
|
||||||
|
import ch.dissem.bitmessage.entity.Version;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||||
import ch.dissem.bitmessage.factory.V3MessageReader;
|
import ch.dissem.bitmessage.factory.V3MessageReader;
|
||||||
@ -26,9 +27,12 @@ import ch.dissem.bitmessage.networking.AbstractConnection;
|
|||||||
import ch.dissem.bitmessage.ports.NetworkHandler;
|
import ch.dissem.bitmessage.ports.NetworkHandler;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.*;
|
import java.util.Iterator;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT;
|
||||||
|
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
|
||||||
import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_MESSAGE_SIZE;
|
import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_MESSAGE_SIZE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,6 +47,10 @@ public class ConnectionInfo extends AbstractConnection {
|
|||||||
NetworkAddress node, NetworkHandler.MessageListener listener,
|
NetworkAddress node, NetworkHandler.MessageListener listener,
|
||||||
Set<InventoryVector> commonRequestedObjects) {
|
Set<InventoryVector> commonRequestedObjects) {
|
||||||
super(context, mode, node, listener, commonRequestedObjects, false);
|
super(context, mode, node, listener, commonRequestedObjects, false);
|
||||||
|
out.flip();
|
||||||
|
if (mode == CLIENT || mode == SYNC) {
|
||||||
|
send(new Version.Builder().defaults(peerNonce).addrFrom(host).addrRecv(node).build());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public State getState() {
|
public State getState() {
|
||||||
@ -77,12 +85,8 @@ public class ConnectionInfo extends AbstractConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<NetworkMessage> getMessages() {
|
|
||||||
return reader.getMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void send(MessagePayload payload) {
|
protected void send(MessagePayload payload) {
|
||||||
sendingQueue.addFirst(payload);
|
sendingQueue.add(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,11 +37,14 @@ import java.net.InetSocketAddress;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.*;
|
import java.nio.channels.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT;
|
||||||
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
|
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
|
||||||
|
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
|
||||||
import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE;
|
import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE;
|
||||||
import static ch.dissem.bitmessage.utils.DebugUtils.inc;
|
import static ch.dissem.bitmessage.utils.DebugUtils.inc;
|
||||||
|
import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool;
|
||||||
import static java.nio.channels.SelectionKey.OP_READ;
|
import static java.nio.channels.SelectionKey.OP_READ;
|
||||||
import static java.nio.channels.SelectionKey.OP_WRITE;
|
import static java.nio.channels.SelectionKey.OP_WRITE;
|
||||||
|
|
||||||
@ -51,13 +54,40 @@ import static java.nio.channels.SelectionKey.OP_WRITE;
|
|||||||
public class NioNetworkHandler implements NetworkHandler, InternalContext.ContextHolder {
|
public class NioNetworkHandler implements NetworkHandler, InternalContext.ContextHolder {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(NioNetworkHandler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(NioNetworkHandler.class);
|
||||||
|
|
||||||
|
private final ExecutorService pool = Executors.newCachedThreadPool(
|
||||||
|
pool("network")
|
||||||
|
.lowPrio()
|
||||||
|
.daemon()
|
||||||
|
.build());
|
||||||
|
|
||||||
private InternalContext ctx;
|
private InternalContext ctx;
|
||||||
private Selector selector;
|
private Selector selector;
|
||||||
private ServerSocketChannel serverChannel;
|
private ServerSocketChannel serverChannel;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Future<?> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds) {
|
public Future<Void> synchronize(final InetAddress server, final int port, final MessageListener listener, long timeoutInSeconds) {
|
||||||
return null;
|
return pool.submit(new Callable<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void call() throws Exception {
|
||||||
|
Set<InventoryVector> requestedObjects = new HashSet<>();
|
||||||
|
try (SocketChannel channel = SocketChannel.open(new InetSocketAddress(server, port))) {
|
||||||
|
channel.finishConnect();
|
||||||
|
channel.configureBlocking(false);
|
||||||
|
ConnectionInfo connection = new ConnectionInfo(ctx, SYNC,
|
||||||
|
new NetworkAddress.Builder().ip(server).port(port).stream(1).build(),
|
||||||
|
listener, new HashSet<InventoryVector>());
|
||||||
|
while (channel.isConnected() &&
|
||||||
|
(connection.getState() != ACTIVE
|
||||||
|
|| connection.getSendingQueue().isEmpty()
|
||||||
|
|| requestedObjects.isEmpty())) {
|
||||||
|
write(requestedObjects, channel, connection);
|
||||||
|
read(channel, connection);
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -66,7 +96,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
|
|||||||
channel.configureBlocking(true);
|
channel.configureBlocking(true);
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(MAX_MESSAGE_SIZE);
|
ByteBuffer buffer = ByteBuffer.allocate(MAX_MESSAGE_SIZE);
|
||||||
new NetworkMessage(request).write(buffer);
|
new NetworkMessage(request).write(buffer);
|
||||||
channel.write(buffer);
|
while (buffer.hasRemaining()) {
|
||||||
|
channel.write(buffer);
|
||||||
|
}
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
|
||||||
V3MessageReader reader = new V3MessageReader();
|
V3MessageReader reader = new V3MessageReader();
|
||||||
@ -106,34 +138,74 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
|
|||||||
throw new ApplicationException(e);
|
throw new ApplicationException(e);
|
||||||
}
|
}
|
||||||
final Set<InventoryVector> requestedObjects = new HashSet<>();
|
final Set<InventoryVector> requestedObjects = new HashSet<>();
|
||||||
new Thread(new Runnable() {
|
start("connection listener", new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
serverChannel = ServerSocketChannel.open();
|
serverChannel = ServerSocketChannel.open();
|
||||||
serverChannel.bind(new InetSocketAddress(ctx.getPort()));
|
serverChannel.socket().bind(new InetSocketAddress(ctx.getPort()));
|
||||||
|
while (selector.isOpen() && serverChannel.isOpen()) {
|
||||||
SocketChannel accepted = serverChannel.accept();
|
try {
|
||||||
accepted.configureBlocking(false);
|
SocketChannel accepted = serverChannel.accept();
|
||||||
// FIXME: apparently it isn't good practice to generally listen for OP_WRITE
|
accepted.configureBlocking(false);
|
||||||
accepted.register(selector, OP_READ | OP_WRITE).attach(
|
accepted.register(selector, OP_READ | OP_WRITE,
|
||||||
new ConnectionInfo(ctx, SERVER,
|
new ConnectionInfo(ctx, SERVER,
|
||||||
new NetworkAddress.Builder().address(accepted.getRemoteAddress()).stream(1).build(),
|
new NetworkAddress.Builder().address(accepted.getRemoteAddress()).stream(1).build(),
|
||||||
listener,
|
listener,
|
||||||
requestedObjects
|
requestedObjects
|
||||||
));
|
));
|
||||||
|
} catch (AsynchronousCloseException ignore) {
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (ClosedSelectorException | AsynchronousCloseException ignore) {
|
} catch (ClosedSelectorException | AsynchronousCloseException ignore) {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ApplicationException(e);
|
throw new ApplicationException(e);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, "Server").start();
|
});
|
||||||
new Thread(new Runnable() {
|
|
||||||
|
start("connection starter", new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (selector.isOpen()) {
|
||||||
|
List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses(
|
||||||
|
2, ctx.getStreams());
|
||||||
|
for (NetworkAddress address : addresses) {
|
||||||
|
try {
|
||||||
|
SocketChannel channel = SocketChannel.open(
|
||||||
|
new InetSocketAddress(address.toInetAddress(), address.getPort()));
|
||||||
|
channel.configureBlocking(false);
|
||||||
|
channel.register(selector, OP_READ | OP_WRITE,
|
||||||
|
new ConnectionInfo(ctx, CLIENT,
|
||||||
|
address,
|
||||||
|
listener,
|
||||||
|
requestedObjects
|
||||||
|
));
|
||||||
|
} catch (AsynchronousCloseException ignore) {
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(30_000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
start("processor", new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
while (selector.isOpen()) {
|
while (selector.isOpen()) {
|
||||||
// TODO: establish outgoing connections
|
selector.select(1000);
|
||||||
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
|
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
|
||||||
|
|
||||||
while (keyIterator.hasNext()) {
|
while (keyIterator.hasNext()) {
|
||||||
@ -141,22 +213,16 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
|
|||||||
if (key.attachment() instanceof ConnectionInfo) {
|
if (key.attachment() instanceof ConnectionInfo) {
|
||||||
SocketChannel channel = (SocketChannel) key.channel();
|
SocketChannel channel = (SocketChannel) key.channel();
|
||||||
ConnectionInfo connection = (ConnectionInfo) key.attachment();
|
ConnectionInfo connection = (ConnectionInfo) key.attachment();
|
||||||
|
|
||||||
if (key.isWritable()) {
|
if (key.isWritable()) {
|
||||||
if (connection.getOutBuffer().hasRemaining()) {
|
write(requestedObjects, channel, connection);
|
||||||
channel.write(connection.getOutBuffer());
|
|
||||||
}
|
|
||||||
while (!connection.getOutBuffer().hasRemaining() && !connection.getSendingQueue().isEmpty()) {
|
|
||||||
MessagePayload payload = connection.getSendingQueue().poll();
|
|
||||||
if (payload instanceof GetData) {
|
|
||||||
requestedObjects.addAll(((GetData) payload).getInventory());
|
|
||||||
}
|
|
||||||
new NetworkMessage(payload).write(connection.getOutBuffer());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (key.isReadable()) {
|
if (key.isReadable()) {
|
||||||
channel.read(connection.getInBuffer());
|
read(channel, connection);
|
||||||
connection.updateReader();
|
}
|
||||||
|
if (connection.getSendingQueue().isEmpty()) {
|
||||||
|
key.interestOps(OP_READ);
|
||||||
|
} else {
|
||||||
|
key.interestOps(OP_READ | OP_WRITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyIterator.remove();
|
keyIterator.remove();
|
||||||
@ -168,13 +234,52 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
|
|||||||
throw new ApplicationException(e);
|
throw new ApplicationException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, "Connections").start();
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void write(Set<InventoryVector> requestedObjects, SocketChannel channel, ConnectionInfo connection)
|
||||||
|
throws IOException {
|
||||||
|
if (!connection.getSendingQueue().isEmpty()) {
|
||||||
|
ByteBuffer buffer = connection.getOutBuffer();
|
||||||
|
if (buffer.hasRemaining()) {
|
||||||
|
channel.write(buffer);
|
||||||
|
}
|
||||||
|
while (!buffer.hasRemaining()
|
||||||
|
&& !connection.getSendingQueue().isEmpty()) {
|
||||||
|
buffer.clear();
|
||||||
|
MessagePayload payload = connection.getSendingQueue().poll();
|
||||||
|
if (payload instanceof GetData) {
|
||||||
|
requestedObjects.addAll(((GetData) payload).getInventory());
|
||||||
|
}
|
||||||
|
new NetworkMessage(payload).write(buffer);
|
||||||
|
buffer.flip();
|
||||||
|
if (buffer.hasRemaining()) {
|
||||||
|
channel.write(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void read(SocketChannel channel, ConnectionInfo connection) throws IOException {
|
||||||
|
ByteBuffer buffer = connection.getInBuffer();
|
||||||
|
while (channel.read(buffer) > 0) {
|
||||||
|
buffer.flip();
|
||||||
|
connection.updateReader();
|
||||||
|
buffer.compact();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start(String threadName, Runnable runnable) {
|
||||||
|
Thread thread = new Thread(runnable, threadName);
|
||||||
|
thread.setDaemon(true);
|
||||||
|
thread.setPriority(Thread.MIN_PRIORITY);
|
||||||
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
try {
|
try {
|
||||||
serverChannel.close();
|
serverChannel.socket().close();
|
||||||
for (SelectionKey key : selector.keys()) {
|
for (SelectionKey key : selector.keys()) {
|
||||||
key.channel().close();
|
key.channel().close();
|
||||||
}
|
}
|
||||||
|
@ -22,14 +22,23 @@ import ch.dissem.bitmessage.entity.CustomMessage;
|
|||||||
import ch.dissem.bitmessage.entity.MessagePayload;
|
import ch.dissem.bitmessage.entity.MessagePayload;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||||
import ch.dissem.bitmessage.exception.NodeException;
|
import ch.dissem.bitmessage.exception.NodeException;
|
||||||
|
import ch.dissem.bitmessage.networking.nio.NioNetworkHandler;
|
||||||
import ch.dissem.bitmessage.ports.*;
|
import ch.dissem.bitmessage.ports.*;
|
||||||
import ch.dissem.bitmessage.utils.Property;
|
import ch.dissem.bitmessage.utils.Property;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.DisableOnDebug;
|
||||||
|
import org.junit.rules.TestRule;
|
||||||
|
import org.junit.rules.Timeout;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
||||||
@ -42,6 +51,7 @@ import static org.mockito.Mockito.mock;
|
|||||||
/**
|
/**
|
||||||
* FIXME: there really should be sensible tests for the network handler
|
* FIXME: there really should be sensible tests for the network handler
|
||||||
*/
|
*/
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
public class NetworkHandlerTest {
|
public class NetworkHandlerTest {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(NetworkHandlerTest.class);
|
private static final Logger LOG = LoggerFactory.getLogger(NetworkHandlerTest.class);
|
||||||
private static NetworkAddress peerAddress = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(6001).build();
|
private static NetworkAddress peerAddress = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(6001).build();
|
||||||
@ -51,7 +61,27 @@ public class NetworkHandlerTest {
|
|||||||
|
|
||||||
private BitmessageContext peer;
|
private BitmessageContext peer;
|
||||||
private BitmessageContext node;
|
private BitmessageContext node;
|
||||||
private NetworkHandler networkHandler;
|
|
||||||
|
private final NetworkHandler peerNetworkHandler;
|
||||||
|
private final NetworkHandler nodeNetworkHandler;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TestRule timeout = new DisableOnDebug(Timeout.seconds(5));
|
||||||
|
|
||||||
|
public NetworkHandlerTest(NetworkHandler peer, NetworkHandler node) {
|
||||||
|
this.peerNetworkHandler = peer;
|
||||||
|
this.nodeNetworkHandler = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameterized.Parameters
|
||||||
|
public static List<Object[]> parameters() {
|
||||||
|
return Arrays.asList(new Object[][]{
|
||||||
|
{new DefaultNetworkHandler(), new DefaultNetworkHandler()},
|
||||||
|
{new DefaultNetworkHandler(), new NioNetworkHandler()},
|
||||||
|
{new NioNetworkHandler(), new DefaultNetworkHandler()},
|
||||||
|
{new NioNetworkHandler(), new NioNetworkHandler()}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@ -63,7 +93,7 @@ public class NetworkHandlerTest {
|
|||||||
.powRepo(mock(ProofOfWorkRepository.class))
|
.powRepo(mock(ProofOfWorkRepository.class))
|
||||||
.port(peerAddress.getPort())
|
.port(peerAddress.getPort())
|
||||||
.nodeRegistry(new TestNodeRegistry())
|
.nodeRegistry(new TestNodeRegistry())
|
||||||
.networkHandler(new DefaultNetworkHandler())
|
.networkHandler(peerNetworkHandler)
|
||||||
.cryptography(new BouncyCryptography())
|
.cryptography(new BouncyCryptography())
|
||||||
.listener(mock(BitmessageContext.Listener.class))
|
.listener(mock(BitmessageContext.Listener.class))
|
||||||
.customCommandHandler(new CustomCommandHandler() {
|
.customCommandHandler(new CustomCommandHandler() {
|
||||||
@ -90,7 +120,6 @@ public class NetworkHandlerTest {
|
|||||||
peer.startup();
|
peer.startup();
|
||||||
|
|
||||||
nodeInventory = new TestInventory();
|
nodeInventory = new TestInventory();
|
||||||
networkHandler = new DefaultNetworkHandler();
|
|
||||||
node = new BitmessageContext.Builder()
|
node = new BitmessageContext.Builder()
|
||||||
.addressRepo(mock(AddressRepository.class))
|
.addressRepo(mock(AddressRepository.class))
|
||||||
.inventory(nodeInventory)
|
.inventory(nodeInventory)
|
||||||
@ -98,7 +127,7 @@ public class NetworkHandlerTest {
|
|||||||
.powRepo(mock(ProofOfWorkRepository.class))
|
.powRepo(mock(ProofOfWorkRepository.class))
|
||||||
.port(6002)
|
.port(6002)
|
||||||
.nodeRegistry(new TestNodeRegistry(peerAddress))
|
.nodeRegistry(new TestNodeRegistry(peerAddress))
|
||||||
.networkHandler(networkHandler)
|
.networkHandler(nodeNetworkHandler)
|
||||||
.cryptography(new BouncyCryptography())
|
.cryptography(new BouncyCryptography())
|
||||||
.listener(mock(BitmessageContext.Listener.class))
|
.listener(mock(BitmessageContext.Listener.class))
|
||||||
.build();
|
.build();
|
||||||
@ -108,7 +137,7 @@ public class NetworkHandlerTest {
|
|||||||
public void cleanUp() {
|
public void cleanUp() {
|
||||||
shutdown(peer);
|
shutdown(peer);
|
||||||
shutdown(node);
|
shutdown(node);
|
||||||
shutdown(networkHandler);
|
shutdown(nodeNetworkHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void shutdown(BitmessageContext ctx) {
|
private static void shutdown(BitmessageContext ctx) {
|
||||||
@ -140,7 +169,7 @@ public class NetworkHandlerTest {
|
|||||||
} while (networkHandler.isRunning());
|
} while (networkHandler.isRunning());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 5_000)
|
@Test
|
||||||
public void ensureNodesAreConnecting() throws Exception {
|
public void ensureNodesAreConnecting() throws Exception {
|
||||||
node.startup();
|
node.startup();
|
||||||
Property status;
|
Property status;
|
||||||
@ -151,14 +180,14 @@ public class NetworkHandlerTest {
|
|||||||
assertEquals(1, status.getProperty("outgoing").getValue());
|
assertEquals(1, status.getProperty("outgoing").getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 5_000)
|
@Test
|
||||||
public void ensureCustomMessageIsSentAndResponseRetrieved() throws Exception {
|
public void ensureCustomMessageIsSentAndResponseRetrieved() throws Exception {
|
||||||
byte[] data = cryptography().randomBytes(8);
|
byte[] data = cryptography().randomBytes(8);
|
||||||
data[0] = (byte) 1;
|
data[0] = (byte) 1;
|
||||||
CustomMessage request = new CustomMessage("test request", data);
|
CustomMessage request = new CustomMessage("test request", data);
|
||||||
node.startup();
|
node.startup();
|
||||||
|
|
||||||
CustomMessage response = networkHandler.send(peerAddress.toInetAddress(), peerAddress.getPort(), request);
|
CustomMessage response = nodeNetworkHandler.send(peerAddress.toInetAddress(), peerAddress.getPort(), request);
|
||||||
|
|
||||||
assertThat(response, notNullValue());
|
assertThat(response, notNullValue());
|
||||||
assertThat(response.getCustomCommand(), is("test response"));
|
assertThat(response.getCustomCommand(), is("test response"));
|
||||||
@ -172,14 +201,14 @@ public class NetworkHandlerTest {
|
|||||||
CustomMessage request = new CustomMessage("test request", data);
|
CustomMessage request = new CustomMessage("test request", data);
|
||||||
node.startup();
|
node.startup();
|
||||||
|
|
||||||
CustomMessage response = networkHandler.send(peerAddress.toInetAddress(), peerAddress.getPort(), request);
|
CustomMessage response = nodeNetworkHandler.send(peerAddress.toInetAddress(), peerAddress.getPort(), request);
|
||||||
|
|
||||||
assertThat(response, notNullValue());
|
assertThat(response, notNullValue());
|
||||||
assertThat(response.getCustomCommand(), is("test response"));
|
assertThat(response.getCustomCommand(), is("test response"));
|
||||||
assertThat(response.getData(), is(request.getData()));
|
assertThat(response.getData(), is(request.getData()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 5_000)
|
@Test
|
||||||
public void ensureObjectsAreSynchronizedIfBothHaveObjects() throws Exception {
|
public void ensureObjectsAreSynchronizedIfBothHaveObjects() throws Exception {
|
||||||
peerInventory.init(
|
peerInventory.init(
|
||||||
"V4Pubkey.payload",
|
"V4Pubkey.payload",
|
||||||
@ -191,7 +220,7 @@ public class NetworkHandlerTest {
|
|||||||
"V4Pubkey.payload"
|
"V4Pubkey.payload"
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<?> future = networkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(),
|
Future<?> future = nodeNetworkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(),
|
||||||
mock(NetworkHandler.MessageListener.class),
|
mock(NetworkHandler.MessageListener.class),
|
||||||
10);
|
10);
|
||||||
future.get();
|
future.get();
|
||||||
@ -199,7 +228,7 @@ public class NetworkHandlerTest {
|
|||||||
assertInventorySize(3, peerInventory);
|
assertInventorySize(3, peerInventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 5_000)
|
@Test
|
||||||
public void ensureObjectsAreSynchronizedIfOnlyPeerHasObjects() throws Exception {
|
public void ensureObjectsAreSynchronizedIfOnlyPeerHasObjects() throws Exception {
|
||||||
peerInventory.init(
|
peerInventory.init(
|
||||||
"V4Pubkey.payload",
|
"V4Pubkey.payload",
|
||||||
@ -208,7 +237,7 @@ public class NetworkHandlerTest {
|
|||||||
|
|
||||||
nodeInventory.init();
|
nodeInventory.init();
|
||||||
|
|
||||||
Future<?> future = networkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(),
|
Future<?> future = nodeNetworkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(),
|
||||||
mock(NetworkHandler.MessageListener.class),
|
mock(NetworkHandler.MessageListener.class),
|
||||||
10);
|
10);
|
||||||
future.get();
|
future.get();
|
||||||
@ -216,7 +245,7 @@ public class NetworkHandlerTest {
|
|||||||
assertInventorySize(2, peerInventory);
|
assertInventorySize(2, peerInventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 5_000)
|
@Test
|
||||||
public void ensureObjectsAreSynchronizedIfOnlyNodeHasObjects() throws Exception {
|
public void ensureObjectsAreSynchronizedIfOnlyNodeHasObjects() throws Exception {
|
||||||
peerInventory.init();
|
peerInventory.init();
|
||||||
|
|
||||||
@ -224,7 +253,7 @@ public class NetworkHandlerTest {
|
|||||||
"V1Msg.payload"
|
"V1Msg.payload"
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<?> future = networkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(),
|
Future<?> future = nodeNetworkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(),
|
||||||
mock(NetworkHandler.MessageListener.class),
|
mock(NetworkHandler.MessageListener.class),
|
||||||
10);
|
10);
|
||||||
future.get();
|
future.get();
|
||||||
|
@ -13,7 +13,7 @@ uploadArchives {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile 'org.ini4j:ini4j:0.5.4'
|
compile 'org.ini4j:ini4j:0.5.4'
|
||||||
testCompile 'junit:junit:4.11'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
testCompile project(':cryptography-bc')
|
testCompile project(':cryptography-bc')
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user