add missing files

This commit is contained in:
Manfred Karrer 2015-10-28 13:34:03 +01:00
parent c6ece486ed
commit 9ef8b42509
239 changed files with 20558 additions and 51 deletions

View file

@ -0,0 +1,48 @@
package io.bitsquare.p2p;
import java.io.Serializable;
import java.util.regex.Pattern;
public class Address implements Serializable {
public final String hostName;
public final int port;
public Address(String hostName, int port) {
this.hostName = hostName;
this.port = port;
}
public Address(String fullAddress) {
final String[] split = fullAddress.split(Pattern.quote(":"));
this.hostName = split[0];
this.port = Integer.parseInt(split[1]);
}
public String getFullAddress() {
return hostName + ":" + port;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Address)) return false;
Address address = (Address) o;
if (port != address.port) return false;
return !(hostName != null ? !hostName.equals(address.hostName) : address.hostName != null);
}
@Override
public int hashCode() {
int result = hostName != null ? hostName.hashCode() : 0;
result = 31 * result + port;
return result;
}
@Override
public String toString() {
return getFullAddress();
}
}

View file

@ -0,0 +1,22 @@
package io.bitsquare.p2p;
public class AuthenticationException extends RuntimeException {
public AuthenticationException() {
}
public AuthenticationException(String message) {
super(message);
}
public AuthenticationException(String message, Throwable cause) {
super(message, cause);
}
public AuthenticationException(Throwable cause) {
super(cause);
}
public AuthenticationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -0,0 +1,14 @@
package io.bitsquare.p2p;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NetworkStatistics {
private static final Logger log = LoggerFactory.getLogger(NetworkStatistics.class);
@Inject
public NetworkStatistics() {
}
}

View file

@ -49,6 +49,7 @@ public class P2PService {
private static final Logger log = LoggerFactory.getLogger(P2PService.class);
private final EncryptionService encryptionService;
private final SetupListener setupListener;
private KeyRing keyRing;
private final NetworkStatistics networkStatistics;
@ -108,6 +109,58 @@ public class P2PService {
// Listeners
setupListener = new SetupListener() {
@Override
public void onTorNodeReady() {
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onTorNodeReady()));
// we don't know yet our own address so we can not filter that from the
// seedNodeAddresses in case we are a seed node
sendGetAllDataMessage(seedNodeAddresses);
}
@Override
public void onHiddenServiceReady() {
hiddenServiceReady = true;
tryStartAuthentication();
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onHiddenServiceReady()));
}
@Override
public void onSetupFailed(Throwable throwable) {
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onSetupFailed(throwable)));
}
};
networkNode.addConnectionListener(new ConnectionListener() {
@Override
public void onConnection(Connection connection) {
}
@Override
public void onPeerAddressAuthenticated(Address peerAddress, Connection connection) {
authenticatedPeerAddresses.add(peerAddress);
authenticatedToFirstPeer = true;
P2PService.this.authenticated = true;
dataStorage.setAuthenticated(true);
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onAuthenticated()));
}
@Override
public void onDisconnect(Reason reason, Connection connection) {
Address peerAddress = connection.getPeerAddress();
if (peerAddress != null)
authenticatedPeerAddresses.remove(peerAddress);
}
@Override
public void onError(Throwable throwable) {
log.error("onError self/ConnectionException " + networkNode.getAddress() + "/" + throwable);
}
});
networkNode.addMessageListener((message, connection) -> {
if (message instanceof GetDataSetMessage) {
log.trace("Received GetAllDataMessage: " + message);
@ -201,57 +254,7 @@ public class P2PService {
if (listener != null)
addP2PServiceListener(listener);
networkNode.start(new SetupListener() {
@Override
public void onTorNodeReady() {
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onTorNodeReady()));
// we don't know yet our own address so we can not filter that from the
// seedNodeAddresses in case we are a seed node
sendGetAllDataMessage(seedNodeAddresses);
}
@Override
public void onHiddenServiceReady() {
hiddenServiceReady = true;
tryStartAuthentication();
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onHiddenServiceReady()));
}
@Override
public void onSetupFailed(Throwable throwable) {
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onSetupFailed(throwable)));
}
});
networkNode.addConnectionListener(new ConnectionListener() {
@Override
public void onConnection(Connection connection) {
}
@Override
public void onPeerAddressAuthenticated(Address peerAddress, Connection connection) {
authenticatedPeerAddresses.add(peerAddress);
authenticatedToFirstPeer = true;
P2PService.this.authenticated = true;
dataStorage.setAuthenticated(true);
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onAuthenticated()));
}
@Override
public void onDisconnect(Reason reason, Connection connection) {
Address peerAddress = connection.getPeerAddress();
if (peerAddress != null)
authenticatedPeerAddresses.remove(peerAddress);
}
@Override
public void onError(Throwable throwable) {
log.error("onError self/ConnectionException " + networkNode.getAddress() + "/" + throwable);
}
});
networkNode.start(setupListener);
}
public void shutDown(Runnable shutDownCompleteHandler) {

View file

@ -0,0 +1,11 @@
package io.bitsquare.p2p;
import io.bitsquare.p2p.network.SetupListener;
public interface P2PServiceListener extends SetupListener {
void onAllDataReceived();
void onAuthenticated();
}

View file

@ -0,0 +1,96 @@
package io.bitsquare.p2p;
import io.bitsquare.common.ByteArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.net.ServerSocket;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
public class Utils {
private static final Logger log = LoggerFactory.getLogger(Utils.class);
public static int findFreeSystemPort() {
try {
ServerSocket server = new ServerSocket(0);
int port = server.getLocalPort();
server.close();
return port;
} catch (IOException ignored) {
} finally {
return new Random().nextInt(10000) + 50000;
}
}
public static void shutDownExecutorService(ExecutorService executorService) {
shutDownExecutorService(executorService, 200);
}
public static void shutDownExecutorService(ExecutorService executorService, long waitBeforeShutDown) {
executorService.shutdown();
try {
executorService.awaitTermination(waitBeforeShutDown, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
executorService.shutdownNow();
}
public static byte[] compress(Serializable input) {
return compress(ByteArrayUtils.objectToByteArray(input));
}
public static byte[] compress(byte[] input) {
Deflater compressor = new Deflater();
compressor.setLevel(Deflater.BEST_SPEED);
compressor.setInput(input);
compressor.finish();
ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length);
byte[] buf = new byte[8192];
while (!compressor.finished()) {
int count = compressor.deflate(buf);
bos.write(buf, 0, count);
}
try {
bos.close();
} catch (IOException e) {
}
return bos.toByteArray();
}
public static byte[] decompress(byte[] compressedData, int offset, int length) {
Inflater decompressor = new Inflater();
decompressor.setInput(compressedData, offset, length);
ByteArrayOutputStream bos = new ByteArrayOutputStream(length);
byte[] buf = new byte[8192];
while (!decompressor.finished()) {
try {
int count = decompressor.inflate(buf);
bos.write(buf, 0, count);
} catch (DataFormatException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return bos.toByteArray();
}
public static Serializable decompress(byte[] compressedData) {
return (Serializable) ByteArrayUtils.byteArrayToObject(decompress(compressedData, 0, compressedData.length));
}
}

View file

@ -0,0 +1,8 @@
package io.bitsquare.p2p.messaging;
import io.bitsquare.p2p.Address;
public interface DecryptedMailListener {
void onMailMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Address peerAddress);
}

View file

@ -0,0 +1,8 @@
package io.bitsquare.p2p.messaging;
import io.bitsquare.p2p.Address;
public interface DecryptedMailboxListener {
void onMailboxMessageAdded(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Address senderAddress);
}

View file

@ -0,0 +1,64 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.messaging;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Message;
import java.security.PublicKey;
public final class DecryptedMessageWithPubKey implements MailMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final Message message;
public final PublicKey signaturePubKey;
public DecryptedMessageWithPubKey(Message message, PublicKey signaturePubKey) {
this.message = message;
this.signaturePubKey = signaturePubKey;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DecryptedMessageWithPubKey)) return false;
DecryptedMessageWithPubKey that = (DecryptedMessageWithPubKey) o;
if (message != null ? !message.equals(that.message) : that.message != null) return false;
return !(signaturePubKey != null ? !signaturePubKey.equals(that.signaturePubKey) : that.signaturePubKey != null);
}
@Override
public int hashCode() {
int result = message != null ? message.hashCode() : 0;
result = 31 * result + (signaturePubKey != null ? signaturePubKey.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "DecryptedMessageWithPubKey{" +
"hashCode=" + hashCode() +
", message=" + message +
", signaturePubKey.hashCode()=" + signaturePubKey.hashCode() +
'}';
}
}

View file

@ -0,0 +1,24 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.messaging;
import io.bitsquare.p2p.Message;
public interface MailMessage extends Message {
}

View file

@ -0,0 +1,25 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.messaging;
import io.bitsquare.p2p.Address;
public interface MailboxMessage extends MailMessage {
Address getSenderAddress();
}

View file

@ -0,0 +1,68 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.messaging;
import io.bitsquare.app.Version;
import io.bitsquare.common.util.Utilities;
import javax.crypto.SealedObject;
import java.security.PublicKey;
import java.util.Arrays;
/**
* Packs the encrypted symmetric secretKey and the encrypted and signed message into one object.
* SecretKey is encrypted with asymmetric pubKey of peer. Signed message is encrypted with secretKey.
* Using that hybrid encryption model we are not restricted by data size and performance as symmetric encryption is very fast.
*/
public final class SealedAndSignedMessage implements MailMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final SealedObject sealedSecretKey;
public final SealedObject sealedMessage;
public final PublicKey signaturePubKey;
public SealedAndSignedMessage(SealedObject sealedSecretKey, SealedObject sealedMessage, PublicKey signaturePubKey) {
this.sealedSecretKey = sealedSecretKey;
this.sealedMessage = sealedMessage;
this.signaturePubKey = signaturePubKey;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SealedAndSignedMessage)) return false;
SealedAndSignedMessage that = (SealedAndSignedMessage) o;
return Arrays.equals(Utilities.objectToByteArray(this), Utilities.objectToByteArray(that));
}
@Override
public int hashCode() {
byte[] bytes = Utilities.objectToByteArray(this);
return bytes != null ? Arrays.hashCode(bytes) : 0;
}
@Override
public String toString() {
return "SealedAndSignedMessage{" +
"hashCode=" + hashCode() +
'}';
}
}

View file

@ -0,0 +1,7 @@
package io.bitsquare.p2p.messaging;
public interface SendMailMessageListener {
void onArrived();
void onFault();
}

View file

@ -0,0 +1,9 @@
package io.bitsquare.p2p.messaging;
public interface SendMailboxMessageListener {
void onArrived();
void onStoredInMailbox();
void onFault();
}

View file

@ -0,0 +1,365 @@
package io.bitsquare.p2p.network;
import io.bitsquare.common.ByteArrayUtils;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.Utils;
import io.bitsquare.p2p.network.messages.CloseConnectionMessage;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Connection {
private static final Logger log = LoggerFactory.getLogger(Connection.class);
private static final int MAX_MSG_SIZE = 5 * 1024 * 1024; // 5 MB of compressed data
private static final int MAX_ILLEGAL_REQUESTS = 5;
private static final int SOCKET_TIMEOUT = 30 * 60 * 1000; // 30 min.
public static int getMaxMsgSize() {
return MAX_MSG_SIZE;
}
private final Socket socket;
private final int port;
private final MessageListener messageListener;
private final ConnectionListener connectionListener;
private final String uid;
private final Map<IllegalRequest, Integer> illegalRequests = new ConcurrentHashMap<>();
private final ExecutorService executorService = Executors.newCachedThreadPool();
private ObjectOutputStream out;
private ObjectInputStream in;
private volatile boolean stopped;
private volatile boolean shutDownInProgress;
private volatile boolean inputHandlerStopped;
private volatile Date lastActivityDate;
@Nullable
private Address peerAddress;
private boolean isAuthenticated;
//TODO got java.util.zip.DataFormatException: invalid distance too far back
// java.util.zip.DataFormatException: invalid literal/lengths set
// use GZIPInputStream but problems with blocking
private boolean useCompression = false;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public Connection(Socket socket, MessageListener messageListener, ConnectionListener connectionListener) {
this.socket = socket;
port = socket.getLocalPort();
this.messageListener = messageListener;
this.connectionListener = connectionListener;
uid = UUID.randomUUID().toString();
try {
socket.setSoTimeout(SOCKET_TIMEOUT);
// Need to access first the ObjectOutputStream otherwise the ObjectInputStream would block
// See: https://stackoverflow.com/questions/5658089/java-creating-a-new-objectinputstream-blocks/5658109#5658109
// When you construct an ObjectInputStream, in the constructor the class attempts to read a header that
// the associated ObjectOutputStream on the other end of the connection has written.
// It will not return until that header has been read.
if (useCompression) {
out = new ObjectOutputStream(socket.getOutputStream());
in = new ObjectInputStream(socket.getInputStream());
} else {
out = new ObjectOutputStream(socket.getOutputStream());
in = new ObjectInputStream(socket.getInputStream());
}
executorService.submit(new InputHandler());
} catch (IOException e) {
handleConnectionException(e);
}
lastActivityDate = new Date();
connectionListener.onConnection(this);
}
public void onAuthenticationComplete(Address peerAddress, Connection connection) {
isAuthenticated = true;
this.peerAddress = peerAddress;
connectionListener.onPeerAddressAuthenticated(peerAddress, connection);
}
public boolean isStopped() {
return stopped;
}
public void sendMessage(Message message) {
if (!stopped) {
try {
log.trace("writeObject " + message + " on connection with port " + port);
if (!stopped) {
Object objectToWrite;
if (useCompression) {
byte[] messageAsBytes = ByteArrayUtils.objectToByteArray(message);
// log.trace("Write object uncompressed data size: " + messageAsBytes.length);
byte[] compressed = Utils.compress(message);
//log.trace("Write object compressed data size: " + compressed.length);
objectToWrite = compressed;
} else {
// log.trace("Write object data size: " + ByteArrayUtils.objectToByteArray(message).length);
objectToWrite = message;
}
out.writeObject(objectToWrite);
out.flush();
lastActivityDate = new Date();
}
} catch (IOException e) {
handleConnectionException(e);
}
} else {
connectionListener.onDisconnect(ConnectionListener.Reason.ALREADY_CLOSED, Connection.this);
}
}
public void reportIllegalRequest(IllegalRequest illegalRequest) {
log.warn("We got reported an illegal request " + illegalRequest);
int prevCounter = illegalRequests.get(illegalRequest);
if (prevCounter > illegalRequest.limit) {
log.warn("We close connection as we received too many illegal requests.\n" + illegalRequests.toString());
shutDown();
} else {
illegalRequests.put(illegalRequest, ++prevCounter);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public Socket getSocket() {
return socket;
}
@Nullable
public Address getPeerAddress() {
return peerAddress;
}
public Date getLastActivityDate() {
return lastActivityDate;
}
public boolean isAuthenticated() {
return isAuthenticated;
}
public String getUid() {
return uid;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setPeerAddress(Address peerAddress) {
this.peerAddress = peerAddress;
}
///////////////////////////////////////////////////////////////////////////////////////////
// ShutDown
///////////////////////////////////////////////////////////////////////////////////////////
public void shutDown(Runnable completeHandler) {
shutDown(true, completeHandler);
}
public void shutDown() {
shutDown(true, null);
}
private void shutDown(boolean sendCloseConnectionMessage) {
shutDown(sendCloseConnectionMessage, null);
}
private void shutDown(boolean sendCloseConnectionMessage, @Nullable Runnable shutDownCompleteHandler) {
if (!shutDownInProgress) {
log.info("\n\nShutDown connection:"
+ "\npeerAddress=" + peerAddress
+ "\nuid=" + getUid()
+ "\nisAuthenticated=" + isAuthenticated
+ "\nsocket.getPort()=" + socket.getPort()
+ "\n\n");
log.debug("ShutDown " + this.getObjectId());
log.debug("ShutDown connection requested. Connection=" + this.toString());
shutDownInProgress = true;
inputHandlerStopped = true;
if (!stopped) {
if (sendCloseConnectionMessage) {
sendMessage(new CloseConnectionMessage());
try {
// give a bit of time for closing gracefully
Thread.sleep(100);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
stopped = true;
connectionListener.onDisconnect(ConnectionListener.Reason.SHUT_DOWN, Connection.this);
try {
socket.close();
} catch (SocketException e) {
log.trace("SocketException at shutdown might be expected " + e.getMessage());
} catch (IOException e) {
e.printStackTrace();
} finally {
Utils.shutDownExecutorService(executorService);
log.debug("Connection shutdown complete " + this.toString());
// dont use executorService as its shut down but call handler on own thread
// to not get interrupted by caller
if (shutDownCompleteHandler != null)
new Thread(shutDownCompleteHandler).start();
}
}
}
}
private void handleConnectionException(Exception e) {
if (e instanceof SocketException) {
if (socket.isClosed())
connectionListener.onDisconnect(ConnectionListener.Reason.SOCKET_CLOSED, Connection.this);
else
connectionListener.onDisconnect(ConnectionListener.Reason.RESET, Connection.this);
} else if (e instanceof SocketTimeoutException) {
connectionListener.onDisconnect(ConnectionListener.Reason.TIMEOUT, Connection.this);
} else if (e instanceof EOFException) {
connectionListener.onDisconnect(ConnectionListener.Reason.PEER_DISCONNECTED, Connection.this);
} else {
log.info("Exception at connection with port " + socket.getLocalPort());
e.printStackTrace();
connectionListener.onDisconnect(ConnectionListener.Reason.UNKNOWN, Connection.this);
}
shutDown(false);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Connection)) return false;
Connection that = (Connection) o;
if (port != that.port) return false;
if (uid != null ? !uid.equals(that.uid) : that.uid != null) return false;
return !(peerAddress != null ? !peerAddress.equals(that.peerAddress) : that.peerAddress != null);
}
@Override
public int hashCode() {
int result = port;
result = 31 * result + (uid != null ? uid.hashCode() : 0);
result = 31 * result + (peerAddress != null ? peerAddress.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Connection{" +
"OBJECT ID=" + super.toString().split("@")[1] +
", uid=" + uid +
", port=" + port +
", isAuthenticated=" + isAuthenticated +
", peerAddress=" + peerAddress +
", lastActivityDate=" + lastActivityDate +
", stopped=" + stopped +
", inputHandlerStopped=" + inputHandlerStopped +
'}';
}
public String getObjectId() {
return super.toString().split("@")[1].toString();
}
///////////////////////////////////////////////////////////////////////////////////////////
// InputHandler
///////////////////////////////////////////////////////////////////////////////////////////
private class InputHandler implements Runnable {
@Override
public void run() {
Thread.currentThread().setName("InputHandler-" + socket.getLocalPort());
while (!inputHandlerStopped) {
try {
log.trace("InputHandler waiting for incoming messages connection=" + Connection.this.getObjectId());
Object rawInputObject = in.readObject();
log.trace("New data arrived at inputHandler of connection=" + Connection.this.getObjectId()
+ " rawInputObject " + rawInputObject);
int size = ByteArrayUtils.objectToByteArray(rawInputObject).length;
if (size <= MAX_MSG_SIZE) {
Serializable serializable = null;
if (useCompression) {
if (rawInputObject instanceof byte[]) {
byte[] compressedObjectAsBytes = (byte[]) rawInputObject;
size = compressedObjectAsBytes.length;
//log.trace("Read object compressed data size: " + size);
serializable = Utils.decompress(compressedObjectAsBytes);
} else {
reportIllegalRequest(IllegalRequest.InvalidDataType);
}
} else {
if (rawInputObject instanceof Serializable) {
serializable = (Serializable) rawInputObject;
} else {
reportIllegalRequest(IllegalRequest.InvalidDataType);
}
}
//log.trace("Read object decompressed data size: " + ByteArrayUtils.objectToByteArray(serializable).length);
// compressed size might be bigger theoretically so we check again after decompression
if (size <= MAX_MSG_SIZE) {
if (serializable instanceof Message) {
lastActivityDate = new Date();
Message message = (Message) serializable;
if (message instanceof CloseConnectionMessage)
shutDown(false);
else
executorService.submit(() -> messageListener.onMessage(message, Connection.this));
} else {
reportIllegalRequest(IllegalRequest.InvalidDataType);
}
} else {
log.error("Received decompressed data exceeds max. msg size.");
reportIllegalRequest(IllegalRequest.MaxSizeExceeded);
}
} else {
log.error("Received compressed data exceeds max. msg size.");
reportIllegalRequest(IllegalRequest.MaxSizeExceeded);
}
} catch (IOException | ClassNotFoundException e) {
log.error("Exception" + e);
inputHandlerStopped = true;
handleConnectionException(e);
}
}
}
}
}

View file

@ -0,0 +1,25 @@
package io.bitsquare.p2p.network;
import io.bitsquare.p2p.Address;
public interface ConnectionListener {
enum Reason {
SOCKET_CLOSED,
RESET,
TIMEOUT,
SHUT_DOWN,
PEER_DISCONNECTED,
ALREADY_CLOSED,
UNKNOWN
}
void onConnection(Connection connection);
void onPeerAddressAuthenticated(Address peerAddress, Connection connection);
void onDisconnect(Reason reason, Connection connection);
void onError(Throwable throwable);
}

View file

@ -0,0 +1,13 @@
package io.bitsquare.p2p.network;
public enum IllegalRequest {
MaxSizeExceeded(1),
NotAuthenticated(2),
InvalidDataType(2);
public final int limit;
IllegalRequest(int limit) {
this.limit = limit;
}
}

View file

@ -0,0 +1,132 @@
package io.bitsquare.p2p.network;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyContext;
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyManager;
import io.bitsquare.p2p.Address;
import io.nucleo.net.HiddenServiceDescriptor;
import io.nucleo.net.TorNode;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
public class LocalhostNetworkNode extends NetworkNode {
private static final Logger log = LoggerFactory.getLogger(LocalhostNetworkNode.class);
private static int simulateTorDelayTorNode = 0;
private static int simulateTorDelayHiddenService = 0;
private Address address;
public static void setSimulateTorDelayTorNode(int simulateTorDelayTorNode) {
LocalhostNetworkNode.simulateTorDelayTorNode = simulateTorDelayTorNode;
}
public static void setSimulateTorDelayHiddenService(int simulateTorDelayHiddenService) {
LocalhostNetworkNode.simulateTorDelayHiddenService = simulateTorDelayHiddenService;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public LocalhostNetworkNode(int port) {
super(port);
}
@Override
public void start(@Nullable SetupListener setupListener) {
if (setupListener != null) addSetupListener(setupListener);
executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
//Tor delay simulation
createTorNode(torNode -> {
setupListeners.stream().forEach(e -> e.onTorNodeReady());
// Create Hidden Service (takes about 40 sec.)
createHiddenService(hiddenServiceDescriptor -> {
try {
startServer(new ServerSocket(port));
} catch (BindException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
address = new Address("localhost", port);
setupListeners.stream().forEach(e -> e.onHiddenServiceReady());
});
});
}
@Override
@Nullable
public Address getAddress() {
return address;
}
@Override
protected Socket getSocket(Address peerAddress) throws IOException {
return new Socket(peerAddress.hostName, peerAddress.port);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Tor delay simulation
///////////////////////////////////////////////////////////////////////////////////////////
private void createTorNode(final Consumer<TorNode> resultHandler) {
Callable<TorNode<JavaOnionProxyManager, JavaOnionProxyContext>> task = () -> {
long ts = System.currentTimeMillis();
log.trace("[simulation] Create TorNode");
if (simulateTorDelayTorNode > 0) Thread.sleep(simulateTorDelayTorNode);
log.trace("\n\n##### TorNode created [simulation]. Took " + (System.currentTimeMillis() - ts) + " ms\n\n");
return null;
};
ListenableFuture<TorNode<JavaOnionProxyManager, JavaOnionProxyContext>> future = executorService.submit(task);
Futures.addCallback(future, new FutureCallback<TorNode<JavaOnionProxyManager, JavaOnionProxyContext>>() {
public void onSuccess(TorNode<JavaOnionProxyManager, JavaOnionProxyContext> torNode) {
resultHandler.accept(torNode);
}
public void onFailure(Throwable throwable) {
log.error("[simulation] TorNode creation failed");
}
});
}
private void createHiddenService(final Consumer<HiddenServiceDescriptor> resultHandler) {
Callable<HiddenServiceDescriptor> task = () -> {
long ts = System.currentTimeMillis();
log.debug("[simulation] Create hidden service");
if (simulateTorDelayHiddenService > 0) Thread.sleep(simulateTorDelayHiddenService);
log.debug("\n\n##### Hidden service created [simulation]. Took " + (System.currentTimeMillis() - ts) + " ms\n\n");
return null;
};
ListenableFuture<HiddenServiceDescriptor> future = executorService.submit(task);
Futures.addCallback(future, new FutureCallback<HiddenServiceDescriptor>() {
public void onSuccess(HiddenServiceDescriptor hiddenServiceDescriptor) {
resultHandler.accept(hiddenServiceDescriptor);
}
public void onFailure(Throwable throwable) {
log.error("[simulation] Hidden service creation failed");
}
});
}
}

View file

@ -0,0 +1,8 @@
package io.bitsquare.p2p.network;
import io.bitsquare.p2p.Message;
public interface MessageListener {
void onMessage(Message message, Connection connection);
}

View file

@ -0,0 +1,320 @@
package io.bitsquare.p2p.network;
import com.google.common.util.concurrent.*;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.Message;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import static com.google.common.base.Preconditions.checkNotNull;
public abstract class NetworkNode implements MessageListener, ConnectionListener {
private static final Logger log = LoggerFactory.getLogger(NetworkNode.class);
protected final int port;
private final Map<Address, Connection> outBoundConnections = new ConcurrentHashMap<>();
private final Map<Address, Connection> inBoundAuthenticatedConnections = new ConcurrentHashMap<>();
private final List<Connection> inBoundTempConnections = new CopyOnWriteArrayList<>();
private final List<MessageListener> messageListeners = new CopyOnWriteArrayList<>();
private final List<ConnectionListener> connectionListeners = new CopyOnWriteArrayList<>();
protected final List<SetupListener> setupListeners = new CopyOnWriteArrayList<>();
protected ListeningExecutorService executorService;
private Server server;
private volatile boolean shutDownInProgress;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public NetworkNode(int port) {
this.port = port;
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void start() {
start(null);
}
abstract public void start(@Nullable SetupListener setupListener);
public SettableFuture<Connection> sendMessage(@NotNull Address peerAddress, Message message) {
log.trace("sendMessage message=" + message);
checkNotNull(peerAddress, "peerAddress must not be null");
final SettableFuture<Connection> resultFuture = SettableFuture.create();
Callable<Connection> task = () -> {
Thread.currentThread().setName("Outgoing-connection-to-" + peerAddress);
Connection connection = outBoundConnections.get(peerAddress);
if (connection != null && connection.isStopped()) {
// can happen because of threading...
log.trace("We have a connection which is already stopped in outBoundConnections. Connection.uid=" + connection.getUid());
outBoundConnections.remove(peerAddress);
connection = null;
}
if (connection == null) {
Optional<Connection> connectionOptional = inBoundAuthenticatedConnections.values().stream()
.filter(e -> peerAddress.equals(e.getPeerAddress()))
.findAny();
if (connectionOptional.isPresent())
connection = connectionOptional.get();
if (connection != null)
log.trace("We have found a connection in inBoundAuthenticatedConnections. Connection.uid=" + connection.getUid());
}
if (connection == null) {
Optional<Connection> connectionOptional = inBoundTempConnections.stream()
.filter(e -> peerAddress.equals(e.getPeerAddress()))
.findAny();
if (connectionOptional.isPresent())
connection = connectionOptional.get();
if (connection != null)
log.trace("We have found a connection in inBoundTempConnections. Connection.uid=" + connection.getUid());
}
if (connection == null) {
try {
Socket socket = getSocket(peerAddress); // can take a while when using tor
connection = new Connection(socket,
(message1, connection1) -> NetworkNode.this.onMessage(message1, connection1),
new ConnectionListener() {
@Override
public void onConnection(Connection connection) {
NetworkNode.this.onConnection(connection);
}
@Override
public void onPeerAddressAuthenticated(Address peerAddress, Connection connection) {
NetworkNode.this.onPeerAddressAuthenticated(peerAddress, connection);
}
@Override
public void onDisconnect(Reason reason, Connection connection) {
log.trace("onDisconnect at outgoing connection to peerAddress " + peerAddress);
NetworkNode.this.onDisconnect(reason, connection);
}
@Override
public void onError(Throwable throwable) {
NetworkNode.this.onError(throwable);
}
});
outBoundConnections.put(peerAddress, connection);
log.info("\n\nNetworkNode created new outbound connection:"
+ "\npeerAddress=" + peerAddress.port
+ "\nconnection.uid=" + connection.getUid()
+ "\nmessage=" + message
+ "\n\n");
} catch (Throwable t) {
resultFuture.setException(t);
return null;
}
}
connection.sendMessage(message);
return connection;
};
ListenableFuture<Connection> future = executorService.submit(task);
Futures.addCallback(future, new FutureCallback<Connection>() {
public void onSuccess(Connection connection) {
resultFuture.set(connection);
}
public void onFailure(@NotNull Throwable throwable) {
resultFuture.setException(throwable);
}
});
return resultFuture;
}
public SettableFuture<Connection> sendMessage(Connection connection, Message message) {
final SettableFuture<Connection> resultFuture = SettableFuture.create();
ListenableFuture<Connection> future = executorService.submit(() -> {
connection.sendMessage(message);
return connection;
});
Futures.addCallback(future, new FutureCallback<Connection>() {
public void onSuccess(Connection connection) {
resultFuture.set(connection);
}
public void onFailure(@NotNull Throwable throwable) {
resultFuture.setException(throwable);
}
});
return resultFuture;
}
public Set<Connection> getAllConnections() {
Set<Connection> set = new HashSet<>(inBoundAuthenticatedConnections.values());
set.addAll(outBoundConnections.values());
set.addAll(inBoundTempConnections);
return set;
}
public void shutDown(Runnable shutDownCompleteHandler) {
log.info("Shutdown NetworkNode");
if (!shutDownInProgress) {
shutDownInProgress = true;
if (server != null) {
server.shutDown();
server = null;
}
getAllConnections().stream().forEach(e -> e.shutDown());
log.info("NetworkNode shutdown complete");
if (shutDownCompleteHandler != null) new Thread(shutDownCompleteHandler).start();
;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// SetupListener
///////////////////////////////////////////////////////////////////////////////////////////
public void addSetupListener(SetupListener setupListener) {
setupListeners.add(setupListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// ConnectionListener
///////////////////////////////////////////////////////////////////////////////////////////
public void addConnectionListener(ConnectionListener connectionListener) {
connectionListeners.add(connectionListener);
}
public void removeConnectionListener(ConnectionListener connectionListener) {
connectionListeners.remove(connectionListener);
}
@Override
public void onConnection(Connection connection) {
connectionListeners.stream().forEach(e -> e.onConnection(connection));
}
@Override
public void onPeerAddressAuthenticated(Address peerAddress, Connection connection) {
log.trace("onAuthenticationComplete peerAddress=" + peerAddress);
log.trace("onAuthenticationComplete connection=" + connection);
connectionListeners.stream().forEach(e -> e.onPeerAddressAuthenticated(peerAddress, connection));
}
@Override
public void onDisconnect(Reason reason, Connection connection) {
Address peerAddress = connection.getPeerAddress();
log.trace("onDisconnect connection " + connection + ", peerAddress= " + peerAddress);
if (peerAddress != null) {
inBoundAuthenticatedConnections.remove(peerAddress);
outBoundConnections.remove(peerAddress);
} else {
// try to find if we have connection
outBoundConnections.values().stream()
.filter(e -> e.equals(connection))
.findAny()
.ifPresent(e -> outBoundConnections.remove(e.getPeerAddress()));
inBoundAuthenticatedConnections.values().stream()
.filter(e -> e.equals(connection))
.findAny()
.ifPresent(e -> inBoundAuthenticatedConnections.remove(e.getPeerAddress()));
}
inBoundTempConnections.remove(connection);
connectionListeners.stream().forEach(e -> e.onDisconnect(reason, connection));
}
@Override
public void onError(Throwable throwable) {
connectionListeners.stream().forEach(e -> e.onError(throwable));
}
///////////////////////////////////////////////////////////////////////////////////////////
// MessageListener
///////////////////////////////////////////////////////////////////////////////////////////
public void addMessageListener(MessageListener messageListener) {
messageListeners.add(messageListener);
}
public void removeMessageListener(MessageListener messageListener) {
messageListeners.remove(messageListener);
}
@Override
public void onMessage(Message message, Connection connection) {
messageListeners.stream().forEach(e -> e.onMessage(message, connection));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
protected void startServer(ServerSocket serverSocket) {
server = new Server(serverSocket, (message, connection) -> {
NetworkNode.this.onMessage(message, connection);
}, new ConnectionListener() {
@Override
public void onConnection(Connection connection) {
// we still have not authenticated so put it to the temp list
inBoundTempConnections.add(connection);
NetworkNode.this.onConnection(connection);
}
@Override
public void onPeerAddressAuthenticated(Address peerAddress, Connection connection) {
NetworkNode.this.onPeerAddressAuthenticated(peerAddress, connection);
// now we know the the peers address is correct and we add it to inBoundConnections and
// remove it from tempConnections
inBoundAuthenticatedConnections.put(peerAddress, connection);
inBoundTempConnections.remove(connection);
}
@Override
public void onDisconnect(Reason reason, Connection connection) {
Address peerAddress = connection.getPeerAddress();
log.trace("onDisconnect at incoming connection to peerAddress " + peerAddress);
if (peerAddress != null)
inBoundAuthenticatedConnections.remove(peerAddress);
inBoundTempConnections.remove(connection);
NetworkNode.this.onDisconnect(reason, connection);
}
@Override
public void onError(Throwable throwable) {
NetworkNode.this.onError(throwable);
}
});
executorService.submit(server);
}
abstract protected Socket getSocket(Address peerAddress) throws IOException;
@Nullable
abstract public Address getAddress();
}

View file

@ -0,0 +1,75 @@
package io.bitsquare.p2p.network;
import io.bitsquare.p2p.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server implements Runnable {
private static final Logger log = LoggerFactory.getLogger(Server.class);
private final ServerSocket serverSocket;
private final MessageListener messageListener;
private final ConnectionListener connectionListener;
private final ExecutorService executorService = Executors.newCachedThreadPool();
private final List<Connection> connections = new CopyOnWriteArrayList<>();
private volatile boolean stopped;
public Server(ServerSocket serverSocket, MessageListener messageListener, ConnectionListener connectionListener) {
this.serverSocket = serverSocket;
this.messageListener = messageListener;
this.connectionListener = connectionListener;
}
@Override
public void run() {
Thread.currentThread().setName("Server-" + serverSocket.getLocalPort());
while (!stopped) {
try {
log.info("Ready to accept new clients on port " + serverSocket.getLocalPort());
final Socket socket = serverSocket.accept();
log.info("Accepted new client on port " + socket.getLocalPort());
Connection connection = new Connection(socket, messageListener, connectionListener);
log.info("\n\nServer created new inbound connection:"
+ "\nserverSocket.getLocalPort()=" + serverSocket.getLocalPort()
+ "\nsocket.getPort()=" + socket.getPort()
+ "\nconnection.uid=" + connection.getUid()
+ "\n\n");
log.info("Server created new socket with port " + socket.getPort());
connections.add(connection);
} catch (IOException e) {
if (!stopped)
e.printStackTrace();
}
}
}
public void shutDown() {
if (!stopped) {
stopped = true;
connections.stream().forEach(e -> e.shutDown());
try {
serverSocket.close();
} catch (SocketException e) {
log.warn("SocketException at shutdown might be expected " + e.getMessage());
} catch (IOException e) {
e.printStackTrace();
} finally {
Utils.shutDownExecutorService(executorService);
log.debug("Server shutdown complete");
}
}
}
}

View file

@ -0,0 +1,5 @@
package io.bitsquare.p2p.network;
public interface ServerListener {
void onSocketHandler(Connection connection);
}

View file

@ -0,0 +1,12 @@
package io.bitsquare.p2p.network;
public interface SetupListener {
void onTorNodeReady();
void onHiddenServiceReady();
void onSetupFailed(Throwable throwable);
}

View file

@ -0,0 +1,369 @@
package io.bitsquare.p2p.network;
import com.google.common.util.concurrent.*;
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyContext;
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyManager;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.Utils;
import io.bitsquare.p2p.network.messages.SelfTestMessage;
import io.nucleo.net.HiddenServiceDescriptor;
import io.nucleo.net.TorNode;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class TorNetworkNode extends NetworkNode {
private static final Logger log = LoggerFactory.getLogger(TorNetworkNode.class);
private static final Random random = new Random();
private static final long TIMEOUT = 5000;
private static final long SELF_TEST_INTERVAL = 10 * 60 * 1000;
private static final int MAX_ERRORS_BEFORE_RESTART = 3;
private static final int MAX_RESTART_ATTEMPTS = 3;
private static final int WAIT_BEFORE_RESTART = 2000;
private static final long SHUT_DOWN_TIMEOUT = 5000;
private final File torDir;
private TorNode torNode;
private HiddenServiceDescriptor hiddenServiceDescriptor;
private Timer shutDownTimeoutTimer, selfTestTimer, selfTestTimeoutTimer;
private TimerTask selfTestTimeoutTask, selfTestTask;
private AtomicBoolean selfTestRunning = new AtomicBoolean(false);
private int nonce;
private int errorCounter;
private int restartCounter;
private Runnable shutDownCompleteHandler;
private boolean torShutDownComplete, networkNodeShutDownDoneComplete;
// /////////////////////////////////////////////////////////////////////////////////////////
// Constructor
// /////////////////////////////////////////////////////////////////////////////////////////
public TorNetworkNode(int port, File torDir) {
super(port);
this.torDir = torDir;
selfTestTimeoutTask = new TimerTask() {
@Override
public void run() {
log.error("A timeout occurred at self test");
stopSelfTestTimer();
selfTestFailed();
}
};
selfTestTask = new TimerTask() {
@Override
public void run() {
stopTimeoutTimer();
if (selfTestRunning.get()) {
log.debug("running self test");
selfTestTimeoutTimer = new Timer();
selfTestTimeoutTimer.schedule(selfTestTimeoutTask, TIMEOUT);
// might be interrupted by timeout task
if (selfTestRunning.get()) {
nonce = random.nextInt();
log.trace("send msg with nonce " + nonce);
try {
SettableFuture<Connection> future = sendMessage(new Address(hiddenServiceDescriptor.getFullAddress()), new SelfTestMessage(nonce));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("Sending self test message succeeded");
}
@Override
public void onFailure(Throwable throwable) {
log.error("Error at sending self test message. Exception = " + throwable);
stopTimeoutTimer();
throwable.printStackTrace();
selfTestFailed();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
addMessageListener(new MessageListener() {
@Override
public void onMessage(Message message, Connection connection) {
if (message instanceof SelfTestMessage) {
if (((SelfTestMessage) message).nonce == nonce) {
runSelfTest();
} else {
log.error("Nonce not matching our challenge. That should never happen.");
selfTestFailed();
}
}
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void start(@Nullable SetupListener setupListener) {
if (setupListener != null) addSetupListener(setupListener);
// executorService might have been shutdown before a restart, so we create a new one
executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
// Create the tor node (takes about 6 sec.)
createTorNode(torDir, torNode -> {
TorNetworkNode.this.torNode = torNode;
setupListeners.stream().forEach(e -> e.onTorNodeReady());
// Create Hidden Service (takes about 40 sec.)
createHiddenService(torNode, port, hiddenServiceDescriptor -> {
TorNetworkNode.this.hiddenServiceDescriptor = hiddenServiceDescriptor;
startServer(hiddenServiceDescriptor.getServerSocket());
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
setupListeners.stream().forEach(e -> e.onHiddenServiceReady());
// we are ready. so we start our periodic self test if our HS is available
// startSelfTest();
});
});
}
@Override
@Nullable
public Address getAddress() {
if (hiddenServiceDescriptor != null)
return new Address(hiddenServiceDescriptor.getFullAddress());
else
return null;
}
public void shutDown(Runnable shutDownCompleteHandler) {
log.info("Shutdown TorNetworkNode");
this.shutDownCompleteHandler = shutDownCompleteHandler;
checkNotNull(executorService, "executorService must not be null");
selfTestRunning.set(false);
stopSelfTestTimer();
shutDownTimeoutTimer = new Timer();
shutDownTimeoutTimer.schedule(new TimerTask() {
@Override
public void run() {
log.error("A timeout occurred at shutDown");
shutDownExecutorService();
}
}, SHUT_DOWN_TIMEOUT);
executorService.submit(() -> super.shutDown(() -> {
networkNodeShutDownDoneComplete = true;
if (torShutDownComplete)
shutDownExecutorService();
}));
ListenableFuture<?> future2 = executorService.submit(() -> {
long ts = System.currentTimeMillis();
log.info("Shutdown torNode");
try {
if (torNode != null)
torNode.shutdown();
log.info("Shutdown torNode done after " + (System.currentTimeMillis() - ts) + " ms.");
} catch (IOException e) {
e.printStackTrace();
log.error("Shutdown torNode failed with exception: " + e.getMessage());
shutDownExecutorService();
}
});
Futures.addCallback(future2, new FutureCallback<Object>() {
@Override
public void onSuccess(Object o) {
torShutDownComplete = true;
if (networkNodeShutDownDoneComplete)
shutDownExecutorService();
}
@Override
public void onFailure(Throwable throwable) {
throwable.printStackTrace();
log.error("Shutdown torNode failed with exception: " + throwable.getMessage());
shutDownExecutorService();
}
});
}
// /////////////////////////////////////////////////////////////////////////////////////////
// shutdown, restart
// /////////////////////////////////////////////////////////////////////////////////////////
private void shutDownExecutorService() {
shutDownTimeoutTimer.cancel();
ListenableFuture<?> future = executorService.submit(() -> {
long ts = System.currentTimeMillis();
log.info("Shutdown executorService");
Utils.shutDownExecutorService(executorService);
log.info("Shutdown executorService done after " + (System.currentTimeMillis() - ts) + " ms.");
});
Futures.addCallback(future, new FutureCallback<Object>() {
@Override
public void onSuccess(Object o) {
log.info("Shutdown completed");
new Thread(shutDownCompleteHandler).start();
}
@Override
public void onFailure(Throwable throwable) {
throwable.printStackTrace();
log.error("Shutdown executorService failed with exception: " + throwable.getMessage());
new Thread(shutDownCompleteHandler).start();
}
});
}
private void restartTor() {
restartCounter++;
if (restartCounter <= MAX_RESTART_ATTEMPTS) {
shutDown(() -> {
try {
Thread.sleep(WAIT_BEFORE_RESTART);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
log.warn("We restart tor as too many self tests failed.");
start(null);
});
} else {
log.error("We tried to restart tor " + restartCounter
+ " times, but we failed to get tor running. We give up now.");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// create tor
///////////////////////////////////////////////////////////////////////////////////////////
private void createTorNode(final File torDir, final Consumer<TorNode> resultHandler) {
Callable<TorNode<JavaOnionProxyManager, JavaOnionProxyContext>> task = () -> {
long ts = System.currentTimeMillis();
if (torDir.mkdirs())
log.trace("Created directory for tor");
log.trace("Create TorNode");
TorNode<JavaOnionProxyManager, JavaOnionProxyContext> torNode1 = new TorNode<JavaOnionProxyManager, JavaOnionProxyContext>(
torDir) {
};
log.trace("\n\n##### TorNode created. Took " + (System.currentTimeMillis() - ts) + " ms\n\n");
return torNode1;
};
ListenableFuture<TorNode<JavaOnionProxyManager, JavaOnionProxyContext>> future = executorService.submit(task);
Futures.addCallback(future, new FutureCallback<TorNode<JavaOnionProxyManager, JavaOnionProxyContext>>() {
public void onSuccess(TorNode<JavaOnionProxyManager, JavaOnionProxyContext> torNode) {
resultHandler.accept(torNode);
}
public void onFailure(Throwable throwable) {
log.error("TorNode creation failed");
restartTor();
}
});
}
private void createHiddenService(final TorNode torNode, final int port,
final Consumer<HiddenServiceDescriptor> resultHandler) {
Callable<HiddenServiceDescriptor> task = () -> {
long ts = System.currentTimeMillis();
log.debug("Create hidden service");
HiddenServiceDescriptor hiddenServiceDescriptor = torNode.createHiddenService(port);
log.debug("\n\n##### Hidden service created. Address = " + hiddenServiceDescriptor.getFullAddress() + ". Took " + (System.currentTimeMillis() - ts) + " ms\n\n");
return hiddenServiceDescriptor;
};
ListenableFuture<HiddenServiceDescriptor> future = executorService.submit(task);
Futures.addCallback(future, new FutureCallback<HiddenServiceDescriptor>() {
public void onSuccess(HiddenServiceDescriptor hiddenServiceDescriptor) {
resultHandler.accept(hiddenServiceDescriptor);
}
public void onFailure(Throwable throwable) {
log.error("Hidden service creation failed");
restartTor();
}
});
}
// /////////////////////////////////////////////////////////////////////////////////////////
// Self test
// /////////////////////////////////////////////////////////////////////////////////////////
private void startSelfTest() {
selfTestRunning.set(true);
//addListener(messageListener);
runSelfTest();
}
private void runSelfTest() {
stopSelfTestTimer();
selfTestTimer = new Timer();
selfTestTimer.schedule(selfTestTask, SELF_TEST_INTERVAL);
}
private void stopSelfTestTimer() {
stopTimeoutTimer();
if (selfTestTimer != null)
selfTestTimer.cancel();
}
private void stopTimeoutTimer() {
if (selfTestTimeoutTimer != null)
selfTestTimeoutTimer.cancel();
}
private void selfTestFailed() {
errorCounter++;
log.warn("Self test failed. Already " + errorCounter + " failure(s). Max. errors before restart: "
+ MAX_ERRORS_BEFORE_RESTART);
if (errorCounter >= MAX_ERRORS_BEFORE_RESTART)
restartTor();
else
runSelfTest();
}
@Override
protected Socket getSocket(Address peerAddress) throws IOException {
checkArgument(peerAddress.hostName.endsWith(".onion"), "PeerAddress is not an onion address");
return torNode.connectToHiddenService(peerAddress.hostName, peerAddress.port);
}
}

View file

@ -0,0 +1,10 @@
package io.bitsquare.p2p.network.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Message;
public final class CloseConnectionMessage implements Message {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
}

View file

@ -0,0 +1,15 @@
package io.bitsquare.p2p.network.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Message;
public final class SelfTestMessage implements Message {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final Integer nonce;
public SelfTestMessage(Integer nonce) {
this.nonce = nonce;
}
}

View file

@ -0,0 +1,17 @@
package io.bitsquare.p2p.routing;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.network.Connection;
public abstract class AuthenticationListener implements RoutingListener {
public void onFirstNeighborAdded(Neighbor neighbor) {
}
public void onNeighborAdded(Neighbor neighbor) {
}
public void onNeighborRemoved(Address address) {
}
abstract public void onConnectionAuthenticated(Connection connection);
}

View file

@ -0,0 +1,49 @@
package io.bitsquare.p2p.routing;
import io.bitsquare.p2p.Address;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
public class Neighbor implements Serializable {
private static final Logger log = LoggerFactory.getLogger(Neighbor.class);
public final Address address;
private int pingNonce;
public Neighbor(Address address) {
this.address = address;
}
public void setPingNonce(int pingNonce) {
this.pingNonce = pingNonce;
}
public int getPingNonce() {
return pingNonce;
}
@Override
public int hashCode() {
return address != null ? address.hashCode() : 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Neighbor)) return false;
Neighbor neighbor = (Neighbor) o;
return !(address != null ? !address.equals(neighbor.address) : neighbor.address != null);
}
@Override
public String toString() {
return "Neighbor{" +
"address=" + address +
", pingNonce=" + pingNonce +
'}';
}
}

View file

@ -0,0 +1,592 @@
package io.bitsquare.p2p.routing;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.Utils;
import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.routing.messages.*;
import io.bitsquare.p2p.storage.messages.BroadcastMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public class Routing {
private static final Logger log = LoggerFactory.getLogger(Routing.class);
private static int MAX_CONNECTIONS = 8;
private long startAuthTs;
public static void setMaxConnections(int maxConnections) {
MAX_CONNECTIONS = maxConnections;
}
private final NetworkNode networkNode;
private final List<Address> seedNodes;
private final Map<Address, Integer> nonceMap = new ConcurrentHashMap<>();
private final List<RoutingListener> routingListeners = new CopyOnWriteArrayList<>();
private final Map<Address, Neighbor> connectedNeighbors = new ConcurrentHashMap<>();
private final Map<Address, Neighbor> reportedNeighbors = new ConcurrentHashMap<>();
private final Map<Address, Runnable> authenticationCompleteHandlers = new ConcurrentHashMap<>();
private final Timer maintenanceTimer = new Timer();
private final ExecutorService executorService = Executors.newCachedThreadPool();
private volatile boolean shutDownInProgress;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public Routing(final NetworkNode networkNode, List<Address> seeds) {
this.networkNode = networkNode;
// We copy it as we remove ourselves later from the list if we are a seed node
this.seedNodes = new CopyOnWriteArrayList<>(seeds);
networkNode.addMessageListener((message, connection) -> {
if (message instanceof AuthenticationMessage)
processAuthenticationMessage((AuthenticationMessage) message, connection);
else if (message instanceof MaintenanceMessage)
processMaintenanceMessage((MaintenanceMessage) message, connection);
});
networkNode.addConnectionListener(new ConnectionListener() {
@Override
public void onConnection(Connection connection) {
}
@Override
public void onPeerAddressAuthenticated(Address peerAddress, Connection connection) {
}
@Override
public void onDisconnect(Reason reason, Connection connection) {
// only removes authenticated nodes
if (connection.getPeerAddress() != null)
removeNeighbor(connection.getPeerAddress());
}
@Override
public void onError(Throwable throwable) {
}
});
networkNode.addSetupListener(new SetupListener() {
@Override
public void onTorNodeReady() {
}
@Override
public void onHiddenServiceReady() {
// remove ourselves in case we are a seed node
Address myAddress = getAddress();
if (myAddress != null)
seedNodes.remove(myAddress);
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
int maintenanceInterval = new Random().nextInt(15 * 60 * 1000) + 15 * 60 * 1000; // 15-30 min.
maintenanceTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
disconnectOldConnections();
pingNeighbors();
}
}, maintenanceInterval, maintenanceInterval);
}
private void disconnectOldConnections() {
List<Connection> authenticatedConnections = networkNode.getAllConnections().stream()
.filter(e -> e.isAuthenticated())
.collect(Collectors.toList());
if (authenticatedConnections.size() > MAX_CONNECTIONS) {
authenticatedConnections.sort((o1, o2) -> o1.getLastActivityDate().compareTo(o2.getLastActivityDate()));
log.info("Number of connections exceeds MAX_CONNECTIONS. Current size=" + authenticatedConnections.size());
Connection connection = authenticatedConnections.remove(0);
log.info("Shutdown oldest connection with last activity date=" + connection.getLastActivityDate() + " / connection=" + connection);
connection.shutDown(() -> disconnectOldConnections());
try {
Thread.sleep(200);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
}
private void pingNeighbors() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void shutDown() {
if (!shutDownInProgress) {
shutDownInProgress = true;
if (maintenanceTimer != null)
maintenanceTimer.cancel();
Utils.shutDownExecutorService(executorService);
}
}
public void broadcast(BroadcastMessage message, Address sender) {
log.trace("Broadcast message to " + connectedNeighbors.values().size() + " neighbors.");
connectedNeighbors.values().parallelStream()
.filter(e -> !e.address.equals(sender))
.forEach(neighbor -> {
log.trace("Broadcast message " + message + " from " + getAddress() + " to " + neighbor.address + ".");
SettableFuture<Connection> future = networkNode.sendMessage(neighbor.address, message);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("Broadcast from " + getAddress() + " to " + neighbor.address + " succeeded.");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.info("Broadcast failed. " + throwable.getMessage());
removeNeighbor(neighbor.address);
}
});
});
}
public void addMessageListener(MessageListener messageListener) {
networkNode.addMessageListener(messageListener);
}
public void removeMessageListener(MessageListener messageListener) {
networkNode.removeMessageListener(messageListener);
}
public void addRoutingListener(RoutingListener routingListener) {
routingListeners.add(routingListener);
}
public void removeRoutingListener(RoutingListener routingListener) {
routingListeners.remove(routingListener);
}
public Map<Address, Neighbor> getReportedNeighbors() {
return reportedNeighbors;
}
public Map<Address, Neighbor> getConnectedNeighbors() {
return connectedNeighbors;
}
public Map<Address, Neighbor> getAllNeighbors() {
Map<Address, Neighbor> hashMap = new ConcurrentHashMap<>(reportedNeighbors);
hashMap.putAll(connectedNeighbors);
// remove own address and seed nodes
hashMap.remove(getAddress());
return hashMap;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Authentication
///////////////////////////////////////////////////////////////////////////////////////////
// authentication example:
// node2 -> node1 RequestAuthenticationMessage
// node1: close connection
// node1 -> node2 ChallengeMessage on new connection
// node2: authentication to node1 done if nonce ok
// node2 -> node1 GetNeighborsMessage
// node1: authentication to node2 done if nonce ok
// node1 -> node2 NeighborsMessage
public void startAuthentication(List<Address> connectedSeedNodes) {
connectedSeedNodes.forEach(connectedSeedNode -> {
executorService.submit(() -> {
sendRequestAuthenticationMessage(seedNodes, connectedSeedNode);
try {
// give a random pause of 3-5 sec. before using the next
Thread.sleep(new Random().nextInt(2000) + 3000);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
});
});
}
private void sendRequestAuthenticationMessage(final List<Address> remainingSeedNodes, final Address address) {
log.info("We try to authenticate to a random seed node. " + address);
startAuthTs = System.currentTimeMillis();
final boolean[] alreadyConnected = {false};
connectedNeighbors.values().stream().forEach(e -> {
remainingSeedNodes.remove(e.address);
if (address.equals(e.address))
alreadyConnected[0] = true;
});
if (!alreadyConnected[0]) {
int nonce = addToMapAndGetNonce(address);
SettableFuture<Connection> future = networkNode.sendMessage(address, new RequestAuthenticationMessage(getAddress(), nonce));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.info("send RequestAuthenticationMessage to " + address + " succeeded.");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.info("Send RequestAuthenticationMessage to " + address + " failed. Exception:" + throwable.getMessage());
log.trace("We try to authenticate to another random seed nodes of that list: " + remainingSeedNodes);
getNextSeedNode(remainingSeedNodes);
}
});
} else {
getNextSeedNode(remainingSeedNodes);
}
}
private void getNextSeedNode(List<Address> remainingSeedNodes) {
List<Address> remainingSeedNodeAddresses = new CopyOnWriteArrayList<>(remainingSeedNodes);
Address myAddress = getAddress();
if (myAddress != null)
remainingSeedNodeAddresses.remove(myAddress);
if (!remainingSeedNodeAddresses.isEmpty()) {
Collections.shuffle(remainingSeedNodeAddresses);
Address address = remainingSeedNodeAddresses.remove(0);
sendRequestAuthenticationMessage(remainingSeedNodeAddresses, address);
} else {
log.info("No other seed node found. That is expected for the first seed node.");
}
}
private void processAuthenticationMessage(AuthenticationMessage message, Connection connection) {
log.trace("processAuthenticationMessage " + message + " from " + connection.getPeerAddress() + " at " + getAddress());
if (message instanceof RequestAuthenticationMessage) {
RequestAuthenticationMessage requestAuthenticationMessage = (RequestAuthenticationMessage) message;
Address peerAddress = requestAuthenticationMessage.address;
log.trace("RequestAuthenticationMessage from " + peerAddress + " at " + getAddress());
connection.shutDown(() -> {
// we delay a bit as listeners for connection.onDisconnect are on other threads and might lead to
// inconsistent state (removal of connection from NetworkNode.authenticatedConnections)
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
log.trace("processAuthenticationMessage: connection.shutDown complete. RequestAuthenticationMessage from " + peerAddress + " at " + getAddress());
int nonce = addToMapAndGetNonce(peerAddress);
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new ChallengeMessage(getAddress(), requestAuthenticationMessage.nonce, nonce));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.debug("onSuccess ");
// TODO check nr. of connections, remove older connections (?)
}
@Override
public void onFailure(Throwable throwable) {
log.debug("onFailure ");
// TODO skip to next node or retry?
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new ChallengeMessage(getAddress(), requestAuthenticationMessage.nonce, nonce));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.debug("onSuccess ");
}
@Override
public void onFailure(Throwable throwable) {
log.debug("onFailure ");
}
});
}
});
});
} else if (message instanceof ChallengeMessage) {
ChallengeMessage challengeMessage = (ChallengeMessage) message;
Address peerAddress = challengeMessage.address;
connection.setPeerAddress(peerAddress);
log.trace("ChallengeMessage from " + peerAddress + " at " + getAddress());
boolean verified = verifyNonceAndAuthenticatePeerAddress(challengeMessage.requesterNonce, peerAddress);
if (verified) {
HashMap<Address, Neighbor> allNeighbors = new HashMap<>(getAllNeighbors());
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new GetNeighborsMessage(getAddress(), challengeMessage.challengerNonce, allNeighbors));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("GetNeighborsMessage sent successfully from " + getAddress() + " to " + peerAddress);
// we wait to get the success to reduce the time span of the moment of
// authentication at both sides of the connection
setAuthenticated(connection, peerAddress);
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.info("GetNeighborsMessage sending failed " + throwable.getMessage());
removeNeighbor(peerAddress);
}
});
}
} else if (message instanceof GetNeighborsMessage) {
GetNeighborsMessage getNeighborsMessage = (GetNeighborsMessage) message;
Address peerAddress = getNeighborsMessage.address;
log.trace("GetNeighborsMessage from " + peerAddress + " at " + getAddress());
boolean verified = verifyNonceAndAuthenticatePeerAddress(getNeighborsMessage.challengerNonce, peerAddress);
if (verified) {
setAuthenticated(connection, peerAddress);
purgeReportedNeighbors();
HashMap<Address, Neighbor> allNeighbors = new HashMap<>(getAllNeighbors());
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new NeighborsMessage(allNeighbors));
log.trace("sent NeighborsMessage to " + peerAddress + " from " + getAddress() + " with allNeighbors=" + allNeighbors.values());
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("NeighborsMessage sent successfully from " + getAddress() + " to " + peerAddress);
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.info("NeighborsMessage sending failed " + throwable.getMessage());
removeNeighbor(peerAddress);
}
});
// now we add the reported neighbors to our own set
final HashMap<Address, Neighbor> neighbors = ((GetNeighborsMessage) message).neighbors;
log.trace("Received neighbors: " + neighbors);
// remove ourselves
neighbors.remove(getAddress());
addToReportedNeighbors(neighbors, connection);
}
} else if (message instanceof NeighborsMessage) {
log.trace("NeighborsMessage from " + connection.getPeerAddress() + " at " + getAddress());
final HashMap<Address, Neighbor> neighbors = ((NeighborsMessage) message).neighbors;
log.trace("Received neighbors: " + neighbors);
// remove ourselves
neighbors.remove(getAddress());
addToReportedNeighbors(neighbors, connection);
log.info("\n\nAuthenticationComplete\nPeer with address " + connection.getPeerAddress().toString()
+ " authenticated (" + connection.getObjectId() + "). Took "
+ (System.currentTimeMillis() - startAuthTs) + " ms. \n\n");
Runnable authenticationCompleteHandler = authenticationCompleteHandlers.remove(connection.getPeerAddress());
if (authenticationCompleteHandler != null)
authenticationCompleteHandler.run();
authenticateToNextRandomNeighbor();
}
}
private void addToReportedNeighbors(HashMap<Address, Neighbor> neighbors, Connection connection) {
// we disconnect misbehaving nodes trying to send too many neighbors
// reported neighbors include the peers connected neighbors which is normally max. 8 but we give some headroom
// for safety
if (neighbors.size() > 1100) {
connection.shutDown();
} else {
reportedNeighbors.putAll(neighbors);
purgeReportedNeighbors();
}
}
private void purgeReportedNeighbors() {
int all = getAllNeighbors().size();
if (all > 1000) {
int diff = all - 100;
ArrayList<Neighbor> reportedNeighborsList = new ArrayList<>(reportedNeighbors.values());
for (int i = 0; i < diff; i++) {
Neighbor neighborToRemove = reportedNeighborsList.remove(new Random().nextInt(reportedNeighborsList.size()));
reportedNeighbors.remove(neighborToRemove.address);
}
}
}
private void authenticateToNextRandomNeighbor() {
if (getConnectedNeighbors().size() <= MAX_CONNECTIONS) {
Neighbor randomNotConnectedNeighbor = getRandomNotConnectedNeighbor();
if (randomNotConnectedNeighbor != null) {
log.info("We try to build an authenticated connection to a random neighbor. " + randomNotConnectedNeighbor);
authenticateToPeer(randomNotConnectedNeighbor.address, null, () -> authenticateToNextRandomNeighbor());
} else {
log.info("No more neighbors available for connecting.");
}
} else {
log.info("We have already enough connections.");
}
}
public void authenticateToPeer(Address address, @Nullable Runnable authenticationCompleteHandler, @Nullable Runnable faultHandler) {
startAuthTs = System.currentTimeMillis();
if (authenticationCompleteHandler != null)
authenticationCompleteHandlers.put(address, authenticationCompleteHandler);
int nonce = addToMapAndGetNonce(address);
SettableFuture<Connection> future = networkNode.sendMessage(address, new RequestAuthenticationMessage(getAddress(), nonce));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.debug("send RequestAuthenticationMessage succeeded");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.info("send IdMessage failed. " + throwable.getMessage());
removeNeighbor(address);
if (faultHandler != null) faultHandler.run();
}
});
}
private int addToMapAndGetNonce(Address address) {
int nonce = new Random().nextInt();
while (nonce == 0) {
nonce = new Random().nextInt();
}
nonceMap.put(address, nonce);
return nonce;
}
private boolean verifyNonceAndAuthenticatePeerAddress(int peersNonce, Address peerAddress) {
int nonce = nonceMap.remove(peerAddress);
boolean result = nonce == peersNonce;
return result;
}
private void setAuthenticated(Connection connection, Address address) {
log.info("We got the connection from " + getAddress() + " to " + address + " authenticated.");
Neighbor neighbor = new Neighbor(address);
addConnectedNeighbor(address, neighbor);
connection.onAuthenticationComplete(address, connection);
routingListeners.stream().forEach(e -> e.onConnectionAuthenticated(connection));
}
private Neighbor getRandomNotConnectedNeighbor() {
List<Neighbor> list = reportedNeighbors.values().stream()
.filter(e -> !connectedNeighbors.values().contains(e))
.collect(Collectors.toList());
if (list.size() > 0) {
Collections.shuffle(list);
return list.get(0);
} else {
return null;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Maintenance
///////////////////////////////////////////////////////////////////////////////////////////
private void processMaintenanceMessage(MaintenanceMessage message, Connection connection) {
log.debug("Received routing message " + message + " at " + getAddress() + " from " + connection.getPeerAddress());
if (message instanceof PingMessage) {
SettableFuture<Connection> future = networkNode.sendMessage(connection, new PongMessage(((PingMessage) message).nonce));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("PongMessage sent successfully");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.info("PongMessage sending failed " + throwable.getMessage());
removeNeighbor(connection.getPeerAddress());
}
});
} else if (message instanceof PongMessage) {
Neighbor neighbor = connectedNeighbors.get(connection.getPeerAddress());
if (neighbor != null) {
if (((PongMessage) message).nonce != neighbor.getPingNonce()) {
removeNeighbor(neighbor.address);
log.warn("PongMessage invalid: self/peer " + getAddress() + "/" + connection.getPeerAddress());
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Neighbors
///////////////////////////////////////////////////////////////////////////////////////////
private void removeNeighbor(Address address) {
reportedNeighbors.remove(address);
Neighbor disconnectedNeighbor;
disconnectedNeighbor = connectedNeighbors.remove(address);
if (disconnectedNeighbor != null)
UserThread.execute(() -> routingListeners.stream().forEach(e -> e.onNeighborRemoved(address)));
nonceMap.remove(address);
}
private void addConnectedNeighbor(Address address, Neighbor neighbor) {
boolean firstNeighborAdded;
connectedNeighbors.put(address, neighbor);
firstNeighborAdded = connectedNeighbors.size() == 1;
UserThread.execute(() -> routingListeners.stream().forEach(e -> e.onNeighborAdded(neighbor)));
if (firstNeighborAdded)
UserThread.execute(() -> routingListeners.stream().forEach(e -> e.onFirstNeighborAdded(neighbor)));
if (connectedNeighbors.size() > MAX_CONNECTIONS)
disconnectOldConnections();
}
private Address getAddress() {
return networkNode.getAddress();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
public void printConnectedNeighborsMap() {
StringBuilder result = new StringBuilder("\nConnected neighbors for node " + getAddress() + ":");
connectedNeighbors.values().stream().forEach(e -> {
result.append("\n\t" + e.address);
});
result.append("\n");
log.info(result.toString());
}
public void printReportedNeighborsMap() {
StringBuilder result = new StringBuilder("\nReported neighbors for node " + getAddress() + ":");
reportedNeighbors.values().stream().forEach(e -> {
result.append("\n\t" + e.address);
});
result.append("\n");
log.info(result.toString());
}
private String getObjectId() {
return super.toString().split("@")[1].toString();
}
}

View file

@ -0,0 +1,15 @@
package io.bitsquare.p2p.routing;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.network.Connection;
public interface RoutingListener {
void onFirstNeighborAdded(Neighbor neighbor);
void onNeighborAdded(Neighbor neighbor);
void onNeighborRemoved(Address address);
// TODO remove
void onConnectionAuthenticated(Connection connection);
}

View file

@ -0,0 +1,6 @@
package io.bitsquare.p2p.routing.messages;
import io.bitsquare.p2p.Message;
public interface AuthenticationMessage extends Message {
}

View file

@ -0,0 +1,28 @@
package io.bitsquare.p2p.routing.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Address;
public final class ChallengeMessage implements AuthenticationMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final Address address;
public final int requesterNonce;
public final int challengerNonce;
public ChallengeMessage(Address address, int requesterNonce, int challengerNonce) {
this.address = address;
this.requesterNonce = requesterNonce;
this.challengerNonce = challengerNonce;
}
@Override
public String toString() {
return "ChallengeMessage{" +
"address=" + address +
", requesterNonce=" + requesterNonce +
", challengerNonce=" + challengerNonce +
'}';
}
}

View file

@ -0,0 +1,31 @@
package io.bitsquare.p2p.routing.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.routing.Neighbor;
import java.util.HashMap;
public final class GetNeighborsMessage implements AuthenticationMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final Address address;
public final int challengerNonce;
public final HashMap<Address, Neighbor> neighbors;
public GetNeighborsMessage(Address address, int challengerNonce, HashMap<Address, Neighbor> neighbors) {
this.address = address;
this.challengerNonce = challengerNonce;
this.neighbors = neighbors;
}
@Override
public String toString() {
return "GetNeighborsMessage{" +
"address=" + address +
", challengerNonce=" + challengerNonce +
", neighbors=" + neighbors +
'}';
}
}

View file

@ -0,0 +1,6 @@
package io.bitsquare.p2p.routing.messages;
import io.bitsquare.p2p.Message;
public interface MaintenanceMessage extends Message {
}

View file

@ -0,0 +1,24 @@
package io.bitsquare.p2p.routing.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.routing.Neighbor;
import java.util.HashMap;
public final class NeighborsMessage implements AuthenticationMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final HashMap<Address, Neighbor> neighbors;
public NeighborsMessage(HashMap<Address, Neighbor> neighbors) {
this.neighbors = neighbors;
}
@Override
public String toString() {
return "NeighborsMessage{" + "neighbors=" + neighbors + '}';
}
}

View file

@ -0,0 +1,21 @@
package io.bitsquare.p2p.routing.messages;
import io.bitsquare.app.Version;
public final class PingMessage implements MaintenanceMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final int nonce;
public PingMessage(int nonce) {
this.nonce = nonce;
}
@Override
public String toString() {
return "PingMessage{" +
"nonce=" + nonce +
'}';
}
}

View file

@ -0,0 +1,21 @@
package io.bitsquare.p2p.routing.messages;
import io.bitsquare.app.Version;
public final class PongMessage implements MaintenanceMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final int nonce;
public PongMessage(int nonce) {
this.nonce = nonce;
}
@Override
public String toString() {
return "PongMessage{" +
"nonce=" + nonce +
'}';
}
}

View file

@ -0,0 +1,25 @@
package io.bitsquare.p2p.routing.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Address;
public final class RequestAuthenticationMessage implements AuthenticationMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final Address address;
public final int nonce;
public RequestAuthenticationMessage(Address address, int nonce) {
this.address = address;
this.nonce = nonce;
}
@Override
public String toString() {
return "RequestAuthenticationMessage{" +
"address=" + address +
", nonce=" + nonce +
'}';
}
}

View file

@ -0,0 +1,110 @@
package io.bitsquare.p2p.seed;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.*;
public class SeedNode {
private static final Logger log = LoggerFactory.getLogger(SeedNode.class);
private int port = 8001;
private boolean useLocalhost = true;
private List<Address> seedNodes;
private P2PService p2PService;
protected boolean stopped;
public SeedNode() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
// args: port useLocalhost seedNodes
// eg. 4444 true localhost:7777 localhost:8888
// To stop enter: q
public void processArgs(String[] args) {
if (args.length > 0) {
port = Integer.parseInt(args[0]);
if (args.length > 1) {
useLocalhost = ("true").equals(args[1]);
if (args.length > 2) {
seedNodes = new ArrayList<>();
for (int i = 2; i < args.length; i++) {
seedNodes.add(new Address(args[i]));
}
}
}
}
}
public void listenForExitCommand() {
Scanner scan = new Scanner(System.in);
String line;
while (!stopped && ((line = scan.nextLine()) != null)) {
if (line.equals("q")) {
Timer timeout = new Timer();
timeout.schedule(new TimerTask() {
@Override
public void run() {
log.error("Timeout occurred at shutDown request");
System.exit(1);
}
}, 10 * 1000);
shutDown(() -> {
timeout.cancel();
log.debug("Shutdown seed node complete.");
System.exit(0);
});
}
}
}
public void createAndStartP2PService() {
createAndStartP2PService(null, null, port, useLocalhost, seedNodes, null);
}
public void createAndStartP2PService(EncryptionService encryptionService, KeyRing keyRing, int port, boolean useLocalhost, @Nullable List<Address> seedNodes, @Nullable P2PServiceListener listener) {
SeedNodesRepository seedNodesRepository = new SeedNodesRepository();
if (seedNodes != null && !seedNodes.isEmpty()) {
if (useLocalhost)
seedNodesRepository.setLocalhostSeedNodeAddresses(seedNodes);
else
seedNodesRepository.setTorSeedNodeAddresses(seedNodes);
}
p2PService = new P2PService(seedNodesRepository, port, new File("bitsquare_seed_node_" + port), useLocalhost, encryptionService, keyRing);
p2PService.start(listener);
}
public P2PService getP2PService() {
return p2PService;
}
public void shutDown() {
shutDown(null);
}
public void shutDown(@Nullable Runnable shutDownCompleteHandler) {
log.debug("Request shutdown seed node");
if (!stopped) {
stopped = true;
p2PService.shutDown(() -> {
if (shutDownCompleteHandler != null) new Thread(shutDownCompleteHandler).start();
});
}
}
}

View file

@ -0,0 +1,41 @@
package io.bitsquare.p2p.seed;
import io.bitsquare.p2p.Address;
import java.util.Arrays;
import java.util.List;
public class SeedNodesRepository {
protected List<Address> torSeedNodeAddresses = Arrays.asList(
new Address("3anjm5mw2sr6abx6.onion:8001")
);
protected List<Address> localhostSeedNodeAddresses = Arrays.asList(
new Address("localhost:8001"),
new Address("localhost:8002"),
new Address("localhost:8003")
);
public List<Address> getTorSeedNodeAddresses() {
return torSeedNodeAddresses;
}
public List<Address> geSeedNodeAddresses(boolean useLocalhost) {
return useLocalhost ? localhostSeedNodeAddresses : torSeedNodeAddresses;
}
public List<Address> getLocalhostSeedNodeAddresses() {
return localhostSeedNodeAddresses;
}
public void setTorSeedNodeAddresses(List<Address> torSeedNodeAddresses) {
this.torSeedNodeAddresses = torSeedNodeAddresses;
}
public void setLocalhostSeedNodeAddresses(List<Address> localhostSeedNodeAddresses) {
this.localhostSeedNodeAddresses = localhostSeedNodeAddresses;
}
}

View file

@ -0,0 +1,9 @@
package io.bitsquare.p2p.storage;
import io.bitsquare.p2p.storage.data.ProtectedData;
public interface HashSetChangedListener {
void onAdded(ProtectedData entry);
void onRemoved(ProtectedData entry);
}

View file

@ -0,0 +1,308 @@
package io.bitsquare.p2p.storage;
import com.google.common.annotations.VisibleForTesting;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.CryptoUtil;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.network.IllegalRequest;
import io.bitsquare.p2p.network.MessageListener;
import io.bitsquare.p2p.routing.Routing;
import io.bitsquare.p2p.storage.data.*;
import io.bitsquare.p2p.storage.messages.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.security.*;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class ProtectedExpirableDataStorage {
private static final Logger log = LoggerFactory.getLogger(ProtectedExpirableDataStorage.class);
@VisibleForTesting
public static int CHECK_TTL_INTERVAL = 10 * 60 * 1000;
private final Routing routing;
private final EncryptionService encryptionService;
private final Map<BigInteger, ProtectedData> map = new ConcurrentHashMap<>();
private final List<HashSetChangedListener> hashSetChangedListeners = new CopyOnWriteArrayList<>();
private final Map<BigInteger, Integer> sequenceNumberMap = new ConcurrentHashMap<>();
private boolean authenticated;
private final Timer timer = new Timer();
private volatile boolean shutDownInProgress;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public ProtectedExpirableDataStorage(Routing routing, EncryptionService encryptionService) {
this.routing = routing;
this.encryptionService = encryptionService;
addMessageListener((message, connection) -> {
if (message instanceof DataMessage) {
if (connection.isAuthenticated()) {
log.trace("ProtectedExpirableDataMessage received " + message + " on connection " + connection);
if (message instanceof AddDataMessage) {
add(((AddDataMessage) message).data, connection.getPeerAddress());
} else if (message instanceof RemoveDataMessage) {
remove(((RemoveDataMessage) message).data, connection.getPeerAddress());
} else if (message instanceof RemoveMailboxDataMessage) {
removeMailboxData(((RemoveMailboxDataMessage) message).data, connection.getPeerAddress());
}
} else {
log.warn("Connection is not authenticated yet. We don't accept storage operations form non-authenticated nodes.");
connection.reportIllegalRequest(IllegalRequest.NotAuthenticated);
}
}
});
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
log.info("removeExpiredEntries called ");
map.entrySet().stream().filter(entry -> entry.getValue().isExpired())
.forEach(entry -> map.remove(entry.getKey()));
}
},
CHECK_TTL_INTERVAL,
CHECK_TTL_INTERVAL);
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void shutDown() {
if (!shutDownInProgress) {
shutDownInProgress = true;
timer.cancel();
routing.shutDown();
}
}
public void setAuthenticated(boolean authenticated) {
this.authenticated = authenticated;
}
public boolean add(ProtectedData protectedData, Address sender) {
BigInteger hashOfPayload = getHashAsBigInteger(protectedData.expirablePayload);
boolean containsKey = map.containsKey(hashOfPayload);
boolean result = checkPublicKeys(protectedData, true)
&& isSequenceNrValid(protectedData, hashOfPayload)
&& checkSignature(protectedData)
&& (!containsKey || checkIfStoredDataMatchesNewData(protectedData, hashOfPayload))
&& doAddProtectedExpirableData(protectedData, hashOfPayload, sender);
if (result)
sequenceNumberMap.put(hashOfPayload, protectedData.sequenceNumber);
else
log.debug("add failed");
return result;
}
public boolean remove(ProtectedData protectedData, Address sender) {
BigInteger hashOfPayload = getHashAsBigInteger(protectedData.expirablePayload);
boolean containsKey = map.containsKey(hashOfPayload);
if (!containsKey) log.debug("Remove data ignored as we don't have an entry for that data.");
boolean result = containsKey
&& checkPublicKeys(protectedData, false)
&& isSequenceNrValid(protectedData, hashOfPayload)
&& checkSignature(protectedData)
&& checkIfStoredDataMatchesNewData(protectedData, hashOfPayload)
&& doRemoveProtectedExpirableData(protectedData, hashOfPayload, sender);
if (result)
sequenceNumberMap.put(hashOfPayload, protectedData.sequenceNumber);
else
log.debug("remove failed");
return result;
}
public boolean removeMailboxData(ProtectedMailboxData protectedMailboxData, Address sender) {
BigInteger hashOfData = getHashAsBigInteger(protectedMailboxData.expirablePayload);
boolean containsKey = map.containsKey(hashOfData);
if (!containsKey) log.debug("Remove data ignored as we don't have an entry for that data.");
boolean result = containsKey
&& checkPublicKeys(protectedMailboxData, false)
&& isSequenceNrValid(protectedMailboxData, hashOfData)
&& protectedMailboxData.receiversPubKey.equals(protectedMailboxData.ownerStoragePubKey) // at remove both keys are the same (only receiver is able to remove data)
&& checkSignature(protectedMailboxData)
&& checkIfStoredMailboxDataMatchesNewMailboxData(protectedMailboxData, hashOfData)
&& doRemoveProtectedExpirableData(protectedMailboxData, hashOfData, sender);
if (result)
sequenceNumberMap.put(hashOfData, protectedMailboxData.sequenceNumber);
else
log.debug("removeMailboxData failed");
return result;
}
public Map<BigInteger, ProtectedData> getMap() {
return map;
}
public ProtectedData getDataWithSignedSeqNr(ExpirablePayload payload, KeyPair ownerStoragePubKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
BigInteger hashOfData = getHashAsBigInteger(payload);
int sequenceNumber;
if (sequenceNumberMap.containsKey(hashOfData))
sequenceNumber = sequenceNumberMap.get(hashOfData) + 1;
else
sequenceNumber = 0;
byte[] hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(payload, sequenceNumber));
byte[] signature = CryptoUtil.signStorageData(ownerStoragePubKey.getPrivate(), hashOfDataAndSeqNr);
return new ProtectedData(payload, payload.getTTL(), ownerStoragePubKey.getPublic(), sequenceNumber, signature);
}
public ProtectedMailboxData getMailboxDataWithSignedSeqNr(ExpirableMailboxPayload expirableMailboxPayload, KeyPair storageSignaturePubKey, PublicKey receiversPublicKey)
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
BigInteger hashOfData = getHashAsBigInteger(expirableMailboxPayload);
int sequenceNumber;
if (sequenceNumberMap.containsKey(hashOfData))
sequenceNumber = sequenceNumberMap.get(hashOfData) + 1;
else
sequenceNumber = 0;
byte[] hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(expirableMailboxPayload, sequenceNumber));
byte[] signature = CryptoUtil.signStorageData(storageSignaturePubKey.getPrivate(), hashOfDataAndSeqNr);
return new ProtectedMailboxData(expirableMailboxPayload, expirableMailboxPayload.getTTL(), storageSignaturePubKey.getPublic(), sequenceNumber, signature, receiversPublicKey);
}
public void addHashSetChangedListener(HashSetChangedListener hashSetChangedListener) {
hashSetChangedListeners.add(hashSetChangedListener);
}
public void addMessageListener(MessageListener messageListener) {
routing.addMessageListener(messageListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private boolean isSequenceNrValid(ProtectedData data, BigInteger hashOfData) {
int newSequenceNumber = data.sequenceNumber;
Integer storedSequenceNumber = sequenceNumberMap.get(hashOfData);
if (sequenceNumberMap.containsKey(hashOfData) && newSequenceNumber <= storedSequenceNumber) {
log.warn("Sequence number is invalid. That might happen in rare cases. newSequenceNumber="
+ newSequenceNumber + " / storedSequenceNumber=" + storedSequenceNumber);
return false;
} else {
return true;
}
}
private boolean checkSignature(ProtectedData data) {
byte[] hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, data.sequenceNumber));
try {
boolean result = CryptoUtil.verifyStorageData(data.ownerStoragePubKey, hashOfDataAndSeqNr, data.signature);
if (!result)
log.error("Signature verification failed at checkSignature. " +
"That should not happen. Consider it might be an attempt of fraud.");
return result;
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
log.error("Signature verification failed at checkSignature");
return false;
}
}
private boolean checkPublicKeys(ProtectedData data, boolean isAddOperation) {
boolean result = false;
if (data.expirablePayload instanceof ExpirableMailboxPayload) {
ExpirableMailboxPayload expirableMailboxPayload = (ExpirableMailboxPayload) data.expirablePayload;
if (isAddOperation)
result = expirableMailboxPayload.senderStoragePublicKey.equals(data.ownerStoragePubKey);
else
result = expirableMailboxPayload.receiverStoragePublicKey.equals(data.ownerStoragePubKey);
} else if (data.expirablePayload instanceof PubKeyProtectedExpirablePayload) {
result = ((PubKeyProtectedExpirablePayload) data.expirablePayload).getPubKey().equals(data.ownerStoragePubKey);
}
if (!result)
log.error("PublicKey of payload data and ProtectedData are not matching. Consider it might be an attempt of fraud");
return result;
}
private boolean checkIfStoredDataMatchesNewData(ProtectedData data, BigInteger hashOfData) {
ProtectedData storedData = map.get(hashOfData);
boolean result = getHashAsBigInteger(storedData.expirablePayload).equals(hashOfData)
&& storedData.ownerStoragePubKey.equals(data.ownerStoragePubKey);
if (!result)
log.error("New data entry does not match our stored data. Consider it might be an attempt of fraud");
return result;
}
private boolean checkIfStoredMailboxDataMatchesNewMailboxData(ProtectedMailboxData data, BigInteger hashOfData) {
ProtectedData storedData = map.get(hashOfData);
if (storedData instanceof ProtectedMailboxData) {
ProtectedMailboxData storedMailboxData = (ProtectedMailboxData) storedData;
// publicKey is not the same (stored: sender, new: receiver)
boolean result = storedMailboxData.receiversPubKey.equals(data.receiversPubKey)
&& getHashAsBigInteger(storedMailboxData.expirablePayload).equals(hashOfData);
if (!result)
log.error("New data entry does not match our stored data. Consider it might be an attempt of fraud");
return result;
} else {
log.error("We expected a MailboxData but got other type. That must never happen. storedData=" + storedData);
return false;
}
}
private boolean doAddProtectedExpirableData(ProtectedData data, BigInteger hashOfData, Address sender) {
map.put(hashOfData, data);
log.trace("Data added to our map and it will be broadcasted to our neighbors.");
UserThread.execute(() -> hashSetChangedListeners.stream().forEach(e -> e.onAdded(data)));
broadcast(new AddDataMessage(data), sender);
StringBuilder sb = new StringBuilder("\n\nSet after addProtectedExpirableData:\n");
map.values().stream().forEach(e -> sb.append(e.toString() + "\n\n"));
sb.append("\n\n");
log.trace(sb.toString());
return true;
}
private boolean doRemoveProtectedExpirableData(ProtectedData data, BigInteger hashOfData, Address sender) {
map.remove(hashOfData);
log.trace("Data removed from our map. We broadcast the message to our neighbors.");
UserThread.execute(() -> hashSetChangedListeners.stream().forEach(e -> e.onRemoved(data)));
if (data instanceof ProtectedMailboxData)
broadcast(new RemoveMailboxDataMessage((ProtectedMailboxData) data), sender);
else
broadcast(new RemoveDataMessage(data), sender);
StringBuilder sb = new StringBuilder("\n\nSet after removeProtectedExpirableData:\n");
map.values().stream().forEach(e -> sb.append(e.toString() + "\n\n"));
sb.append("\n\n");
log.trace(sb.toString());
return true;
}
private void broadcast(BroadcastMessage message, Address sender) {
if (authenticated) {
routing.broadcast(message, sender);
log.trace("Broadcast message " + message);
} else {
log.trace("Broadcast not allowed because we are not authenticated yet. That is normal after received AllDataMessage at startup.");
}
}
private BigInteger getHashAsBigInteger(ExpirablePayload payload) {
return new BigInteger(CryptoUtil.getHash(payload));
}
}

View file

@ -0,0 +1,32 @@
package io.bitsquare.p2p.storage.data;
import java.io.Serializable;
public class DataAndSeqNr implements Serializable {
public final Serializable data;
public final int sequenceNumber;
public DataAndSeqNr(Serializable data, int sequenceNumber) {
this.data = data;
this.sequenceNumber = sequenceNumber;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DataAndSeqNr)) return false;
DataAndSeqNr that = (DataAndSeqNr) o;
if (sequenceNumber != that.sequenceNumber) return false;
return !(data != null ? !data.equals(that.data) : that.data != null);
}
@Override
public int hashCode() {
int result = data != null ? data.hashCode() : 0;
result = 31 * result + sequenceNumber;
return result;
}
}

View file

@ -0,0 +1,52 @@
package io.bitsquare.p2p.storage.data;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.messaging.SealedAndSignedMessage;
import java.security.PublicKey;
public final class ExpirableMailboxPayload implements ExpirablePayload {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public static final long TTL = 10 * 24 * 60 * 60 * 1000; // 10 days
public final SealedAndSignedMessage sealedAndSignedMessage;
public final PublicKey senderStoragePublicKey;
public final PublicKey receiverStoragePublicKey;
public ExpirableMailboxPayload(SealedAndSignedMessage sealedAndSignedMessage, PublicKey senderStoragePublicKey, PublicKey receiverStoragePublicKey) {
this.sealedAndSignedMessage = sealedAndSignedMessage;
this.senderStoragePublicKey = senderStoragePublicKey;
this.receiverStoragePublicKey = receiverStoragePublicKey;
}
@Override
public long getTTL() {
return TTL;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ExpirableMailboxPayload)) return false;
ExpirableMailboxPayload that = (ExpirableMailboxPayload) o;
return !(sealedAndSignedMessage != null ? !sealedAndSignedMessage.equals(that.sealedAndSignedMessage) : that.sealedAndSignedMessage != null);
}
@Override
public int hashCode() {
return sealedAndSignedMessage != null ? sealedAndSignedMessage.hashCode() : 0;
}
@Override
public String toString() {
return "MailboxEntry{" +
"hashCode=" + hashCode() +
", sealedAndSignedMessage=" + sealedAndSignedMessage +
'}';
}
}

View file

@ -0,0 +1,7 @@
package io.bitsquare.p2p.storage.data;
import java.io.Serializable;
public interface ExpirablePayload extends Serializable {
long getTTL();
}

View file

@ -0,0 +1,66 @@
package io.bitsquare.p2p.storage.data;
import com.google.common.annotations.VisibleForTesting;
import io.bitsquare.p2p.storage.ProtectedExpirableDataStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.Serializable;
import java.security.PublicKey;
import java.util.Date;
public class ProtectedData implements Serializable {
private static final Logger log = LoggerFactory.getLogger(ProtectedExpirableDataStorage.class);
public final ExpirablePayload expirablePayload;
transient public long ttl;
public final PublicKey ownerStoragePubKey;
public final int sequenceNumber;
public final byte[] signature;
@VisibleForTesting
public Date date;
public ProtectedData(ExpirablePayload expirablePayload, long ttl, PublicKey ownerStoragePubKey, int sequenceNumber, byte[] signature) {
this.expirablePayload = expirablePayload;
this.ttl = ttl;
this.ownerStoragePubKey = ownerStoragePubKey;
this.sequenceNumber = sequenceNumber;
this.signature = signature;
this.date = new Date();
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
try {
in.defaultReadObject();
ttl = expirablePayload.getTTL();
// in case the reported creation date is in the future
// we reset the date to the current time
if (date.getTime() > new Date().getTime()) {
log.warn("Date of object is in future. " +
"That might be ok as clocks are not synced but could be also a spam attack. " +
"date=" + date + " / now=" + new Date());
date = new Date();
}
date = new Date();
} catch (Throwable t) {
t.printStackTrace();
}
}
public boolean isExpired() {
return (new Date().getTime() - date.getTime()) > ttl;
}
@Override
public String toString() {
return "ProtectedData{" +
"data=\n" + expirablePayload +
", \nttl=" + ttl +
", sequenceNumber=" + sequenceNumber +
", date=" + date +
"\n}";
}
}

View file

@ -0,0 +1,55 @@
package io.bitsquare.p2p.storage.data;
import io.bitsquare.p2p.storage.ProtectedExpirableDataStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.security.PublicKey;
import java.util.Date;
public class ProtectedMailboxData extends ProtectedData {
private static final Logger log = LoggerFactory.getLogger(ProtectedExpirableDataStorage.class);
public final PublicKey receiversPubKey;
public ProtectedMailboxData(ExpirableMailboxPayload data, long ttl, PublicKey ownerStoragePubKey, int sequenceNumber, byte[] signature, PublicKey receiversPubKey) {
super(data, ttl, ownerStoragePubKey, sequenceNumber, signature);
this.receiversPubKey = receiversPubKey;
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
try {
in.defaultReadObject();
ttl = expirablePayload.getTTL();
// in case the reported creation date is in the future
// we reset the date to the current time
if (date.getTime() > new Date().getTime()) {
log.warn("Date of object is in future. " +
"That might be ok as clocks are not synced but could be also a spam attack. " +
"date=" + date + " / now=" + new Date());
date = new Date();
}
date = new Date();
} catch (Throwable t) {
t.printStackTrace();
}
}
public boolean isExpired() {
return (new Date().getTime() - date.getTime()) > ttl;
}
@Override
public String toString() {
return "MailboxData{" +
"data=\n" + expirablePayload +
", \nttl=" + ttl +
", sequenceNumber=" + sequenceNumber +
", date=" + date +
"\n}";
}
}

View file

@ -0,0 +1,7 @@
package io.bitsquare.p2p.storage.data;
import java.security.PublicKey;
public interface PubKeyProtectedExpirablePayload extends ExpirablePayload {
PublicKey getPubKey();
}

View file

@ -0,0 +1,37 @@
package io.bitsquare.p2p.storage.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.storage.data.ProtectedData;
public final class AddDataMessage implements DataMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final ProtectedData data;
public AddDataMessage(ProtectedData data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AddDataMessage)) return false;
AddDataMessage that = (AddDataMessage) o;
return !(data != null ? !data.equals(that.data) : that.data != null);
}
@Override
public int hashCode() {
return data != null ? data.hashCode() : 0;
}
@Override
public String toString() {
return "AddDataMessage{" +
"data=" + data +
'}';
}
}

View file

@ -0,0 +1,6 @@
package io.bitsquare.p2p.storage.messages;
import io.bitsquare.p2p.Message;
public interface BroadcastMessage extends Message {
}

View file

@ -0,0 +1,5 @@
package io.bitsquare.p2p.storage.messages;
// market interface for messages which manipulate data (add, remove)
public interface DataMessage extends BroadcastMessage {
}

View file

@ -0,0 +1,41 @@
package io.bitsquare.p2p.storage.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.storage.data.ProtectedData;
import java.util.HashSet;
public final class DataSetMessage implements Message {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final HashSet<ProtectedData> set;
public DataSetMessage(HashSet<ProtectedData> set) {
this.set = set;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DataSetMessage)) return false;
DataSetMessage that = (DataSetMessage) o;
return !(set != null ? !set.equals(that.set) : that.set != null);
}
@Override
public int hashCode() {
return set != null ? set.hashCode() : 0;
}
@Override
public String toString() {
return "AllDataMessage{" +
"set=" + set +
'}';
}
}

View file

@ -0,0 +1,15 @@
package io.bitsquare.p2p.storage.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Message;
public final class GetDataSetMessage implements Message {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final long nonce;
public GetDataSetMessage(long nonce) {
this.nonce = nonce;
}
}

View file

@ -0,0 +1,38 @@
package io.bitsquare.p2p.storage.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.storage.data.ProtectedData;
public final class RemoveDataMessage implements DataMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final ProtectedData data;
public RemoveDataMessage(ProtectedData data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof RemoveDataMessage)) return false;
RemoveDataMessage that = (RemoveDataMessage) o;
return !(data != null ? !data.equals(that.data) : that.data != null);
}
@Override
public int hashCode() {
return data != null ? data.hashCode() : 0;
}
@Override
public String toString() {
return "RemoveDataMessage{" +
"data=" + data +
'}';
}
}

View file

@ -0,0 +1,38 @@
package io.bitsquare.p2p.storage.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.storage.data.ProtectedMailboxData;
public final class RemoveMailboxDataMessage implements DataMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final ProtectedMailboxData data;
public RemoveMailboxDataMessage(ProtectedMailboxData data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof RemoveMailboxDataMessage)) return false;
RemoveMailboxDataMessage that = (RemoveMailboxDataMessage) o;
return !(data != null ? !data.equals(that.data) : that.data != null);
}
@Override
public int hashCode() {
return data != null ? data.hashCode() : 0;
}
@Override
public String toString() {
return "RemoveMailboxDataMessage{" +
"data=" + data +
'}';
}
}

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %xEx%n</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="CONSOLE_APPENDER"/>
</root>
</configuration>

View file

@ -0,0 +1,362 @@
package io.bitsquare.p2p;
import io.bitsquare.common.crypto.CryptoException;
import io.bitsquare.common.crypto.CryptoUtil;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.KeyStorage;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.messaging.DecryptedMessageWithPubKey;
import io.bitsquare.p2p.messaging.MailboxMessage;
import io.bitsquare.p2p.messaging.SealedAndSignedMessage;
import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
import io.bitsquare.p2p.mocks.MockMailboxMessage;
import io.bitsquare.p2p.network.LocalhostNetworkNode;
import io.bitsquare.p2p.routing.Routing;
import io.bitsquare.p2p.seed.SeedNode;
import io.bitsquare.p2p.storage.data.DataAndSeqNr;
import io.bitsquare.p2p.storage.data.ProtectedData;
import io.bitsquare.p2p.storage.mocks.MockData;
import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
// TorNode created. Took 6 sec.
// Hidden service created. Took 40-50 sec.
// Connection establishment takes about 4 sec.
// need to define seed node addresses first before using tor version
@Ignore
public class P2PServiceTest {
private static final Logger log = LoggerFactory.getLogger(P2PServiceTest.class);
boolean useLocalhost = true;
private ArrayList<Address> seedNodes;
private int sleepTime;
private KeyRing keyRing1, keyRing2, keyRing3;
private EncryptionService encryptionService1, encryptionService2, encryptionService3;
private P2PService p2PService1, p2PService2, p2PService3;
private SeedNode seedNode1, seedNode2, seedNode3;
@Before
public void setup() throws InterruptedException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, CryptoException {
LocalhostNetworkNode.setSimulateTorDelayTorNode(10);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(100);
Routing.setMaxConnections(8);
keyRing1 = new KeyRing(new KeyStorage(new File("temp_keyStorage1")));
keyRing2 = new KeyRing(new KeyStorage(new File("temp_keyStorage2")));
keyRing3 = new KeyRing(new KeyStorage(new File("temp_keyStorage3")));
encryptionService1 = new EncryptionService(keyRing1);
encryptionService2 = new EncryptionService(keyRing2);
encryptionService3 = new EncryptionService(keyRing3);
seedNodes = new ArrayList<>();
if (useLocalhost) {
seedNodes.add(new Address("localhost:8001"));
seedNodes.add(new Address("localhost:8002"));
seedNodes.add(new Address("localhost:8003"));
sleepTime = 100;
} else {
seedNodes.add(new Address("3omjuxn7z73pxoee.onion:8001"));
seedNodes.add(new Address("j24fxqyghjetgpdx.onion:8002"));
seedNodes.add(new Address("45367tl6unwec6kw.onion:8003"));
sleepTime = 1000;
}
seedNode1 = TestUtils.getAndStartSeedNode(8001, encryptionService1, keyRing1, useLocalhost, seedNodes);
p2PService1 = seedNode1.getP2PService();
p2PService2 = TestUtils.getAndAuthenticateP2PService(8002, encryptionService2, keyRing2, useLocalhost, seedNodes);
}
@After
public void tearDown() throws InterruptedException {
Thread.sleep(sleepTime);
if (seedNode1 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
seedNode1.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
if (seedNode2 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
seedNode2.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
if (seedNode3 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
seedNode3.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
if (p2PService1 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
p2PService1.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
if (p2PService2 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
p2PService2.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
if (p2PService3 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
p2PService3.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
}
@Test
public void testAdversaryAttacks() throws InterruptedException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException {
p2PService3 = TestUtils.getAndAuthenticateP2PService(8003, encryptionService3, keyRing3, useLocalhost, seedNodes);
MockData origData = new MockData("mockData1", keyRing1.getStorageSignatureKeyPair().getPublic());
p2PService1.addData(origData);
Assert.assertEquals(1, p2PService1.getDataMap().size());
Assert.assertEquals(1, p2PService2.getDataMap().size());
Assert.assertEquals(1, p2PService3.getDataMap().size());
// p2PService3 is adversary
KeyPair msgSignatureKeyPairAdversary = keyRing3.getStorageSignatureKeyPair();
// try to remove data -> fails
Assert.assertFalse(p2PService3.removeData(origData));
Thread.sleep(sleepTime);
Assert.assertEquals(1, p2PService1.getDataMap().size());
Assert.assertEquals(1, p2PService2.getDataMap().size());
Assert.assertEquals(1, p2PService3.getDataMap().size());
// try to add manipulated data -> fails
Assert.assertFalse(p2PService3.removeData(new MockData("mockData2", origData.publicKey)));
Thread.sleep(sleepTime);
Assert.assertEquals(1, p2PService1.getDataMap().size());
Assert.assertEquals(1, p2PService2.getDataMap().size());
Assert.assertEquals(1, p2PService3.getDataMap().size());
// try to manipulate seq nr. -> fails
ProtectedData origProtectedData = p2PService3.getDataMap().values().stream().findFirst().get();
ProtectedData protectedDataManipulated = new ProtectedData(origProtectedData.expirablePayload, origProtectedData.ttl, origProtectedData.ownerStoragePubKey, origProtectedData.sequenceNumber + 1, origProtectedData.signature);
Assert.assertFalse(p2PService3.removeData(protectedDataManipulated.expirablePayload));
Thread.sleep(sleepTime);
Assert.assertEquals(1, p2PService1.getDataMap().size());
Assert.assertEquals(1, p2PService2.getDataMap().size());
Assert.assertEquals(1, p2PService3.getDataMap().size());
// try to manipulate seq nr. + pubKey -> fails
protectedDataManipulated = new ProtectedData(origProtectedData.expirablePayload, origProtectedData.ttl, msgSignatureKeyPairAdversary.getPublic(), origProtectedData.sequenceNumber + 1, origProtectedData.signature);
Assert.assertFalse(p2PService3.removeData(protectedDataManipulated.expirablePayload));
Thread.sleep(sleepTime);
Assert.assertEquals(1, p2PService1.getDataMap().size());
Assert.assertEquals(1, p2PService2.getDataMap().size());
Assert.assertEquals(1, p2PService3.getDataMap().size());
// try to manipulate seq nr. + pubKey + sig -> fails
int sequenceNumberManipulated = origProtectedData.sequenceNumber + 1;
byte[] hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(origProtectedData.expirablePayload, sequenceNumberManipulated));
byte[] signature = CryptoUtil.signStorageData(msgSignatureKeyPairAdversary.getPrivate(), hashOfDataAndSeqNr);
protectedDataManipulated = new ProtectedData(origProtectedData.expirablePayload, origProtectedData.ttl, msgSignatureKeyPairAdversary.getPublic(), sequenceNumberManipulated, signature);
Assert.assertFalse(p2PService3.removeData(protectedDataManipulated.expirablePayload));
Thread.sleep(sleepTime);
Assert.assertEquals(1, p2PService1.getDataMap().size());
Assert.assertEquals(1, p2PService2.getDataMap().size());
Assert.assertEquals(1, p2PService3.getDataMap().size());
// data owner removes -> ok
Assert.assertTrue(p2PService1.removeData(origData));
Thread.sleep(sleepTime);
Assert.assertEquals(0, p2PService1.getDataMap().size());
Assert.assertEquals(0, p2PService2.getDataMap().size());
Assert.assertEquals(0, p2PService3.getDataMap().size());
// adversary manage to change data before it is broadcasted to others
// data owner is connected only to adversary -> he change data at onMessage and broadcast manipulated data
// first he tries to use the orig. pubKey in the data -> fails as pub keys not matching
MockData manipulatedData = new MockData("mockData1_manipulated", origData.publicKey);
sequenceNumberManipulated = 0;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(manipulatedData, sequenceNumberManipulated));
signature = CryptoUtil.signStorageData(msgSignatureKeyPairAdversary.getPrivate(), hashOfDataAndSeqNr);
protectedDataManipulated = new ProtectedData(origProtectedData.expirablePayload, origProtectedData.ttl, msgSignatureKeyPairAdversary.getPublic(), sequenceNumberManipulated, signature);
Assert.assertFalse(p2PService3.addData(protectedDataManipulated.expirablePayload));
Thread.sleep(sleepTime);
Assert.assertEquals(0, p2PService1.getDataMap().size());
Assert.assertEquals(0, p2PService2.getDataMap().size());
Assert.assertEquals(0, p2PService3.getDataMap().size());
// then he tries to use his pubKey but orig data payload -> fails as pub keys nto matching
manipulatedData = new MockData("mockData1_manipulated", msgSignatureKeyPairAdversary.getPublic());
sequenceNumberManipulated = 0;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(manipulatedData, sequenceNumberManipulated));
signature = CryptoUtil.signStorageData(msgSignatureKeyPairAdversary.getPrivate(), hashOfDataAndSeqNr);
protectedDataManipulated = new ProtectedData(origProtectedData.expirablePayload, origProtectedData.ttl, msgSignatureKeyPairAdversary.getPublic(), sequenceNumberManipulated, signature);
Assert.assertFalse(p2PService3.addData(protectedDataManipulated.expirablePayload));
Thread.sleep(sleepTime);
Assert.assertEquals(0, p2PService1.getDataMap().size());
Assert.assertEquals(0, p2PService2.getDataMap().size());
Assert.assertEquals(0, p2PService3.getDataMap().size());
// then he tries to use his pubKey -> now he succeeds, but its same as adding a completely new msg and
// payload data has adversary's pubKey so he could hijack the owners data
manipulatedData = new MockData("mockData1_manipulated", msgSignatureKeyPairAdversary.getPublic());
sequenceNumberManipulated = 0;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(manipulatedData, sequenceNumberManipulated));
signature = CryptoUtil.signStorageData(msgSignatureKeyPairAdversary.getPrivate(), hashOfDataAndSeqNr);
protectedDataManipulated = new ProtectedData(manipulatedData, origProtectedData.ttl, msgSignatureKeyPairAdversary.getPublic(), sequenceNumberManipulated, signature);
Assert.assertTrue(p2PService3.addData(protectedDataManipulated.expirablePayload));
Thread.sleep(sleepTime);
Assert.assertEquals(1, p2PService1.getDataMap().size());
Assert.assertEquals(1, p2PService2.getDataMap().size());
Assert.assertEquals(1, p2PService3.getDataMap().size());
// let clean up. he can remove his own data
Assert.assertTrue(p2PService3.removeData(manipulatedData));
Thread.sleep(sleepTime);
Assert.assertEquals(0, p2PService1.getDataMap().size());
Assert.assertEquals(0, p2PService2.getDataMap().size());
Assert.assertEquals(0, p2PService3.getDataMap().size());
// finally he tries both previous attempts with same data - > same as before
manipulatedData = new MockData("mockData1", origData.publicKey);
sequenceNumberManipulated = 0;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(manipulatedData, sequenceNumberManipulated));
signature = CryptoUtil.signStorageData(msgSignatureKeyPairAdversary.getPrivate(), hashOfDataAndSeqNr);
protectedDataManipulated = new ProtectedData(origProtectedData.expirablePayload, origProtectedData.ttl, msgSignatureKeyPairAdversary.getPublic(), sequenceNumberManipulated, signature);
Assert.assertFalse(p2PService3.addData(protectedDataManipulated.expirablePayload));
Thread.sleep(sleepTime);
Assert.assertEquals(0, p2PService1.getDataMap().size());
Assert.assertEquals(0, p2PService2.getDataMap().size());
Assert.assertEquals(0, p2PService3.getDataMap().size());
manipulatedData = new MockData("mockData1", msgSignatureKeyPairAdversary.getPublic());
sequenceNumberManipulated = 0;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(manipulatedData, sequenceNumberManipulated));
signature = CryptoUtil.signStorageData(msgSignatureKeyPairAdversary.getPrivate(), hashOfDataAndSeqNr);
protectedDataManipulated = new ProtectedData(manipulatedData, origProtectedData.ttl, msgSignatureKeyPairAdversary.getPublic(), sequenceNumberManipulated, signature);
Assert.assertTrue(p2PService3.addData(protectedDataManipulated.expirablePayload));
Thread.sleep(sleepTime);
Assert.assertEquals(1, p2PService1.getDataMap().size());
Assert.assertEquals(1, p2PService2.getDataMap().size());
Assert.assertEquals(1, p2PService3.getDataMap().size());
// lets reset map
Assert.assertTrue(p2PService3.removeData(protectedDataManipulated.expirablePayload));
Thread.sleep(sleepTime);
Assert.assertEquals(0, p2PService1.getDataMap().size());
Assert.assertEquals(0, p2PService2.getDataMap().size());
Assert.assertEquals(0, p2PService3.getDataMap().size());
// owner can add any time his data
Assert.assertTrue(p2PService1.addData(origData));
Thread.sleep(sleepTime);
Assert.assertEquals(1, p2PService1.getDataMap().size());
Assert.assertEquals(1, p2PService2.getDataMap().size());
Assert.assertEquals(1, p2PService3.getDataMap().size());
}
//@Test
public void testSendMailboxMessageToOnlinePeer() throws InterruptedException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, CryptoException {
LocalhostNetworkNode.setSimulateTorDelayTorNode(0);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(0);
// send to online peer
CountDownLatch latch2 = new CountDownLatch(2);
MockMailboxMessage mockMessage = new MockMailboxMessage("MockMailboxMessage", p2PService2.getAddress());
p2PService2.addMessageListener((message, connection) -> {
log.trace("message " + message);
if (message instanceof SealedAndSignedMessage) {
try {
DecryptedMessageWithPubKey decryptedMessageWithPubKey = encryptionService2.decryptAndVerifyMessage((SealedAndSignedMessage) message);
Assert.assertEquals(mockMessage, decryptedMessageWithPubKey.message);
Assert.assertEquals(p2PService2.getAddress(), ((MailboxMessage) decryptedMessageWithPubKey.message).getSenderAddress());
latch2.countDown();
} catch (CryptoException e) {
e.printStackTrace();
}
}
});
p2PService1.sendEncryptedMailboxMessage(
p2PService2.getAddress(),
keyRing2.getPubKeyRing(),
mockMessage,
new SendMailboxMessageListener() {
@Override
public void onArrived() {
log.trace("Message arrived at peer.");
latch2.countDown();
}
@Override
public void onStoredInMailbox() {
log.trace("Message stored in mailbox.");
}
@Override
public void onFault() {
log.error("onFault");
}
}
);
latch2.await();
}
//@Test
public void testSendMailboxMessageToOfflinePeer() throws InterruptedException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, CryptoException {
LocalhostNetworkNode.setSimulateTorDelayTorNode(0);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(0);
// send msg to offline peer
MockMailboxMessage mockMessage = new MockMailboxMessage(
"MockMailboxMessage",
p2PService2.getAddress()
);
CountDownLatch latch2 = new CountDownLatch(1);
p2PService2.sendEncryptedMailboxMessage(
new Address("localhost:8003"),
keyRing3.getPubKeyRing(),
mockMessage,
new SendMailboxMessageListener() {
@Override
public void onArrived() {
log.trace("Message arrived at peer.");
}
@Override
public void onStoredInMailbox() {
log.trace("Message stored in mailbox.");
latch2.countDown();
}
@Override
public void onFault() {
log.error("onFault");
}
}
);
latch2.await();
Thread.sleep(2000);
// start node 3
p2PService3 = TestUtils.getAndAuthenticateP2PService(8003, encryptionService3, keyRing3, useLocalhost, seedNodes);
Thread.sleep(sleepTime);
CountDownLatch latch3 = new CountDownLatch(1);
p2PService3.addDecryptedMailboxListener((decryptedMessageWithPubKey, senderAddress) -> {
log.debug("decryptedMessageWithPubKey " + decryptedMessageWithPubKey.toString());
Assert.assertEquals(mockMessage, decryptedMessageWithPubKey.message);
Assert.assertEquals(p2PService2.getAddress(), ((MailboxMessage) decryptedMessageWithPubKey.message).getSenderAddress());
latch3.countDown();
});
latch3.await();
}
}

View file

@ -0,0 +1,150 @@
package io.bitsquare.p2p;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.seed.SeedNode;
import io.bitsquare.p2p.seed.SeedNodesRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.security.*;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
public class TestUtils {
private static final Logger log = LoggerFactory.getLogger(TestUtils.class);
public static int sleepTime;
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
long ts = System.currentTimeMillis();
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
log.trace("Generate storageSignatureKeyPair needed {} ms", System.currentTimeMillis() - ts);
return keyPair;
}
public static byte[] sign(PrivateKey privateKey, Serializable data)
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
Signature sig = Signature.getInstance("SHA1withDSA");
sig.initSign(privateKey);
sig.update(objectToByteArray(data));
return sig.sign();
}
public static byte[] objectToByteArray(Object object) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null;
byte[] result = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(object);
result = bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException ex) {
// ignore close exception
}
try {
bos.close();
} catch (IOException ex) {
// ignore close exception
}
}
return result;
}
public static SeedNode getAndStartSeedNode(int port, EncryptionService encryptionService, KeyRing keyRing, boolean useLocalhost, ArrayList<Address> seedNodes) throws InterruptedException {
SeedNode seedNode;
if (useLocalhost) {
seedNodes.add(new Address("localhost:8001"));
seedNodes.add(new Address("localhost:8002"));
seedNodes.add(new Address("localhost:8003"));
sleepTime = 100;
seedNode = new SeedNode();
} else {
seedNodes.add(new Address("3omjuxn7z73pxoee.onion:8001"));
seedNodes.add(new Address("j24fxqyghjetgpdx.onion:8002"));
seedNodes.add(new Address("45367tl6unwec6kw.onion:8003"));
sleepTime = 10000;
seedNode = new SeedNode();
}
CountDownLatch latch = new CountDownLatch(1);
seedNode.createAndStartP2PService(encryptionService, keyRing, port, useLocalhost, seedNodes, new P2PServiceListener() {
@Override
public void onAllDataReceived() {
}
@Override
public void onAuthenticated() {
}
@Override
public void onTorNodeReady() {
}
@Override
public void onHiddenServiceReady() {
latch.countDown();
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
latch.await();
Thread.sleep(sleepTime);
return seedNode;
}
public static P2PService getAndAuthenticateP2PService(int port, EncryptionService encryptionService, KeyRing keyRing, boolean useLocalhost, ArrayList<Address> seedNodes) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
SeedNodesRepository seedNodesRepository = new SeedNodesRepository();
if (seedNodes != null && !seedNodes.isEmpty()) {
if (useLocalhost)
seedNodesRepository.setLocalhostSeedNodeAddresses(seedNodes);
else
seedNodesRepository.setTorSeedNodeAddresses(seedNodes);
}
P2PService p2PService = new P2PService(seedNodesRepository, port, new File("seed_node_" + port), useLocalhost, encryptionService, keyRing);
p2PService.start(new P2PServiceListener() {
@Override
public void onAllDataReceived() {
}
@Override
public void onTorNodeReady() {
}
@Override
public void onAuthenticated() {
latch.countDown();
}
@Override
public void onHiddenServiceReady() {
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
latch.await();
Thread.sleep(2000);
return p2PService;
}
}

View file

@ -0,0 +1,49 @@
package io.bitsquare.p2p.mocks;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.messaging.MailboxMessage;
import io.bitsquare.p2p.storage.data.ExpirablePayload;
public class MockMailboxMessage implements MailboxMessage, ExpirablePayload {
public String msg;
public Address senderAddress;
public long ttl;
public MockMailboxMessage(String msg, Address senderAddress) {
this.msg = msg;
this.senderAddress = senderAddress;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MockMailboxMessage)) return false;
MockMailboxMessage that = (MockMailboxMessage) o;
return !(msg != null ? !msg.equals(that.msg) : that.msg != null);
}
@Override
public int hashCode() {
return msg != null ? msg.hashCode() : 0;
}
@Override
public String toString() {
return "MockData{" +
"msg='" + msg + '\'' +
'}';
}
@Override
public long getTTL() {
return ttl;
}
@Override
public Address getSenderAddress() {
return senderAddress;
}
}

View file

@ -0,0 +1,41 @@
package io.bitsquare.p2p.mocks;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.storage.data.ExpirablePayload;
public class MockMessage implements Message, ExpirablePayload {
public String msg;
public long ttl;
public MockMessage(String msg) {
this.msg = msg;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MockMessage)) return false;
MockMessage that = (MockMessage) o;
return !(msg != null ? !msg.equals(that.msg) : that.msg != null);
}
@Override
public int hashCode() {
return msg != null ? msg.hashCode() : 0;
}
@Override
public String toString() {
return "MockData{" +
"msg='" + msg + '\'' +
'}';
}
@Override
public long getTTL() {
return ttl;
}
}

View file

@ -0,0 +1,84 @@
package io.bitsquare.p2p.network;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.routing.messages.RequestAuthenticationMessage;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
// TorNode created. Took 6 sec.
// Hidden service created. Took 40-50 sec.
// Connection establishment takes about 4 sec.
@Ignore
public class LocalhostNetworkNodeTest {
private static final Logger log = LoggerFactory.getLogger(LocalhostNetworkNodeTest.class);
@Test
public void testMessage() throws InterruptedException, IOException {
CountDownLatch msgLatch = new CountDownLatch(2);
LocalhostNetworkNode node1 = new LocalhostNetworkNode(9001);
node1.addMessageListener((message, connection) -> {
log.debug("onMessage node1 " + message);
msgLatch.countDown();
});
CountDownLatch startupLatch = new CountDownLatch(2);
node1.start(new SetupListener() {
@Override
public void onTorNodeReady() {
log.debug("onTorNodeReady");
}
@Override
public void onHiddenServiceReady() {
log.debug("onHiddenServiceReady");
startupLatch.countDown();
}
@Override
public void onSetupFailed(Throwable throwable) {
log.debug("onSetupFailed");
}
});
LocalhostNetworkNode node2 = new LocalhostNetworkNode(9002);
node2.addMessageListener((message, connection) -> {
log.debug("onMessage node2 " + message);
msgLatch.countDown();
});
node2.start(new SetupListener() {
@Override
public void onTorNodeReady() {
log.debug("onTorNodeReady 2");
}
@Override
public void onHiddenServiceReady() {
log.debug("onHiddenServiceReady 2");
startupLatch.countDown();
}
@Override
public void onSetupFailed(Throwable throwable) {
log.debug("onSetupFailed 2");
}
});
startupLatch.await();
node2.sendMessage(new Address("localhost", 9001), new RequestAuthenticationMessage(new Address("localhost", 9002), 1));
node1.sendMessage(new Address("localhost", 9002), new RequestAuthenticationMessage(new Address("localhost", 9001), 1));
msgLatch.await();
CountDownLatch shutDownLatch = new CountDownLatch(2);
node1.shutDown(() -> {
shutDownLatch.countDown();
});
node2.shutDown(() -> {
shutDownLatch.countDown();
});
shutDownLatch.await();
}
}

View file

@ -0,0 +1,185 @@
package io.bitsquare.p2p.network;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.mocks.MockMessage;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
// TorNode created. Took 6 sec.
// Hidden service created. Took 40-50 sec.
// Connection establishment takes about 4 sec.
@Ignore
public class TorNetworkNodeTest {
private static final Logger log = LoggerFactory.getLogger(TorNetworkNodeTest.class);
private CountDownLatch latch;
@Test
public void testTorNodeBeforeSecondReady() throws InterruptedException, IOException {
latch = new CountDownLatch(1);
int port = 9001;
TorNetworkNode node1 = new TorNetworkNode(port, new File("torNode_" + port));
node1.start(new SetupListener() {
@Override
public void onTorNodeReady() {
log.debug("onReadyForSendingMessages");
}
@Override
public void onHiddenServiceReady() {
log.debug("onReadyForReceivingMessages");
latch.countDown();
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
latch.await();
latch = new CountDownLatch(1);
int port2 = 9002;
TorNetworkNode node2 = new TorNetworkNode(port2, new File("torNode_" + port2));
node2.start(new SetupListener() {
@Override
public void onTorNodeReady() {
log.debug("onReadyForSendingMessages");
latch.countDown();
}
@Override
public void onHiddenServiceReady() {
log.debug("onReadyForReceivingMessages");
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
latch.await();
latch = new CountDownLatch(2);
node1.addMessageListener(new MessageListener() {
@Override
public void onMessage(Message message, Connection connection) {
log.debug("onMessage node1 " + message);
latch.countDown();
}
});
SettableFuture<Connection> future = node2.sendMessage(node1.getAddress(), new MockMessage("msg1"));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.debug("onSuccess ");
latch.countDown();
}
@Override
public void onFailure(Throwable throwable) {
log.debug("onFailure ");
}
});
latch.await();
latch = new CountDownLatch(2);
node1.shutDown(() -> {
latch.countDown();
});
node2.shutDown(() -> {
latch.countDown();
});
latch.await();
}
//@Test
public void testTorNodeAfterBothReady() throws InterruptedException, IOException {
latch = new CountDownLatch(2);
int port = 9001;
TorNetworkNode node1 = new TorNetworkNode(port, new File("torNode_" + port));
node1.start(new SetupListener() {
@Override
public void onTorNodeReady() {
log.debug("onReadyForSendingMessages");
}
@Override
public void onHiddenServiceReady() {
log.debug("onReadyForReceivingMessages");
latch.countDown();
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
int port2 = 9002;
TorNetworkNode node2 = new TorNetworkNode(port2, new File("torNode_" + port));
node2.start(new SetupListener() {
@Override
public void onTorNodeReady() {
log.debug("onReadyForSendingMessages");
}
@Override
public void onHiddenServiceReady() {
log.debug("onReadyForReceivingMessages");
latch.countDown();
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
latch.await();
latch = new CountDownLatch(2);
node2.addMessageListener(new MessageListener() {
@Override
public void onMessage(Message message, Connection connection) {
log.debug("onMessage node2 " + message);
latch.countDown();
}
});
SettableFuture<Connection> future = node1.sendMessage(node2.getAddress(), new MockMessage("msg1"));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.debug("onSuccess ");
latch.countDown();
}
@Override
public void onFailure(Throwable throwable) {
log.debug("onFailure ");
}
});
latch.await();
latch = new CountDownLatch(2);
node1.shutDown(() -> {
latch.countDown();
});
node2.shutDown(() -> {
latch.countDown();
});
latch.await();
}
}

View file

@ -0,0 +1,411 @@
package io.bitsquare.p2p.routing;
import io.bitsquare.common.util.Profiler;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.LocalhostNetworkNode;
import io.bitsquare.p2p.seed.SeedNode;
import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
// TorNode created. Took 6 sec.
// Hidden service created. Took 40-50 sec.
// Connection establishment takes about 4 sec.
// need to define seed node addresses first before using tor version
@Ignore
public class RoutingTest {
private static final Logger log = LoggerFactory.getLogger(RoutingTest.class);
boolean useLocalhost = true;
private CountDownLatch latch;
private ArrayList<Address> seedNodes;
private int sleepTime;
private SeedNode seedNode1, seedNode2, seedNode3;
@Before
public void setup() throws InterruptedException {
LocalhostNetworkNode.setSimulateTorDelayTorNode(50);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(8);
Routing.setMaxConnections(100);
seedNodes = new ArrayList<>();
if (useLocalhost) {
//seedNodes.add(new Address("localhost:8001"));
// seedNodes.add(new Address("localhost:8002"));
seedNodes.add(new Address("localhost:8003"));
sleepTime = 100;
} else {
seedNodes.add(new Address("3omjuxn7z73pxoee.onion:8001"));
seedNodes.add(new Address("j24fxqyghjetgpdx.onion:8002"));
seedNodes.add(new Address("45367tl6unwec6kw.onion:8003"));
sleepTime = 1000;
}
}
@After
public void tearDown() throws InterruptedException {
Thread.sleep(sleepTime);
if (seedNode1 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
seedNode1.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
if (seedNode2 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
seedNode2.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
if (seedNode3 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
seedNode3.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
}
// @Test
public void testSingleSeedNode() throws InterruptedException {
LocalhostNetworkNode.setSimulateTorDelayTorNode(0);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(0);
seedNodes = new ArrayList<>();
seedNodes.add(new Address("localhost:8001"));
seedNode1 = new SeedNode();
latch = new CountDownLatch(2);
seedNode1.createAndStartP2PService(null, null, 8001, useLocalhost, seedNodes, new P2PServiceListener() {
@Override
public void onAllDataReceived() {
latch.countDown();
}
@Override
public void onTorNodeReady() {
}
@Override
public void onAuthenticated() {
}
@Override
public void onHiddenServiceReady() {
latch.countDown();
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
P2PService p2PService1 = seedNode1.getP2PService();
latch.await();
Thread.sleep(500);
Assert.assertEquals(0, p2PService1.getRouting().getAllNeighbors().size());
}
@Test
public void test2SeedNodes() throws InterruptedException {
LocalhostNetworkNode.setSimulateTorDelayTorNode(0);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(0);
seedNodes = new ArrayList<>();
seedNodes.add(new Address("localhost:8001"));
seedNodes.add(new Address("localhost:8002"));
latch = new CountDownLatch(6);
seedNode1 = new SeedNode();
seedNode1.createAndStartP2PService(null, null, 8001, useLocalhost, seedNodes, new P2PServiceListener() {
@Override
public void onAllDataReceived() {
latch.countDown();
}
@Override
public void onTorNodeReady() {
}
@Override
public void onAuthenticated() {
latch.countDown();
}
@Override
public void onHiddenServiceReady() {
latch.countDown();
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
P2PService p2PService1 = seedNode1.getP2PService();
Thread.sleep(500);
seedNode2 = new SeedNode();
seedNode2.createAndStartP2PService(null, null, 8002, useLocalhost, seedNodes, new P2PServiceListener() {
@Override
public void onAllDataReceived() {
latch.countDown();
}
@Override
public void onTorNodeReady() {
}
@Override
public void onAuthenticated() {
latch.countDown();
}
@Override
public void onHiddenServiceReady() {
latch.countDown();
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
P2PService p2PService2 = seedNode2.getP2PService();
latch.await();
Assert.assertEquals(1, p2PService1.getRouting().getAllNeighbors().size());
Assert.assertEquals(1, p2PService2.getRouting().getAllNeighbors().size());
}
// @Test
public void testAuthentication() throws InterruptedException {
log.debug("### start");
LocalhostNetworkNode.setSimulateTorDelayTorNode(0);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(0);
SeedNode seedNode1 = getAndStartSeedNode(8001);
log.debug("### seedNode1");
Thread.sleep(100);
log.debug("### seedNode1 100");
Thread.sleep(1000);
SeedNode seedNode2 = getAndStartSeedNode(8002);
// authentication:
// node2 -> node1 RequestAuthenticationMessage
// node1: close connection
// node1 -> node2 ChallengeMessage on new connection
// node2: authentication to node1 done if nonce ok
// node2 -> node1 GetNeighborsMessage
// node1: authentication to node2 done if nonce ok
// node1 -> node2 NeighborsMessage
// first authentication from seedNode2 to seedNode1, then from seedNode1 to seedNode2
CountDownLatch latch1 = new CountDownLatch(2);
AuthenticationListener routingListener1 = new AuthenticationListener() {
@Override
public void onConnectionAuthenticated(Connection connection) {
log.debug("onConnectionAuthenticated " + connection);
latch1.countDown();
}
};
seedNode1.getP2PService().getRouting().addRoutingListener(routingListener1);
AuthenticationListener routingListener2 = new AuthenticationListener() {
@Override
public void onConnectionAuthenticated(Connection connection) {
log.debug("onConnectionAuthenticated " + connection);
latch1.countDown();
}
};
seedNode2.getP2PService().getRouting().addRoutingListener(routingListener2);
latch1.await();
seedNode1.getP2PService().getRouting().removeRoutingListener(routingListener1);
seedNode2.getP2PService().getRouting().removeRoutingListener(routingListener2);
// wait until Neighbors msg finished
Thread.sleep(sleepTime);
// authentication:
// authentication from seedNode3 to seedNode1, then from seedNode1 to seedNode3
// authentication from seedNode3 to seedNode2, then from seedNode2 to seedNode3
SeedNode seedNode3 = getAndStartSeedNode(8003);
CountDownLatch latch2 = new CountDownLatch(3);
seedNode1.getP2PService().getRouting().addRoutingListener(new AuthenticationListener() {
@Override
public void onConnectionAuthenticated(Connection connection) {
log.debug("onConnectionAuthenticated " + connection);
latch2.countDown();
}
});
seedNode2.getP2PService().getRouting().addRoutingListener(new AuthenticationListener() {
@Override
public void onConnectionAuthenticated(Connection connection) {
log.debug("onConnectionAuthenticated " + connection);
latch2.countDown();
}
});
seedNode3.getP2PService().getRouting().addRoutingListener(new AuthenticationListener() {
@Override
public void onConnectionAuthenticated(Connection connection) {
log.debug("onConnectionAuthenticated " + connection);
latch2.countDown();
}
});
latch2.await();
// wait until Neighbors msg finished
Thread.sleep(sleepTime);
CountDownLatch shutDownLatch = new CountDownLatch(3);
seedNode1.shutDown(() -> shutDownLatch.countDown());
seedNode2.shutDown(() -> shutDownLatch.countDown());
seedNode3.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
//@Test
public void testAuthenticationWithDisconnect() throws InterruptedException {
LocalhostNetworkNode.setSimulateTorDelayTorNode(0);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(0);
SeedNode seedNode1 = getAndStartSeedNode(8001);
SeedNode seedNode2 = getAndStartSeedNode(8002);
// authentication:
// node2 -> node1 RequestAuthenticationMessage
// node1: close connection
// node1 -> node2 ChallengeMessage on new connection
// node2: authentication to node1 done if nonce ok
// node2 -> node1 GetNeighborsMessage
// node1: authentication to node2 done if nonce ok
// node1 -> node2 NeighborsMessage
// first authentication from seedNode2 to seedNode1, then from seedNode1 to seedNode2
CountDownLatch latch1 = new CountDownLatch(2);
AuthenticationListener routingListener1 = new AuthenticationListener() {
@Override
public void onConnectionAuthenticated(Connection connection) {
log.debug("onConnectionAuthenticated " + connection);
latch1.countDown();
}
};
seedNode1.getP2PService().getRouting().addRoutingListener(routingListener1);
AuthenticationListener routingListener2 = new AuthenticationListener() {
@Override
public void onConnectionAuthenticated(Connection connection) {
log.debug("onConnectionAuthenticated " + connection);
latch1.countDown();
}
};
seedNode2.getP2PService().getRouting().addRoutingListener(routingListener2);
latch1.await();
// shut down node 2
Thread.sleep(sleepTime);
seedNode1.getP2PService().getRouting().removeRoutingListener(routingListener1);
seedNode2.getP2PService().getRouting().removeRoutingListener(routingListener2);
CountDownLatch shutDownLatch1 = new CountDownLatch(1);
seedNode2.shutDown(() -> shutDownLatch1.countDown());
shutDownLatch1.await();
// restart node 2
seedNode2 = getAndStartSeedNode(8002);
CountDownLatch latch3 = new CountDownLatch(1);
routingListener2 = new AuthenticationListener() {
@Override
public void onConnectionAuthenticated(Connection connection) {
log.debug("onConnectionAuthenticated " + connection);
latch3.countDown();
}
};
seedNode2.getP2PService().getRouting().addRoutingListener(routingListener2);
latch3.await();
Thread.sleep(sleepTime);
CountDownLatch shutDownLatch = new CountDownLatch(2);
seedNode1.shutDown(() -> shutDownLatch.countDown());
seedNode2.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
//@Test
public void testAuthenticationWithManyNodes() throws InterruptedException {
int authentications = 0;
int length = 3;
SeedNode[] nodes = new SeedNode[length];
for (int i = 0; i < length; i++) {
SeedNode node = getAndStartSeedNode(8001 + i);
nodes[i] = node;
latch = new CountDownLatch(i * 2);
authentications += (i * 2);
node.getP2PService().getRouting().addRoutingListener(new AuthenticationListener() {
@Override
public void onConnectionAuthenticated(Connection connection) {
log.debug("onConnectionAuthenticated " + connection);
latch.countDown();
}
});
latch.await();
Thread.sleep(sleepTime);
}
log.debug("total authentications " + authentications);
Profiler.printSystemLoad(log);
// total authentications at 8 nodes = 56
// total authentications at com nodes = 90, System load (nr. threads/used memory (MB)): 170/20
// total authentications at 20 nodes = 380, System load (nr. threads/used memory (MB)): 525/46
for (int i = 0; i < length; i++) {
nodes[i].getP2PService().getRouting().printConnectedNeighborsMap();
nodes[i].getP2PService().getRouting().printReportedNeighborsMap();
}
CountDownLatch shutDownLatch = new CountDownLatch(length);
for (int i = 0; i < length; i++) {
nodes[i].shutDown(() -> shutDownLatch.countDown());
}
shutDownLatch.await();
}
private SeedNode getAndStartSeedNode(int port) throws InterruptedException {
SeedNode seedNode = new SeedNode();
latch = new CountDownLatch(1);
seedNode.createAndStartP2PService(null, null, port, useLocalhost, seedNodes, new P2PServiceListener() {
@Override
public void onAllDataReceived() {
latch.countDown();
}
@Override
public void onTorNodeReady() {
}
@Override
public void onAuthenticated() {
}
@Override
public void onHiddenServiceReady() {
}
@Override
public void onSetupFailed(Throwable throwable) {
}
});
latch.await();
Thread.sleep(sleepTime);
return seedNode;
}
}

View file

@ -0,0 +1,338 @@
package io.bitsquare.p2p.storage;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.CryptoException;
import io.bitsquare.common.crypto.CryptoUtil;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.KeyStorage;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.TestUtils;
import io.bitsquare.p2p.messaging.SealedAndSignedMessage;
import io.bitsquare.p2p.mocks.MockMessage;
import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.routing.Routing;
import io.bitsquare.p2p.storage.data.DataAndSeqNr;
import io.bitsquare.p2p.storage.data.ExpirableMailboxPayload;
import io.bitsquare.p2p.storage.data.ProtectedData;
import io.bitsquare.p2p.storage.data.ProtectedMailboxData;
import io.bitsquare.p2p.storage.mocks.MockData;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
public class ProtectedDataStorageTest {
private static final Logger log = LoggerFactory.getLogger(ProtectedDataStorageTest.class);
boolean useClearNet = true;
private ArrayList<Address> seedNodes = new ArrayList<>();
private NetworkNode networkNode1;
private Routing routing1;
private EncryptionService encryptionService1, encryptionService2;
private ProtectedExpirableDataStorage dataStorage1;
private KeyPair storageSignatureKeyPair1, storageSignatureKeyPair2;
private KeyRing keyRing1, keyRing2;
private MockData mockData;
private int sleepTime = 100;
@Before
public void setup() throws InterruptedException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException {
UserThread.executor = Executors.newSingleThreadExecutor();
ProtectedExpirableDataStorage.CHECK_TTL_INTERVAL = 10 * 60 * 1000;
keyRing1 = new KeyRing(new KeyStorage(new File("temp_keyStorage1")));
storageSignatureKeyPair1 = keyRing1.getStorageSignatureKeyPair();
encryptionService1 = new EncryptionService(keyRing1);
networkNode1 = TestUtils.getAndStartSeedNode(8001, encryptionService1, keyRing1, useClearNet, seedNodes).getP2PService().getNetworkNode();
routing1 = new Routing(networkNode1, seedNodes);
dataStorage1 = new ProtectedExpirableDataStorage(routing1, encryptionService1);
// for mailbox
keyRing2 = new KeyRing(new KeyStorage(new File("temp_keyStorage2")));
storageSignatureKeyPair2 = keyRing2.getStorageSignatureKeyPair();
encryptionService2 = new EncryptionService(keyRing2);
mockData = new MockData("mockData", keyRing1.getStorageSignatureKeyPair().getPublic());
Thread.sleep(sleepTime);
}
@After
public void tearDown() throws InterruptedException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException {
Thread.sleep(sleepTime);
if (dataStorage1 != null) dataStorage1.shutDown();
if (routing1 != null) routing1.shutDown();
if (networkNode1 != null) {
CountDownLatch shutDownLatch = new CountDownLatch(1);
networkNode1.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await();
}
}
@Test
public void testAddAndRemove() throws InterruptedException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException {
ProtectedData data = dataStorage1.getDataWithSignedSeqNr(mockData, storageSignatureKeyPair1);
Assert.assertTrue(dataStorage1.add(data, null));
Assert.assertEquals(1, dataStorage1.getMap().size());
int newSequenceNumber = data.sequenceNumber + 1;
byte[] hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
byte[] signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
ProtectedData dataToRemove = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertTrue(dataStorage1.remove(dataToRemove, null));
Assert.assertEquals(0, dataStorage1.getMap().size());
}
@Test
public void testExpirableData() throws InterruptedException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException {
ProtectedExpirableDataStorage.CHECK_TTL_INTERVAL = 10;
// CHECK_TTL_INTERVAL is used in constructor of ProtectedExpirableDataStorage so we recreate it here
dataStorage1 = new ProtectedExpirableDataStorage(routing1, encryptionService1);
mockData.ttl = 50;
ProtectedData data = dataStorage1.getDataWithSignedSeqNr(mockData, storageSignatureKeyPair1);
Assert.assertTrue(dataStorage1.add(data, null));
Thread.sleep(5);
Assert.assertEquals(1, dataStorage1.getMap().size());
// still there
Thread.sleep(20);
Assert.assertEquals(1, dataStorage1.getMap().size());
Thread.sleep(40);
// now should be removed
Assert.assertEquals(0, dataStorage1.getMap().size());
// add with date in future
data = dataStorage1.getDataWithSignedSeqNr(mockData, storageSignatureKeyPair1);
int newSequenceNumber = data.sequenceNumber + 1;
byte[] hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
byte[] signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
ProtectedData dataWithFutureDate = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
dataWithFutureDate.date = new Date(new Date().getTime() + 60 * 60 * sleepTime);
// force serialisation (date check is done in readObject)
ProtectedData newData = Utilities.byteArrayToObject(Utilities.objectToByteArray(dataWithFutureDate));
Assert.assertTrue(dataStorage1.add(newData, null));
Thread.sleep(5);
Assert.assertEquals(1, dataStorage1.getMap().size());
Thread.sleep(50);
Assert.assertEquals(0, dataStorage1.getMap().size());
}
@Test
public void testMultiAddRemoveProtectedData() throws InterruptedException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException {
MockData mockData = new MockData("msg1", keyRing1.getStorageSignatureKeyPair().getPublic());
ProtectedData data = dataStorage1.getDataWithSignedSeqNr(mockData, storageSignatureKeyPair1);
Assert.assertTrue(dataStorage1.add(data, null));
// remove with not updated seq nr -> failure
int newSequenceNumber = 0;
byte[] hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
byte[] signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
ProtectedData dataToRemove = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertFalse(dataStorage1.remove(dataToRemove, null));
// remove with too high updated seq nr -> ok
newSequenceNumber = 2;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertTrue(dataStorage1.remove(dataToRemove, null));
// add with updated seq nr below previous -> failure
newSequenceNumber = 1;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
ProtectedData dataToAdd = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertFalse(dataStorage1.add(dataToAdd, null));
// add with updated seq nr over previous -> ok
newSequenceNumber = 3;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
dataToAdd = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertTrue(dataStorage1.add(dataToAdd, null));
// add with same seq nr -> failure
newSequenceNumber = 3;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
dataToAdd = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertFalse(dataStorage1.add(dataToAdd, null));
// add with same data but higher seq nr. -> ok, ignore
newSequenceNumber = 4;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
dataToAdd = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertTrue(dataStorage1.add(dataToAdd, null));
// remove with with same seq nr as prev. ignored -> failed
newSequenceNumber = 4;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertFalse(dataStorage1.remove(dataToRemove, null));
// remove with with higher seq nr -> ok
newSequenceNumber = 5;
hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertTrue(dataStorage1.remove(dataToRemove, null));
}
@Test
public void testAddAndRemoveMailboxData() throws InterruptedException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException {
// sender
MockMessage mockMessage = new MockMessage("MockMessage");
SealedAndSignedMessage sealedAndSignedMessage = encryptionService1.encryptAndSignMessage(keyRing1.getPubKeyRing(), mockMessage);
ExpirableMailboxPayload expirableMailboxPayload = new ExpirableMailboxPayload(sealedAndSignedMessage,
keyRing1.getStorageSignatureKeyPair().getPublic(),
keyRing2.getStorageSignatureKeyPair().getPublic());
ProtectedMailboxData data = dataStorage1.getMailboxDataWithSignedSeqNr(expirableMailboxPayload, storageSignatureKeyPair1, storageSignatureKeyPair2.getPublic());
Assert.assertTrue(dataStorage1.add(data, null));
Thread.sleep(sleepTime);
Assert.assertEquals(1, dataStorage1.getMap().size());
// receiver (storageSignatureKeyPair2)
int newSequenceNumber = data.sequenceNumber + 1;
byte[] hashOfDataAndSeqNr = CryptoUtil.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
byte[] signature;
ProtectedMailboxData dataToRemove;
// wrong sig -> fail
signature = CryptoUtil.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, storageSignatureKeyPair2.getPublic(), newSequenceNumber, signature, storageSignatureKeyPair2.getPublic());
Assert.assertFalse(dataStorage1.removeMailboxData(dataToRemove, null));
// wrong seq nr
signature = CryptoUtil.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, storageSignatureKeyPair2.getPublic(), data.sequenceNumber, signature, storageSignatureKeyPair2.getPublic());
Assert.assertFalse(dataStorage1.removeMailboxData(dataToRemove, null));
// wrong signingKey
signature = CryptoUtil.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature, storageSignatureKeyPair2.getPublic());
Assert.assertFalse(dataStorage1.removeMailboxData(dataToRemove, null));
// wrong peerPubKey
signature = CryptoUtil.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, storageSignatureKeyPair2.getPublic(), newSequenceNumber, signature, storageSignatureKeyPair1.getPublic());
Assert.assertFalse(dataStorage1.removeMailboxData(dataToRemove, null));
// receiver can remove it (storageSignatureKeyPair2) -> all ok
Assert.assertEquals(1, dataStorage1.getMap().size());
signature = CryptoUtil.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, storageSignatureKeyPair2.getPublic(), newSequenceNumber, signature, storageSignatureKeyPair2.getPublic());
Assert.assertTrue(dataStorage1.removeMailboxData(dataToRemove, null));
Assert.assertEquals(0, dataStorage1.getMap().size());
}
/*@Test
public void testTryToHack() throws InterruptedException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException {
ProtectedData data = dataStorage1.getDataWithSignedSeqNr(mockData, storageSignatureKeyPair1);
Assert.assertTrue(dataStorage1.add(data, null));
Thread.sleep(sleepTime);
Assert.assertEquals(1, dataStorage1.getMap().size());
Assert.assertEquals(1, dataStorage2.getMap().size());
// hackers key pair is storageSignatureKeyPair2
// change seq nr. and signature: fails on both own and peers dataStorage
int newSequenceNumber = data.sequenceNumber + 1;
byte[] hashOfDataAndSeqNr = cryptoService2.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
byte[] signature = cryptoService2.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
ProtectedData dataToAdd = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertFalse(dataStorage1.add(dataToAdd, null));
Assert.assertFalse(dataStorage2.add(dataToAdd, null));
// change seq nr. and signature and data pub key. fails on peers dataStorage, succeeds on own dataStorage
newSequenceNumber = data.sequenceNumber + 2;
hashOfDataAndSeqNr = cryptoService2.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
signature = cryptoService2.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
dataToAdd = new ProtectedData(data.expirablePayload, data.ttl, storageSignatureKeyPair2.getPublic(), newSequenceNumber, signature);
Assert.assertTrue(dataStorage2.add(dataToAdd, null));
Assert.assertFalse(dataStorage1.add(dataToAdd, null));
Thread.sleep(sleepTime);
Assert.assertEquals(1, dataStorage2.getMap().size());
Thread.sleep(sleepTime);
Assert.assertEquals(1, dataStorage1.getMap().size());
Assert.assertEquals(data, dataStorage1.getMap().values().stream().findFirst().get());
Assert.assertEquals(dataToAdd, dataStorage2.getMap().values().stream().findFirst().get());
Assert.assertNotEquals(data, dataToAdd);
newSequenceNumber = data.sequenceNumber + 3;
hashOfDataAndSeqNr = cryptoService1.getHash(new DataAndSeqNr(data.expirablePayload, newSequenceNumber));
signature = cryptoService1.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
ProtectedData dataToRemove = new ProtectedData(data.expirablePayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature);
Assert.assertTrue(dataStorage1.remove(dataToRemove, null));
Assert.assertEquals(0, dataStorage1.getMap().size());
}*/
/* //@Test
public void testTryToHackMailboxData() throws InterruptedException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException {
MockMessage mockMessage = new MockMessage("MockMessage");
SealedAndSignedMessage sealedAndSignedMessage = cryptoService1.encryptAndSignMessage(keyRing1.getPubKeyRing(), mockMessage);
ExpirableMailboxPayload expirableMailboxPayload = new ExpirableMailboxPayload(sealedAndSignedMessage,
keyRing1.getStorageSignatureKeyPair().getPublic(),
keyRing2.getStorageSignatureKeyPair().getPublic());
// sender
ProtectedMailboxData data = dataStorage1.getMailboxDataWithSignedSeqNr(expirableMailboxPayload, storageSignatureKeyPair1, storageSignatureKeyPair2.getPublic());
Assert.assertTrue(dataStorage1.add(data, null));
Thread.sleep(sleepTime);
Assert.assertEquals(1, dataStorage1.getMap().size());
// receiver (storageSignatureKeyPair2)
int newSequenceNumber = data.sequenceNumber + 1;
byte[] hashOfDataAndSeqNr = cryptoService2.getHash(new DataAndSeqNr(expirableMailboxPayload, newSequenceNumber));
byte[] signature;
ProtectedMailboxData dataToRemove;
// wrong sig -> fail
signature = cryptoService2.signStorageData(storageSignatureKeyPair1.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, storageSignatureKeyPair2.getPublic(), newSequenceNumber, signature, storageSignatureKeyPair2.getPublic());
Assert.assertFalse(dataStorage1.removeMailboxData(dataToRemove, null));
// wrong seq nr
signature = cryptoService2.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, storageSignatureKeyPair2.getPublic(), data.sequenceNumber, signature, storageSignatureKeyPair2.getPublic());
Assert.assertFalse(dataStorage1.removeMailboxData(dataToRemove, null));
// wrong signingKey
signature = cryptoService2.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, data.ownerStoragePubKey, newSequenceNumber, signature, storageSignatureKeyPair2.getPublic());
Assert.assertFalse(dataStorage1.removeMailboxData(dataToRemove, null));
// wrong peerPubKey
signature = cryptoService2.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, storageSignatureKeyPair2.getPublic(), newSequenceNumber, signature, storageSignatureKeyPair1.getPublic());
Assert.assertFalse(dataStorage1.removeMailboxData(dataToRemove, null));
// all ok
Assert.assertEquals(1, dataStorage1.getMap().size());
signature = cryptoService2.signStorageData(storageSignatureKeyPair2.getPrivate(), hashOfDataAndSeqNr);
dataToRemove = new ProtectedMailboxData(expirableMailboxPayload, data.ttl, storageSignatureKeyPair2.getPublic(), newSequenceNumber, signature, storageSignatureKeyPair2.getPublic());
Assert.assertTrue(dataStorage1.removeMailboxData(dataToRemove, null));
Assert.assertEquals(0, dataStorage1.getMap().size());
}
*/
}

View file

@ -0,0 +1,49 @@
package io.bitsquare.p2p.storage.mocks;
import io.bitsquare.p2p.storage.data.PubKeyProtectedExpirablePayload;
import java.security.PublicKey;
public class MockData implements PubKeyProtectedExpirablePayload {
public final String msg;
public final PublicKey publicKey;
public long ttl;
public MockData(String msg, PublicKey publicKey) {
this.msg = msg;
this.publicKey = publicKey;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MockData)) return false;
MockData that = (MockData) o;
return !(msg != null ? !msg.equals(that.msg) : that.msg != null);
}
@Override
public int hashCode() {
return msg != null ? msg.hashCode() : 0;
}
@Override
public String toString() {
return "MockData{" +
"msg='" + msg + '\'' +
'}';
}
@Override
public long getTTL() {
return ttl;
}
@Override
public PublicKey getPubKey() {
return publicKey;
}
}