From 6542bd145126c7ca68da5c9969cbf5e5216dc9eb Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Sat, 4 Jul 2015 11:13:35 +0200
Subject: [PATCH 01/42] now the build should work for anyone (esp. travis)
---
gradle.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle.properties b/gradle.properties
index fc7300b..eb6ed30 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,7 +3,7 @@
signing.keyId=
signing.password=
-signing.secretKeyRingFile=
+#signing.secretKeyRingFile=
ossrhUsername=
ossrhPassword=
From b8546e28afc615fb342e64f3a4c3689a6f4254bd Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Wed, 5 Aug 2015 19:52:18 +0200
Subject: [PATCH 02/42] Moving "Security" to a separate port, so there can be a
Bouncycastle and a Spongycastle implementation. (BC doesn't work on Android,
SC can't be used on Oracle's JVM)
---
.../dissem/bitmessage/demo/Application.java | 4 +-
.../java/ch/dissem/bitmessage/demo/Main.java | 4 +-
domain/build.gradle | 7 +-
.../dissem/bitmessage/BitmessageContext.java | 141 ++++---
.../ch/dissem/bitmessage/InternalContext.java | 48 ++-
.../bitmessage/entity/BitmessageAddress.java | 23 +-
.../dissem/bitmessage/entity/Encrypted.java | 1 +
.../bitmessage/entity/NetworkMessage.java | 4 +-
.../bitmessage/entity/ObjectMessage.java | 12 +-
.../dissem/bitmessage/entity/Plaintext.java | 3 +-
.../dissem/bitmessage/entity/Streamable.java | 2 +
.../bitmessage/entity/payload/Broadcast.java | 5 +-
.../bitmessage/entity/payload/CryptoBox.java | 78 +---
.../bitmessage/entity/payload/Pubkey.java | 7 +-
.../entity/payload/V4Broadcast.java | 1 +
.../entity/valueobject/InventoryVector.java | 3 +-
.../entity/valueobject/PrivateKey.java | 24 +-
.../ch/dissem/bitmessage/factory/Factory.java | 18 +-
.../bitmessage/factory/V3MessageFactory.java | 5 +-
.../bitmessage/ports/AbstractSecurity.java | 175 +++++++++
.../bitmessage/ports/NetworkHandler.java | 2 +
.../ch/dissem/bitmessage/ports/Security.java | 206 ++++++++++
.../bitmessage/ports/SimplePOWEngine.java | 2 +-
.../ch/dissem/bitmessage/utils/Points.java | 32 ++
.../ch/dissem/bitmessage/utils/Property.java | 7 +-
.../ch/dissem/bitmessage/utils/Security.java | 365 ------------------
.../ch/dissem/bitmessage/utils/Singleton.java | 34 ++
.../ch/dissem/bitmessage/DecryptionTest.java | 3 +-
.../ch/dissem/bitmessage/EncryptionTest.java | 8 +-
.../ch/dissem/bitmessage/SignatureTest.java | 3 +-
.../entity/BitmessageAddressTest.java | 7 +-
.../ports/ProofOfWorkEngineTest.java | 9 +-
.../ch/dissem/bitmessage/utils/BytesTest.java | 3 -
.../dissem/bitmessage/utils/DecodeTest.java | 3 -
.../dissem/bitmessage/utils/EncodeTest.java | 3 -
.../ch/dissem/bitmessage/utils/TestBase.java | 28 ++
gradle/wrapper/gradle-wrapper.properties | 2 +-
.../bitmessage/networking/Connection.java | 4 +-
...rkNode.java => DefaultNetworkHandler.java} | 11 +-
...st.java => DefaultNetworkHandlerTest.java} | 11 +-
repositories/build.gradle | 4 +-
.../bitmessage/repository/JdbcConfig.java | 6 +-
.../bitmessage/repository/JdbcHelper.java | 9 +-
.../repository/JdbcAddressRepositoryTest.java | 2 +-
.../repository/JdbcInventoryTest.java | 2 +-
.../repository/JdbcMessageRepositoryTest.java | 14 +-
.../repository/JdbcNodeRegistryTest.java | 2 +-
.../bitmessage/repository/TestBase.java | 38 ++
.../bitmessage/repository/TestJdbcConfig.java | 3 +-
security-bc/build.gradle | 18 +
.../security/bc/BouncySecurity.java | 153 ++++++++
.../bitmessage/security}/SecurityTest.java | 62 ++-
security-sc/build.gradle | 17 +
.../security/sc/SpongySecurity.java | 153 ++++++++
settings.gradle | 4 +
wif/build.gradle | 1 +
.../ch/dissem/bitmessage/wif/WifExporter.java | 4 +-
.../ch/dissem/bitmessage/wif/WifImporter.java | 5 +-
.../bitmessage/wif/WifExporterTest.java | 2 +
.../bitmessage/wif/WifImporterTest.java | 2 +
60 files changed, 1168 insertions(+), 641 deletions(-)
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ports/Security.java
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/utils/Points.java
delete mode 100644 domain/src/main/java/ch/dissem/bitmessage/utils/Security.java
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/utils/Singleton.java
create mode 100644 domain/src/test/java/ch/dissem/bitmessage/utils/TestBase.java
rename networking/src/main/java/ch/dissem/bitmessage/networking/{NetworkNode.java => DefaultNetworkHandler.java} (96%)
rename networking/src/test/java/ch/dissem/bitmessage/networking/{NetworkNodeTest.java => DefaultNetworkHandlerTest.java} (86%)
create mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java
create mode 100644 security-bc/build.gradle
create mode 100644 security-bc/src/main/java/ch/dissem/bitmessage/security/bc/BouncySecurity.java
rename {domain/src/test/java/ch/dissem/bitmessage/utils => security-bc/src/test/java/ch/dissem/bitmessage/security}/SecurityTest.java (60%)
create mode 100644 security-sc/build.gradle
create mode 100644 security-sc/src/main/java/ch/dissem/bitmessage/security/sc/SpongySecurity.java
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
index 0e03880..416e7b7 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
@@ -20,7 +20,7 @@ import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey;
-import ch.dissem.bitmessage.networking.NetworkNode;
+import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.repository.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +45,7 @@ public class Application {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
- .networkHandler(new NetworkNode())
+ .networkHandler(new DefaultNetworkHandler())
.port(48444)
.build();
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
index 4ba8fa7..7f934a3 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
@@ -17,7 +17,7 @@
package ch.dissem.bitmessage.demo;
import ch.dissem.bitmessage.BitmessageContext;
-import ch.dissem.bitmessage.networking.NetworkNode;
+import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.wif.WifExporter;
import ch.dissem.bitmessage.wif.WifImporter;
@@ -49,7 +49,7 @@ public class Main {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
- .networkHandler(new NetworkNode())
+ .networkHandler(new DefaultNetworkHandler())
.port(48444)
.build();
diff --git a/domain/build.gradle b/domain/build.gradle
index eb83499..6bcbe6d 100644
--- a/domain/build.gradle
+++ b/domain/build.gradle
@@ -12,6 +12,7 @@ uploadArchives {
dependencies {
compile 'org.slf4j:slf4j-api:1.7.12'
- compile 'org.bouncycastle:bcprov-jdk15on:1.52'
- testCompile group: 'junit', name: 'junit', version: '4.11'
-}
\ No newline at end of file
+ testCompile 'junit:junit:4.11'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(':security-bc')
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 7878ada..d7c04b5 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -27,12 +27,13 @@ import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Property;
-import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
@@ -57,12 +58,17 @@ public class BitmessageContext {
public static final int CURRENT_VERSION = 3;
private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
+ private final ExecutorService pool;
+
private final InternalContext ctx;
private Listener listener;
private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder);
+ // As this thread is used for parts that do POW, which itself uses parallel threads, only
+ // one should be executed at any time.
+ pool = Executors.newFixedThreadPool(1);
}
public AddressRepository addresses() {
@@ -74,7 +80,7 @@ public class BitmessageContext {
}
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
- BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
+ final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
shorter,
ctx.getStreams()[0],
ctx.getNetworkNonceTrialsPerByte(),
@@ -82,8 +88,12 @@ public class BitmessageContext {
features
));
ctx.getAddressRepo().save(identity);
- // TODO: this should happen in a separate thread
- ctx.sendPubkey(identity, identity.getStream());
+ pool.submit(new Runnable() {
+ @Override
+ public void run() {
+ ctx.sendPubkey(identity, identity.getStream());
+ }
+ });
return identity;
}
@@ -91,63 +101,71 @@ public class BitmessageContext {
// TODO
}
- public void broadcast(BitmessageAddress from, String subject, String message) {
- // TODO: all this should happen in a separate thread
- Plaintext msg = new Plaintext.Builder(BROADCAST)
- .from(from)
- .message(subject, message)
- .build();
+ public void broadcast(final BitmessageAddress from, final String subject, final String message) {
+ pool.submit(new Runnable() {
+ @Override
+ public void run() {
+ Plaintext msg = new Plaintext.Builder(BROADCAST)
+ .from(from)
+ .message(subject, message)
+ .build();
- LOG.info("Sending message.");
- msg.setStatus(DOING_PROOF_OF_WORK);
- ctx.getMessageRepository().save(msg);
- ctx.send(
- from,
- from,
- Factory.getBroadcast(from, msg),
- +2 * DAY,
- 0,
- 0
- );
- msg.setStatus(SENT);
- msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
- ctx.getMessageRepository().save(msg);
+ LOG.info("Sending message.");
+ msg.setStatus(DOING_PROOF_OF_WORK);
+ ctx.getMessageRepository().save(msg);
+ ctx.send(
+ from,
+ from,
+ Factory.getBroadcast(from, msg),
+ +2 * DAY,
+ 0,
+ 0
+ );
+ msg.setStatus(SENT);
+ msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
+ ctx.getMessageRepository().save(msg);
+ }
+ });
}
- public void send(BitmessageAddress from, BitmessageAddress to, String subject, String message) {
+ public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) {
if (from.getPrivateKey() == null) {
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
}
- // TODO: all this should happen in a separate thread
- Plaintext msg = new Plaintext.Builder(MSG)
- .from(from)
- .to(to)
- .message(subject, message)
- .build();
- if (to.getPubkey() == null) {
- tryToFindMatchingPubkey(to);
- }
- if (to.getPubkey() == null) {
- LOG.info("Public key is missing from recipient. Requesting.");
- requestPubkey(from, to);
- msg.setStatus(PUBKEY_REQUESTED);
- ctx.getMessageRepository().save(msg);
- } else {
- LOG.info("Sending message.");
- msg.setStatus(DOING_PROOF_OF_WORK);
- ctx.getMessageRepository().save(msg);
- ctx.send(
- from,
- to,
- new Msg(msg),
- +2 * DAY,
- ctx.getNonceTrialsPerByte(to),
- ctx.getExtraBytes(to)
- );
- msg.setStatus(SENT);
- msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
- ctx.getMessageRepository().save(msg);
- }
+ pool.submit(new Runnable() {
+ @Override
+ public void run() {
+ Plaintext msg = new Plaintext.Builder(MSG)
+ .from(from)
+ .to(to)
+ .message(subject, message)
+ .build();
+ if (to.getPubkey() == null) {
+ tryToFindMatchingPubkey(to);
+ }
+ if (to.getPubkey() == null) {
+ LOG.info("Public key is missing from recipient. Requesting.");
+ requestPubkey(from, to);
+ msg.setStatus(PUBKEY_REQUESTED);
+ ctx.getMessageRepository().save(msg);
+ } else {
+ LOG.info("Sending message.");
+ msg.setStatus(DOING_PROOF_OF_WORK);
+ ctx.getMessageRepository().save(msg);
+ ctx.send(
+ from,
+ to,
+ new Msg(msg),
+ +2 * DAY,
+ ctx.getNonceTrialsPerByte(to),
+ ctx.getExtraBytes(to)
+ );
+ msg.setStatus(SENT);
+ msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
+ ctx.getMessageRepository().save(msg);
+ }
+ }
+ });
}
private void requestPubkey(BitmessageAddress requestingIdentity, BitmessageAddress address) {
@@ -169,8 +187,7 @@ public class BitmessageContext {
.expiresTime(expires)
.payload(payload)
.build();
- Security.doProofOfWork(object, ctx.getProofOfWorkEngine(),
- ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
+ ctx.getSecurity().doProofOfWork(object, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
ctx.getInventory().storeObject(object);
ctx.getNetworkHandler().offer(object.getInventoryVector());
}
@@ -184,6 +201,10 @@ public class BitmessageContext {
ctx.getNetworkHandler().stop();
}
+ public boolean isRunning() {
+ return ctx.getNetworkHandler().isRunning();
+ }
+
public void addContact(BitmessageAddress contact) {
ctx.getAddressRepo().save(contact);
tryToFindMatchingPubkey(contact);
@@ -258,6 +279,7 @@ public class BitmessageContext {
AddressRepository addressRepo;
MessageRepository messageRepo;
ProofOfWorkEngine proofOfWorkEngine;
+ Security security;
public Builder() {
}
@@ -292,6 +314,11 @@ public class BitmessageContext {
return this;
}
+ public Builder security(Security security) {
+ this.security = security;
+ return this;
+ }
+
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
this.proofOfWorkEngine = proofOfWorkEngine;
return this;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 8ac64e0..03fef80 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -21,7 +21,7 @@ import ch.dissem.bitmessage.entity.payload.Broadcast;
import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.ports.*;
-import ch.dissem.bitmessage.utils.Security;
+import ch.dissem.bitmessage.utils.Singleton;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +42,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
public class InternalContext {
private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class);
+ private final Security security;
private final Inventory inventory;
private final NodeRegistry nodeRegistry;
private final NetworkHandler networkHandler;
@@ -56,16 +57,29 @@ public class InternalContext {
private long clientNonce;
public InternalContext(BitmessageContext.Builder builder) {
+ this.security = builder.security;
this.inventory = builder.inventory;
this.nodeRegistry = builder.nodeRegistry;
this.networkHandler = builder.networkHandler;
this.addressRepository = builder.addressRepo;
this.messageRepository = builder.messageRepo;
this.proofOfWorkEngine = builder.proofOfWorkEngine;
- this.clientNonce = Security.randomNonce();
+ this.clientNonce = security.randomNonce();
+
+ Singleton.initialize(security);
port = builder.port;
- streams.add(1L); // FIXME
+
+ // TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
+ for (BitmessageAddress address : addressRepository.getIdentities()) {
+ streams.add(address.getStream());
+ }
+ for (BitmessageAddress address : addressRepository.getSubscriptions()) {
+ streams.add(address.getStream());
+ }
+ if (streams.isEmpty()) {
+ streams.add(1L);
+ }
init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
}
@@ -78,6 +92,10 @@ public class InternalContext {
}
}
+ public Security getSecurity() {
+ return security;
+ }
+
public Inventory getInventory() {
return inventory;
}
@@ -111,14 +129,6 @@ public class InternalContext {
return result;
}
- public void addStream(long stream) {
- streams.add(stream);
- }
-
- public void removeStream(long stream) {
- streams.remove(stream);
- }
-
public int getPort() {
return port;
}
@@ -152,14 +162,14 @@ public class InternalContext {
.payload(payload)
.build();
if (object.isSigned()) {
- object.sign(from.getPrivateKey());
+ object.sign( from.getPrivateKey());
}
if (payload instanceof Broadcast) {
((Broadcast) payload).encrypt();
} else if (payload instanceof Encrypted) {
object.encrypt(to.getPubkey());
}
- Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
+ security.doProofOfWork(object, nonceTrialsPerByte, extraBytes);
if (payload instanceof PlaintextHolder) {
Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
plaintext.setInventoryVector(object.getInventoryVector());
@@ -182,13 +192,13 @@ public class InternalContext {
.payload(identity.getPubkey())
.build();
response.sign(identity.getPrivateKey());
- response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
- Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
+ response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
+ security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
if (response.isSigned()) {
response.sign(identity.getPrivateKey());
}
if (response instanceof Encrypted) {
- response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
+ response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
}
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
@@ -206,7 +216,7 @@ public class InternalContext {
.expiresTime(expires)
.payload(new GetPubkey(contact))
.build();
- Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
+ security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
}
@@ -215,10 +225,6 @@ public class InternalContext {
return clientNonce;
}
- public void setClientNonce(long clientNonce) {
- this.clientNonce = clientNonce;
- }
-
public interface ContextHolder {
void setContext(InternalContext context);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
index eb348a2..0441e6e 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
@@ -19,22 +19,27 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
-import ch.dissem.bitmessage.utils.*;
+import ch.dissem.bitmessage.utils.AccessCounter;
+import ch.dissem.bitmessage.utils.Base58;
+import ch.dissem.bitmessage.utils.Bytes;
+import ch.dissem.bitmessage.utils.Encode;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects;
import static ch.dissem.bitmessage.utils.Decode.bytes;
import static ch.dissem.bitmessage.utils.Decode.varInt;
+import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
* holding private keys.
*/
-public class BitmessageAddress {
+public class BitmessageAddress implements Serializable {
private final long version;
private final long stream;
private final byte[] ripe;
@@ -62,19 +67,19 @@ public class BitmessageAddress {
Encode.varInt(version, os);
Encode.varInt(stream, os);
if (version < 4) {
- byte[] checksum = Security.sha512(os.toByteArray(), ripe);
+ byte[] checksum = security().sha512(os.toByteArray(), ripe);
this.tag = null;
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
} else {
// for tag and decryption key, the checksum has to be created with 0x00 padding
- byte[] checksum = Security.doubleSha512(os.toByteArray(), ripe);
+ byte[] checksum = security().doubleSha512(os.toByteArray(), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64);
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
}
// but for the address and its checksum they need to be stripped
int offset = Bytes.numberOfLeadingZeros(ripe);
os.write(ripe, offset, ripe.length - offset);
- byte[] checksum = Security.doubleSha512(os.toByteArray());
+ byte[] checksum = security().doubleSha512(os.toByteArray());
os.write(checksum, 0, 4);
this.address = "BM-" + Base58.encode(os.toByteArray());
} catch (IOException e) {
@@ -103,18 +108,18 @@ public class BitmessageAddress {
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
// test checksum
- byte[] checksum = Security.doubleSha512(bytes, bytes.length - 4);
+ byte[] checksum = security().doubleSha512(bytes, bytes.length - 4);
byte[] expectedChecksum = bytes(in, 4);
for (int i = 0; i < 4; i++) {
if (expectedChecksum[i] != checksum[i])
throw new IllegalArgumentException("Checksum of address failed");
}
if (version < 4) {
- checksum = Security.sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
+ checksum = security().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
this.tag = null;
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
} else {
- checksum = Security.doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
+ checksum = security().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64);
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
}
@@ -129,7 +134,7 @@ public class BitmessageAddress {
Encode.varInt(version, out);
Encode.varInt(stream, out);
out.write(ripe);
- return Arrays.copyOfRange(Security.doubleSha512(out.toByteArray()), 32, 64);
+ return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64);
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java
index 8b15371..9eaa2ab 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java
@@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
+import ch.dissem.bitmessage.ports.Security;
import java.io.IOException;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
index c10f942..8790d3a 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
@@ -26,7 +26,7 @@ import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
-import static ch.dissem.bitmessage.utils.Security.sha512;
+import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* A network message is exchanged between two nodes.
@@ -48,7 +48,7 @@ public class NetworkMessage implements Streamable {
* First 4 bytes of sha512(payload)
*/
private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
- byte[] d = sha512(bytes);
+ byte[] d = security().sha512(bytes);
return new byte[]{d[0], d[1], d[2], d[3]};
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
index 0ef3340..128084e 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
@@ -22,14 +22,16 @@ import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
+import ch.dissem.bitmessage.ports.Security;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Encode;
-import ch.dissem.bitmessage.utils.Security;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
/**
* The 'object' command sends an object that is shared throughout the network.
*/
@@ -89,7 +91,9 @@ public class ObjectMessage implements MessagePayload {
}
public InventoryVector getInventoryVector() {
- return new InventoryVector(Bytes.truncate(Security.doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32));
+ return new InventoryVector(
+ Bytes.truncate(security().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
+ );
}
private boolean isEncrypted() {
@@ -113,7 +117,7 @@ public class ObjectMessage implements MessagePayload {
public void sign(PrivateKey key) {
if (payload.isSigned()) {
- payload.setSignature(Security.getSignature(getBytesToSign(), key));
+ payload.setSignature(security().getSignature(getBytesToSign(), key));
}
}
@@ -147,7 +151,7 @@ public class ObjectMessage implements MessagePayload {
public boolean isSignatureValid(Pubkey pubkey) throws IOException {
if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
- return Security.isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
+ return security().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
}
@Override
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index 28cebfa..e73849f 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -19,6 +19,7 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.factory.Factory;
+import ch.dissem.bitmessage.ports.Security;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
@@ -28,7 +29,7 @@ import java.util.*;
/**
* The unencrypted message to be sent by 'msg' or 'broadcast'.
*/
-public class Plaintext implements Streamable {
+public class Plaintext implements Streamable, Serializable {
private final Type type;
private final BitmessageAddress from;
private final long encoding;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
index 9ee4cf9..0601a44 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
@@ -16,6 +16,8 @@
package ch.dissem.bitmessage.entity;
+import ch.dissem.bitmessage.ports.Security;
+
import java.io.IOException;
import java.io.OutputStream;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
index 2491d85..bf5ff7f 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
@@ -21,11 +21,12 @@ import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
-import ch.dissem.bitmessage.utils.Security;
+import ch.dissem.bitmessage.ports.Security;
import java.io.IOException;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
+import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
@@ -78,7 +79,7 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted, Plai
}
public void encrypt() throws IOException {
- encrypt(Security.createPublicKey(plaintext.getFrom().getPublicDecryptionKey()).getEncoded(false));
+ encrypt(security().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
}
@Override
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
index 2018373..7f994d1 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
@@ -17,28 +17,16 @@
package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.Streamable;
-import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.*;
-import org.bouncycastle.crypto.BufferedBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-import org.bouncycastle.crypto.engines.AESEngine;
-import org.bouncycastle.crypto.modes.CBCBlockCipher;
-import org.bouncycastle.crypto.paddings.PKCS7Padding;
-import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
-import java.math.BigInteger;
import java.util.Arrays;
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
+import static ch.dissem.bitmessage.utils.Singleton.security;
public class CryptoBox implements Streamable {
@@ -46,35 +34,31 @@ public class CryptoBox implements Streamable {
private final byte[] initializationVector;
private final int curveType;
- private final ECPoint R;
+ private final byte[] R;
private final byte[] mac;
private byte[] encrypted;
- public CryptoBox(Streamable data, byte[] encryptionKey) throws IOException {
- this(data, Security.keyToPoint(encryptionKey));
- }
-
- public CryptoBox(Streamable data, ECPoint K) throws IOException {
+ public CryptoBox(Streamable data, byte[] K) throws IOException {
curveType = 0x02CA;
// 1. The destination public key is called K.
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
- initializationVector = Security.randomBytes(16);
+ initializationVector = security().randomBytes(16);
// 3. Generate a new random EC key pair with private key called r and public key called R.
- byte[] r = Security.randomBytes(PRIVATE_KEY_SIZE);
- R = Security.createPublicKey(r);
+ byte[] r = security().randomBytes(PRIVATE_KEY_SIZE);
+ R = security().createPublicKey(r);
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
- ECPoint P = K.multiply(Security.keyToBigInt(r)).normalize();
- byte[] X = P.getXCoord().getEncoded();
+ byte[] P = security().multiply(K, r);
+ byte[] X = Points.getX(P);
// 5. Use the X component of public key P and calculate the SHA512 hash H.
- byte[] H = Security.sha512(X);
+ byte[] H = security().sha512(X);
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
- encrypted = crypt(true, Encode.bytes(data), key_e);
+ encrypted = security().crypt(true, Encode.bytes(data), key_e, initializationVector);
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
mac = calculateMac(key_m);
@@ -84,7 +68,7 @@ public class CryptoBox implements Streamable {
private CryptoBox(Builder builder) {
initializationVector = builder.initializationVector;
curveType = builder.curveType;
- R = Security.createPoint(builder.xComponent, builder.yComponent);
+ R = security().createPoint(builder.xComponent, builder.yComponent);
encrypted = builder.encrypted;
mac = builder.mac;
}
@@ -102,18 +86,17 @@ public class CryptoBox implements Streamable {
}
/**
- * @param privateKey a private key, typically should be 32 bytes long
+ * @param k a private key, typically should be 32 bytes long
* @return an InputStream yielding the decrypted data
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
* @see https://bitmessage.org/wiki/Encryption#Decryption
*/
- public InputStream decrypt(byte[] privateKey) throws DecryptionFailedException {
+ public InputStream decrypt(byte[] k) throws DecryptionFailedException {
// 1. The private key used to decrypt is called k.
- BigInteger k = Security.keyToBigInt(privateKey);
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
- ECPoint P = R.multiply(k).normalize();
+ byte[] P = security().multiply(R, k);
// 3. Use the X component of public key P and calculate the SHA512 hash H.
- byte[] H = Security.sha512(P.getXCoord().getEncoded());
+ byte[] H = security().sha512(Arrays.copyOfRange(P, 1, 33));
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
@@ -126,49 +109,28 @@ public class CryptoBox implements Streamable {
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
// and the cipher text as payload. The output is the padded input text.
- return new ByteArrayInputStream(crypt(false, encrypted, key_e));
+ return new ByteArrayInputStream(security().crypt(false, encrypted, key_e, initializationVector));
}
private byte[] calculateMac(byte[] key_m) {
try {
ByteArrayOutputStream macData = new ByteArrayOutputStream();
writeWithoutMAC(macData);
- return Security.mac(key_m, macData.toByteArray());
+ return security().mac(key_m, macData.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- private byte[] crypt(boolean encrypt, byte[] data, byte[] key_e) {
- BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
-
- CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
-
- cipher.init(encrypt, params);
-
- byte[] buffer = new byte[cipher.getOutputSize(data.length)];
- int length = cipher.processBytes(data, 0, data.length, buffer, 0);
- try {
- length += cipher.doFinal(buffer, length);
- } catch (InvalidCipherTextException e) {
- throw new IllegalArgumentException(e);
- }
- if (length < buffer.length) {
- return Arrays.copyOfRange(buffer, 0, length);
- }
- return buffer;
- }
-
private void writeWithoutMAC(OutputStream out) throws IOException {
out.write(initializationVector);
Encode.int16(curveType, out);
- writeCoordinateComponent(out, R.getXCoord());
- writeCoordinateComponent(out, R.getYCoord());
+ writeCoordinateComponent(out, Points.getX(R));
+ writeCoordinateComponent(out, Points.getY(R));
out.write(encrypted);
}
- private void writeCoordinateComponent(OutputStream out, ECFieldElement coord) throws IOException {
- byte[] x = coord.getEncoded();
+ private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException {
int offset = Bytes.numberOfLeadingZeros(x);
int length = x.length - offset;
Encode.int16(length, out);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
index 2bdcbb6..1243b32 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
@@ -20,8 +20,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
-import static ch.dissem.bitmessage.utils.Security.ripemd160;
-import static ch.dissem.bitmessage.utils.Security.sha512;
+import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
@@ -34,7 +33,7 @@ public abstract class Pubkey extends ObjectPayload {
}
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
- return ripemd160(sha512(publicSigningKey, publicEncryptionKey));
+ return security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey));
}
public abstract byte[] getSigningKey();
@@ -44,7 +43,7 @@ public abstract class Pubkey extends ObjectPayload {
public abstract int getBehaviorBitfield();
public byte[] getRipe() {
- return ripemd160(sha512(getSigningKey(), getEncryptionKey()));
+ return security().ripemd160(security().sha512(getSigningKey(), getEncryptionKey()));
}
public long getNonceTrialsPerByte() {
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java
index 39127a8..03364d2 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java
@@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.ports.Security;
import java.io.IOException;
import java.io.InputStream;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
index 366d26b..f87dd13 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
@@ -21,9 +21,10 @@ import ch.dissem.bitmessage.utils.Strings;
import java.io.IOException;
import java.io.OutputStream;
+import java.io.Serializable;
import java.util.Arrays;
-public class InventoryVector implements Streamable {
+public class InventoryVector implements Streamable, Serializable {
/**
* Hash of the object
*/
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
index f0956a6..d07c859 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
@@ -18,18 +18,18 @@ package ch.dissem.bitmessage.entity.valueobject;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.payload.Pubkey;
-import ch.dissem.bitmessage.entity.payload.V3Pubkey;
-import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
-import ch.dissem.bitmessage.utils.Security;
import java.io.*;
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
/**
- * Created by chris on 18.04.15.
+ * Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
+ * {@link Pubkey} object.
*/
public class PrivateKey implements Streamable {
public static final int PRIVATE_KEY_SIZE = 32;
@@ -45,15 +45,15 @@ public class PrivateKey implements Streamable {
byte[] pubEK;
byte[] ripe;
do {
- privSK = Security.randomBytes(PRIVATE_KEY_SIZE);
- privEK = Security.randomBytes(PRIVATE_KEY_SIZE);
- pubSK = Security.createPublicKey(privSK).getEncoded(false);
- pubEK = Security.createPublicKey(privEK).getEncoded(false);
+ privSK = security().randomBytes(PRIVATE_KEY_SIZE);
+ privEK = security().randomBytes(PRIVATE_KEY_SIZE);
+ pubSK = security().createPublicKey(privSK);
+ pubEK = security().createPublicKey(privEK);
ripe = Pubkey.getRipe(pubSK, pubEK);
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
this.privateSigningKey = privSK;
this.privateEncryptionKey = privEK;
- this.pubkey = Security.createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
+ this.pubkey = security().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
nonceTrialsPerByte, extraBytes, features);
}
@@ -66,9 +66,9 @@ public class PrivateKey implements Streamable {
public PrivateKey(long version, long stream, String passphrase, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
try {
// FIXME: this is most definitely wrong
- this.privateSigningKey = Bytes.truncate(Security.sha512(passphrase.getBytes("UTF-8"), new byte[]{0}), 32);
- this.privateEncryptionKey = Bytes.truncate(Security.sha512(passphrase.getBytes("UTF-8"), new byte[]{1}), 32);
- this.pubkey = Security.createPubkey(version, stream, privateSigningKey, privateEncryptionKey,
+ this.privateSigningKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{0}), 32);
+ this.privateEncryptionKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{1}), 32);
+ this.pubkey = security().createPubkey(version, stream, privateSigningKey, privateEncryptionKey,
nonceTrialsPerByte, extraBytes, features);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java
index a144bb6..5584ec7 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java
@@ -23,7 +23,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.NodeException;
-import ch.dissem.bitmessage.utils.Security;
+import ch.dissem.bitmessage.ports.Security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,6 +32,8 @@ import java.io.InputStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
/**
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
*/
@@ -115,8 +117,8 @@ public class Factory {
BitmessageAddress temp = new BitmessageAddress(address);
PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey,
createPubkey(temp.getVersion(), temp.getStream(),
- Security.createPublicKey(privateSigningKey).getEncoded(false),
- Security.createPublicKey(privateEncryptionKey).getEncoded(false),
+ security().createPublicKey(privateSigningKey),
+ security().createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, behaviourBitfield));
BitmessageAddress result = new BitmessageAddress(privateKey);
if (!result.getAddress().equals(address)) {
@@ -126,11 +128,17 @@ public class Factory {
return result;
}
- public static BitmessageAddress generatePrivateAddress(boolean shorter, long stream, Pubkey.Feature... features) {
+ public static BitmessageAddress generatePrivateAddress(boolean shorter,
+ long stream,
+ Pubkey.Feature... features) {
return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features));
}
- static ObjectPayload getObjectPayload(long objectType, long version, long streamNumber, InputStream stream, int length) throws IOException {
+ static ObjectPayload getObjectPayload(long objectType,
+ long version,
+ long streamNumber,
+ InputStream stream,
+ int length) throws IOException {
ObjectType type = ObjectType.fromNumber(objectType);
if (type != null) {
switch (type) {
diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
index 57d240d..7e48746 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
@@ -22,9 +22,9 @@ import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.NodeException;
+import ch.dissem.bitmessage.ports.Security;
import ch.dissem.bitmessage.utils.AccessCounter;
import ch.dissem.bitmessage.utils.Decode;
-import ch.dissem.bitmessage.utils.Security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,6 +33,7 @@ import java.io.IOException;
import java.io.InputStream;
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
+import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Creates protocol v3 network messages from {@link InputStream InputStreams}
@@ -174,7 +175,7 @@ class V3MessageFactory {
}
private static boolean testChecksum(byte[] checksum, byte[] payload) {
- byte[] payloadChecksum = Security.sha512(payload);
+ byte[] payloadChecksum = security().sha512(payload);
for (int i = 0; i < checksum.length; i++) {
if (checksum[i] != payloadChecksum[i]) {
return false;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java b/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java
new file mode 100644
index 0000000..00ed1f2
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.ports;
+
+import ch.dissem.bitmessage.InternalContext;
+import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
+import ch.dissem.bitmessage.factory.Factory;
+import ch.dissem.bitmessage.utils.Bytes;
+import ch.dissem.bitmessage.utils.UnixTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+
+/**
+ * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
+ */
+public abstract class AbstractSecurity implements Security, InternalContext.ContextHolder {
+ public static final Logger LOG = LoggerFactory.getLogger(Security.class);
+ private static final SecureRandom RANDOM = new SecureRandom();
+ private static final BigInteger TWO = BigInteger.valueOf(2);
+
+ private final String provider;
+ private InternalContext context;
+
+ protected AbstractSecurity(String provider) {
+ this.provider = provider;
+ }
+
+ @Override
+ public void setContext(InternalContext context) {
+ this.context = context;
+ }
+
+ public byte[] sha512(byte[]... data) {
+ return hash("SHA-512", data);
+ }
+
+ public byte[] doubleSha512(byte[]... data) {
+ MessageDigest mda = md("SHA-512");
+ for (byte[] d : data) {
+ mda.update(d);
+ }
+ return mda.digest(mda.digest());
+ }
+
+ public byte[] doubleSha512(byte[] data, int length) {
+ MessageDigest mda = md("SHA-512");
+ mda.update(data, 0, length);
+ return mda.digest(mda.digest());
+ }
+
+ public byte[] ripemd160(byte[]... data) {
+ return hash("RIPEMD160", data);
+ }
+
+ public byte[] doubleSha256(byte[] data, int length) {
+ MessageDigest mda = md("SHA-256");
+ mda.update(data, 0, length);
+ return mda.digest(mda.digest());
+ }
+
+ public byte[] sha1(byte[]... data) {
+ return hash("SHA-1", data);
+ }
+
+ public byte[] randomBytes(int length) {
+ byte[] result = new byte[length];
+ RANDOM.nextBytes(result);
+ return result;
+ }
+
+ public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
+ long extraBytes) {
+ try {
+ if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000;
+ if (extraBytes < 1000) extraBytes = 1000;
+
+ byte[] initialHash = getInitialHash(object);
+
+ byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
+
+ byte[] nonce = context.getProofOfWorkEngine().calculateNonce(initialHash, target);
+ object.setNonce(nonce);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
+ throws IOException {
+ byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
+ byte[] value = doubleSha512(object.getNonce(), getInitialHash(object));
+ if (Bytes.lt(target, value, 8)) {
+ throw new InsufficientProofOfWorkException(target, value);
+ }
+ }
+
+ private byte[] getInitialHash(ObjectMessage object) throws IOException {
+ return sha512(object.getPayloadBytesWithoutNonce());
+ }
+
+ private byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) throws IOException {
+ BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
+ LOG.debug("TTL: " + TTL + "s");
+ BigInteger numerator = TWO.pow(64);
+ BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
+ BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte).multiply(powLength.add(powLength.multiply(TTL).divide(BigInteger.valueOf(2).pow(16))));
+ return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
+ }
+
+ private byte[] hash(String algorithm, byte[]... data) {
+ MessageDigest mda = md(algorithm);
+ for (byte[] d : data) {
+ mda.update(d);
+ }
+ return mda.digest();
+ }
+
+ private MessageDigest md(String algorithm) {
+ try {
+ return MessageDigest.getInstance(algorithm, provider);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public byte[] mac(byte[] key_m, byte[] data) {
+ try {
+ Mac mac = Mac.getInstance("HmacSHA256", provider);
+ mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
+ return mac.doFinal(data);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
+ long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
+ return Factory.createPubkey(version, stream,
+ createPublicKey(privateSigningKey),
+ createPublicKey(privateEncryptionKey),
+ nonceTrialsPerByte, extraBytes, features);
+ }
+
+ public BigInteger keyToBigInt(byte[] privateKey) {
+ return new BigInteger(1, privateKey);
+ }
+
+ public long randomNonce() {
+ return RANDOM.nextLong();
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
index ed2f38a..4b73629 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
@@ -34,6 +34,8 @@ public interface NetworkHandler {
Property getNetworkStatus();
+ boolean isRunning();
+
interface MessageListener {
void receive(ObjectMessage object) throws IOException;
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java b/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java
new file mode 100644
index 0000000..83ad43d
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.ports;
+
+import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+
+/**
+ * Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
+ * which should be secure enough.
+ */
+public interface Security {
+ /**
+ * A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
+ * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
+ * success on the same thread.
+ *
+ * @param data to get hashed
+ * @return SHA-512 hash of data
+ */
+ byte[] sha512(byte[]... data);
+
+ /**
+ * A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
+ * at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
+ * success on the same thread.
+ *
+ * @param data to get hashed
+ * @return SHA-512 hash of data
+ */
+ byte[] doubleSha512(byte[]... data);
+
+ /**
+ * A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
+ * to use for the hash calculation.
+ *
+ * Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
+ * shouldn't use this if you need to do many hash calculations in short order on the same thread.
+ *
+ *
+ * @param data to get hashed
+ * @param length number of bytes to be taken into account
+ * @return SHA-512 hash of data
+ */
+ byte[] doubleSha512(byte[] data, int length);
+
+ /**
+ * A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
+ * concatenation of all arrays, but might perform better.
+ *
+ * Please note that a new {@link MessageDigest} object is created at
+ * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
+ * order on the same thread.
+ *
+ *
+ * @param data to get hashed
+ * @return RIPEMD-160 hash of data
+ */
+ byte[] ripemd160(byte[]... data);
+
+ /**
+ * A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
+ * to use for the hash calculation.
+ *
+ * Please note that a new {@link MessageDigest} object is created at
+ * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
+ * order on the same thread.
+ *
+ *
+ * @param data to get hashed
+ * @param length number of bytes to be taken into account
+ * @return SHA-256 hash of data
+ */
+ byte[] doubleSha256(byte[] data, int length);
+
+ /**
+ * A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
+ * concatenation of all arrays, but might perform better.
+ *
+ * Please note that a new {@link MessageDigest} object is created at
+ * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
+ * order on the same thread.
+ *
+ *
+ * @param data to get hashed
+ * @return SHA hash of data
+ */
+ byte[] sha1(byte[]... data);
+
+ /**
+ * @param length number of bytes to return
+ * @return an array of the given size containing random bytes
+ */
+ byte[] randomBytes(int length);
+
+ /**
+ * Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
+ * live.
+ *
+ * @param object to do the proof of work for
+ * @param nonceTrialsPerByte difficulty
+ * @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
+ */
+ void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
+ long extraBytes);
+
+ /**
+ * @param object to be checked
+ * @param nonceTrialsPerByte difficulty
+ * @param extraBytes bytes to add to the object size
+ * @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
+ */
+ void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
+ throws IOException;
+
+ /**
+ * Calculates the MAC for a message (data)
+ *
+ * @param key_m the symmetric key used
+ * @param data the message data to calculate the MAC for
+ * @return the MAC
+ */
+ byte[] mac(byte[] key_m, byte[] data);
+
+ /**
+ *
+ * @param encrypt if true, encrypts data, otherwise tries to decrypt it.
+ * @param data
+ * @param key_e
+ * @return
+ */
+ byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector);
+
+ /**
+ * Create a new public key fom given private keys.
+ *
+ * @param version of the public key / address
+ * @param stream of the address
+ * @param privateSigningKey private key used for signing
+ * @param privateEncryptionKey private key used for encryption
+ * @param nonceTrialsPerByte proof of work difficulty
+ * @param extraBytes bytes to add for the proof of work (make it harder for small messages)
+ * @param features of the address
+ * @return a public key object
+ */
+ Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
+ long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features);
+
+ /**
+ * @param privateKey private key as byte array
+ * @return a public key corresponding to the given private key
+ */
+ byte[] createPublicKey(byte[] privateKey);
+
+ /**
+ * @param privateKey private key as byte array
+ * @return a big integer representation (unsigned) of the given bytes
+ */
+ BigInteger keyToBigInt(byte[] privateKey);
+
+ /**
+ * @param data to check
+ * @param signature the signature of the message
+ * @param pubkey the sender's public key
+ * @return true if the signature is valid, false otherwise
+ */
+ boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey);
+
+ /**
+ * Calculate the signature of data, using the given private key.
+ *
+ * @param data to be signed
+ * @param privateKey to be used for signing
+ * @return the signature
+ */
+ byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey);
+
+ /**
+ * @return a random number of type long
+ */
+ long randomNonce();
+
+ byte[] multiply(byte[] k, byte[] r);
+
+ byte[] createPoint(byte[] x, byte[] y);
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java b/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java
index 0d9d392..211df53 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java
@@ -23,7 +23,7 @@ import java.security.MessageDigest;
import static ch.dissem.bitmessage.utils.Bytes.inc;
/**
- * Created by chris on 14.04.15.
+ * You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one.
*/
public class SimplePOWEngine implements ProofOfWorkEngine {
@Override
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Points.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Points.java
new file mode 100644
index 0000000..937fe20
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Points.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.utils;
+
+import java.util.Arrays;
+
+/**
+ * Created by chris on 20.07.15.
+ */
+public class Points {
+ public static byte[] getX(byte[] P) {
+ return Arrays.copyOfRange(P, 1, ((P.length - 1) / 2) + 1);
+ }
+
+ public static byte[] getY(byte[] P) {
+ return Arrays.copyOfRange(P, ((P.length - 1) / 2) + 1, P.length);
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java
index 1030513..6fa0ec4 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java
@@ -17,7 +17,12 @@
package ch.dissem.bitmessage.utils;
/**
- * Created by chris on 14.06.15.
+ * Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now
+ * used to contain different status information. It is by default displayed in some JSON inspired human readable
+ * notation, but you might only want to rely on the 'human readable' part.
+ *
+ * If you need a real JSON representation, please add a method toJson().
+ *
*/
public class Property {
private String name;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java
deleted file mode 100644
index 25e248e..0000000
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.utils;
-
-import ch.dissem.bitmessage.entity.ObjectMessage;
-import ch.dissem.bitmessage.entity.payload.Pubkey;
-import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
-import ch.dissem.bitmessage.factory.Factory;
-import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
-import org.bouncycastle.asn1.x9.X9ECParameters;
-import org.bouncycastle.crypto.ec.CustomNamedCurves;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.jce.spec.ECPrivateKeySpec;
-import org.bouncycastle.jce.spec.ECPublicKeySpec;
-import org.bouncycastle.math.ec.ECPoint;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.*;
-import java.security.spec.KeySpec;
-import java.util.Arrays;
-
-/**
- * Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
- * which should be secure enough.
- */
-public class Security {
- public static final Logger LOG = LoggerFactory.getLogger(Security.class);
- private static final SecureRandom RANDOM = new SecureRandom();
- private static final BigInteger TWO = BigInteger.valueOf(2);
- private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
-
- static {
- java.security.Security.addProvider(new BouncyCastleProvider());
- }
-
- /**
- * A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
- * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
- * success on the same thread.
- *
- * @param data to get hashed
- * @return SHA-512 hash of data
- */
- public static byte[] sha512(byte[]... data) {
- return hash("SHA-512", data);
- }
-
- /**
- * A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
- * at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
- * success on the same thread.
- *
- * @param data to get hashed
- * @return SHA-512 hash of data
- */
- public static byte[] doubleSha512(byte[]... data) {
- MessageDigest mda = md("SHA-512");
- for (byte[] d : data) {
- mda.update(d);
- }
- return mda.digest(mda.digest());
- }
-
- /**
- * A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
- * to use for the hash calculation.
- *
- * Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
- * shouldn't use this if you need to do many hash calculations in short order on the same thread.
- *
- *
- * @param data to get hashed
- * @param length number of bytes to be taken into account
- * @return SHA-512 hash of data
- */
- public static byte[] doubleSha512(byte[] data, int length) {
- MessageDigest mda = md("SHA-512");
- mda.update(data, 0, length);
- return mda.digest(mda.digest());
- }
-
-
- /**
- * A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
- * concatenation of all arrays, but might perform better.
- *
- * Please note that a new {@link MessageDigest} object is created at
- * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
- * order on the same thread.
- *
- *
- * @param data to get hashed
- * @return RIPEMD-160 hash of data
- */
- public static byte[] ripemd160(byte[]... data) {
- return hash("RIPEMD160", data);
- }
-
- /**
- * A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
- * to use for the hash calculation.
- *
- * Please note that a new {@link MessageDigest} object is created at
- * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
- * order on the same thread.
- *
- *
- * @param data to get hashed
- * @param length number of bytes to be taken into account
- * @return SHA-256 hash of data
- */
- public static byte[] doubleSha256(byte[] data, int length) {
- MessageDigest mda = md("SHA-256");
- mda.update(data, 0, length);
- return mda.digest(mda.digest());
- }
-
- /**
- * A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
- * concatenation of all arrays, but might perform better.
- *
- * Please note that a new {@link MessageDigest} object is created at
- * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
- * order on the same thread.
- *
- *
- * @param data to get hashed
- * @return SHA hash of data
- */
- public static byte[] sha1(byte[]... data) {
- return hash("SHA-1", data);
- }
-
- /**
- * @param length number of bytes to return
- * @return an array of the given size containing random bytes
- */
- public static byte[] randomBytes(int length) {
- byte[] result = new byte[length];
- RANDOM.nextBytes(result);
- return result;
- }
-
- /**
- * Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
- * live.
- *
- * @param object to do the proof of work for
- * @param worker doing the actual proof of work
- * @param nonceTrialsPerByte difficulty
- * @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
- */
- public static void doProofOfWork(ObjectMessage object, ProofOfWorkEngine worker, long nonceTrialsPerByte,
- long extraBytes) {
- try {
- if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000;
- if (extraBytes < 1000) extraBytes = 1000;
-
- byte[] initialHash = getInitialHash(object);
-
- byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
-
- byte[] nonce = worker.calculateNonce(initialHash, target);
- object.setNonce(nonce);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * @param object to be checked
- * @param nonceTrialsPerByte difficulty
- * @param extraBytes bytes to add to the object size
- * @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
- */
- public static void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
- throws IOException {
- byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
- byte[] value = Security.doubleSha512(object.getNonce(), getInitialHash(object));
- if (Bytes.lt(target, value, 8)) {
- throw new InsufficientProofOfWorkException(target, value);
- }
- }
-
- private static byte[] getInitialHash(ObjectMessage object) throws IOException {
- return Security.sha512(object.getPayloadBytesWithoutNonce());
- }
-
- private static byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) throws IOException {
- BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
- LOG.debug("TTL: " + TTL + "s");
- BigInteger numerator = TWO.pow(64);
- BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
- BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte).multiply(powLength.add(powLength.multiply(TTL).divide(BigInteger.valueOf(2).pow(16))));
- return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
- }
-
- private static byte[] hash(String algorithm, byte[]... data) {
- MessageDigest mda = md(algorithm);
- for (byte[] d : data) {
- mda.update(d);
- }
- return mda.digest();
- }
-
- private static MessageDigest md(String algorithm) {
- try {
- return MessageDigest.getInstance(algorithm, "BC");
- } catch (GeneralSecurityException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Calculates the MAC for a message (data)
- *
- * @param key_m the symmetric key used
- * @param data the message data to calculate the MAC for
- * @return the MAC
- */
- public static byte[] mac(byte[] key_m, byte[] data) {
- try {
- Mac mac = Mac.getInstance("HmacSHA256", "BC");
- mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
- return mac.doFinal(data);
- } catch (GeneralSecurityException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Create a new public key fom given private keys.
- *
- * @param version of the public key / address
- * @param stream of the address
- * @param privateSigningKey private key used for signing
- * @param privateEncryptionKey private key used for encryption
- * @param nonceTrialsPerByte proof of work difficulty
- * @param extraBytes bytes to add for the proof of work (make it harder for small messages)
- * @param features of the address
- * @return a public key object
- */
- public static Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
- long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
- return Factory.createPubkey(version, stream,
- createPublicKey(privateSigningKey).getEncoded(false),
- createPublicKey(privateEncryptionKey).getEncoded(false),
- nonceTrialsPerByte, extraBytes, features);
- }
-
- /**
- * @param privateKey private key as byte array
- * @return a public key corresponding to the given private key
- */
- public static ECPoint createPublicKey(byte[] privateKey) {
- return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize();
- }
-
- /**
- * @param privateKey private key as byte array
- * @return a big integer representation (unsigned) of the given bytes
- */
- public static BigInteger keyToBigInt(byte[] privateKey) {
- return new BigInteger(1, privateKey);
- }
-
- public static ECPoint keyToPoint(byte[] publicKey) {
- BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33));
- BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65));
- return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y);
- }
-
- public static ECPoint createPoint(byte[] x, byte[] y) {
- return EC_CURVE_PARAMETERS.getCurve().createPoint(
- new BigInteger(1, x),
- new BigInteger(1, y)
- );
- }
-
- /**
- * @param data to check
- * @param signature the signature of the message
- * @param pubkey the sender's public key
- * @return true if the signature is valid, false otherwise
- */
- public static boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) {
- try {
- ECParameterSpec spec = new ECParameterSpec(
- EC_CURVE_PARAMETERS.getCurve(),
- EC_CURVE_PARAMETERS.getG(),
- EC_CURVE_PARAMETERS.getN(),
- EC_CURVE_PARAMETERS.getH(),
- EC_CURVE_PARAMETERS.getSeed()
- );
-
- ECPoint Q = keyToPoint(pubkey.getSigningKey());
- KeySpec keySpec = new ECPublicKeySpec(Q, spec);
- PublicKey publicKey = KeyFactory.getInstance("ECDSA", "BC").generatePublic(keySpec);
-
- Signature sig = Signature.getInstance("ECDSA", "BC");
- sig.initVerify(publicKey);
- sig.update(data);
- return sig.verify(signature);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Calculate the signature of data, using the given private key.
- *
- * @param data to be signed
- * @param privateKey to be used for signing
- * @return the signature
- */
- public static byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey) {
- try {
- ECParameterSpec spec = new ECParameterSpec(
- EC_CURVE_PARAMETERS.getCurve(),
- EC_CURVE_PARAMETERS.getG(),
- EC_CURVE_PARAMETERS.getN(),
- EC_CURVE_PARAMETERS.getH(),
- EC_CURVE_PARAMETERS.getSeed()
- );
-
- BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey());
- KeySpec keySpec = new ECPrivateKeySpec(d, spec);
- PrivateKey privKey = KeyFactory.getInstance("ECDSA", "BC").generatePrivate(keySpec);
-
- Signature sig = Signature.getInstance("ECDSA", "BC");
- sig.initSign(privKey);
- sig.update(data);
- return sig.sign();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * @return a random number of type long
- */
- public static long randomNonce() {
- return RANDOM.nextLong();
- }
-}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Singleton.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Singleton.java
new file mode 100644
index 0000000..169db77
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Singleton.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.utils;
+
+import ch.dissem.bitmessage.ports.Security;
+
+/**
+ * Created by chris on 20.07.15.
+ */
+public class Singleton {
+ private static Security security;
+
+ public static void initialize(Security security) {
+ Singleton.security = security;
+ }
+
+ public static Security security() {
+ return security;
+ }
+}
diff --git a/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java b/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java
index ebb3707..21f9506 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java
@@ -21,6 +21,7 @@ import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.V4Broadcast;
import ch.dissem.bitmessage.entity.payload.V5Broadcast;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
+import ch.dissem.bitmessage.utils.TestBase;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
@@ -29,7 +30,7 @@ import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-public class DecryptionTest {
+public class DecryptionTest extends TestBase {
@Test
public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
diff --git a/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java b/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
index f0f8ddb..9a24842 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
@@ -24,20 +24,20 @@ import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
-import ch.dissem.bitmessage.utils.Security;
+import ch.dissem.bitmessage.utils.TestBase;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
import java.io.IOException;
+import static ch.dissem.bitmessage.utils.Singleton.security;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-public class EncryptionTest {
+public class EncryptionTest extends TestBase {
@Test
public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException {
- GenericPayload before = new GenericPayload(0, 1, Security.randomBytes(100));
+ GenericPayload before = new GenericPayload(0, 1, security().randomBytes(100));
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey());
diff --git a/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java b/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java
index dc2eb85..71b7d2a 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java
@@ -25,6 +25,7 @@ import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
+import ch.dissem.bitmessage.utils.TestBase;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
@@ -33,7 +34,7 @@ import java.util.Date;
import static org.junit.Assert.*;
-public class SignatureTest {
+public class SignatureTest extends TestBase {
@Test
public void ensureValidationWorks() throws IOException {
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");
diff --git a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java
index 85b8098..e1fcc7f 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java
@@ -27,6 +27,7 @@ import java.io.IOException;
import java.util.Arrays;
import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
+import static ch.dissem.bitmessage.utils.Singleton.security;
import static org.junit.Assert.*;
public class BitmessageAddressTest {
@@ -102,7 +103,7 @@ public class BitmessageAddressTest {
System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n");
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
- Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
+ security().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
assertEquals(address_string, address.getAddress());
}
@@ -119,7 +120,7 @@ public class BitmessageAddressTest {
if (bytes.length != 37)
throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
- byte[] hash = Security.doubleSha256(bytes, 33);
+ byte[] hash = security().doubleSha256(bytes, 33);
for (int i = 0; i < 4; i++) {
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
}
@@ -132,7 +133,7 @@ public class BitmessageAddressTest {
byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU");
byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz");
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
- Security.createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
+ security().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress());
}
diff --git a/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java b/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
index aed5722..ca17a23 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
@@ -17,14 +17,11 @@
package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.utils.Bytes;
-import ch.dissem.bitmessage.utils.Security;
import org.junit.Test;
+import static ch.dissem.bitmessage.utils.Singleton.security;
import static org.junit.Assert.assertTrue;
-/**
- * Created by chris on 17.04.15.
- */
public class ProofOfWorkEngineTest {
@Test
public void testSimplePOWEngine() {
@@ -38,11 +35,11 @@ public class ProofOfWorkEngineTest {
private void testPOW(ProofOfWorkEngine engine) {
long time = System.currentTimeMillis();
- byte[] initialHash = Security.sha512(new byte[]{1, 3, 6, 4});
+ byte[] initialHash = security().sha512(new byte[]{1, 3, 6, 4});
byte[] target = {0, 0, -1, -1, -1, -1, -1, -1};
byte[] nonce = engine.calculateNonce(initialHash, target);
System.out.println("Calculating nonce took " + (System.currentTimeMillis() - time) + "ms");
- assertTrue(Bytes.lt(Security.doubleSha512(nonce, initialHash), target, 8));
+ assertTrue(Bytes.lt(security().doubleSha512(nonce, initialHash), target, 8));
}
}
diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
index 7c83724..52eb106 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
@@ -25,9 +25,6 @@ import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-/**
- * Created by chris on 10.04.15.
- */
public class BytesTest {
public static final Random rnd = new Random();
diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java
index 8c18ee7..60d882f 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java
@@ -22,9 +22,6 @@ import java.io.*;
import static org.junit.Assert.assertEquals;
-/**
- * Created by chris on 20.03.15.
- */
public class DecodeTest {
@Test
public void ensureDecodingWorks() throws Exception {
diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java
index 3489112..aaba5bf 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java
@@ -23,9 +23,6 @@ import java.io.IOException;
import static org.junit.Assert.assertEquals;
-/**
- * Created by chris on 13.03.15.
- */
public class EncodeTest {
@Test
public void testUint8() throws IOException {
diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/TestBase.java b/domain/src/test/java/ch/dissem/bitmessage/utils/TestBase.java
new file mode 100644
index 0000000..1dd1335
--- /dev/null
+++ b/domain/src/test/java/ch/dissem/bitmessage/utils/TestBase.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.utils;
+
+import ch.dissem.bitmessage.security.bc.BouncySecurity;
+
+/**
+ * Created by chris on 20.07.15.
+ */
+public class TestBase {
+ static {
+ Singleton.initialize(new BouncySecurity());
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f2e98f0..66e6c70 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
index 0eb0f09..d8054ff 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -26,7 +26,6 @@ import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener;
import ch.dissem.bitmessage.utils.DebugUtils;
-import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,6 +43,7 @@ import java.util.concurrent.ConcurrentMap;
import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT;
import static ch.dissem.bitmessage.networking.Connection.State.*;
+import static ch.dissem.bitmessage.utils.Singleton.security;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
/**
@@ -275,7 +275,7 @@ public class Connection implements Runnable {
ObjectMessage objectMessage = (ObjectMessage) messagePayload;
try {
LOG.debug("Received object " + objectMessage.getInventoryVector());
- Security.checkProofOfWork(objectMessage, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
+ security().checkProofOfWork(objectMessage, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
listener.receive(objectMessage);
ctx.getInventory().storeObject(objectMessage);
// offer object to some random nodes so it gets distributed throughout the network:
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
similarity index 96%
rename from networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java
rename to networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index f27f7ff..558d097 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -44,9 +44,9 @@ import static ch.dissem.bitmessage.utils.DebugUtils.inc;
/**
* Handles all the networky stuff.
*/
-public class NetworkNode implements NetworkHandler, ContextHolder {
+public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
public final static int NETWORK_MAGIC_NUMBER = 8;
- private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class);
+ private final static Logger LOG = LoggerFactory.getLogger(DefaultNetworkHandler.class);
private final ExecutorService pool;
private final List connections = new LinkedList<>();
private InternalContext ctx;
@@ -56,7 +56,7 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
private ConcurrentMap requestedObjects = new ConcurrentHashMap<>();
- public NetworkNode() {
+ public DefaultNetworkHandler() {
pool = Executors.newCachedThreadPool();
}
@@ -128,6 +128,11 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
}
}
+ @Override
+ public boolean isRunning() {
+ return connectionManager != null && connectionManager.isAlive();
+ }
+
@Override
public void stop() {
connectionManager.interrupt();
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkNodeTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java
similarity index 86%
rename from networking/src/test/java/ch/dissem/bitmessage/networking/NetworkNodeTest.java
rename to networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java
index 6819311..3bec993 100644
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkNodeTest.java
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java
@@ -24,16 +24,21 @@ import org.junit.Ignore;
import org.junit.Test;
/**
- * Created by chris on 20.03.15.
+ * FIXME: there really should be sensible tests for the network handler
*/
-public class NetworkNodeTest {
+public class DefaultNetworkHandlerTest {
private NetworkAddress localhost = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(8444).build();
+ // void start(MessageListener listener);
+ // void stop();
+ // void offer(InventoryVector iv);
+ // Property getNetworkStatus();
+
@Ignore
@Test(expected = InterruptedException.class)
public void testSendMessage() throws Exception {
final Thread baseThread = Thread.currentThread();
- NetworkNode net = new NetworkNode();
+ DefaultNetworkHandler net = new DefaultNetworkHandler();
// net.setListener(localhost, new NetworkHandler.MessageListener() {
// @Override
// public void receive(ObjectPayload payload) {
diff --git a/repositories/build.gradle b/repositories/build.gradle
index 2f467b8..76509fd 100644
--- a/repositories/build.gradle
+++ b/repositories/build.gradle
@@ -12,7 +12,9 @@ uploadArchives {
dependencies {
compile project(':domain')
- compile 'com.h2database:h2:1.4.187'
compile 'org.flywaydb:flyway-core:3.2.1'
testCompile 'junit:junit:4.11'
+ testCompile 'com.h2database:h2:1.4.187'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(':security-bc')
}
\ No newline at end of file
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java
index 80e079b..7448b19 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java
@@ -28,9 +28,9 @@ import java.sql.SQLException;
*/
public class JdbcConfig {
protected final Flyway flyway;
- private final String dbUrl;
- private final String dbUser;
- private final String dbPassword;
+ protected final String dbUrl;
+ protected final String dbUser;
+ protected final String dbPassword;
public JdbcConfig(String dbUrl, String dbUser, String dbPassword) {
this.dbUrl = dbUrl;
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
index 8795388..4fbea30 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
@@ -18,14 +18,15 @@ package ch.dissem.bitmessage.repository;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.payload.ObjectType;
-import org.flywaydb.core.Flyway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.sql.*;
+import java.sql.Blob;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
import static ch.dissem.bitmessage.utils.Strings.hex;
@@ -81,8 +82,8 @@ abstract class JdbcHelper {
if (data != null) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
data.write(os);
- ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
- ps.setBlob(parameterIndex, is);
+ byte[] bytes = os.toByteArray();
+ ps.setBinaryStream(parameterIndex, new ByteArrayInputStream(bytes), bytes.length);
} else {
ps.setBlob(parameterIndex, (Blob) null);
}
diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java
index 80740ca..18fc83c 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java
@@ -25,7 +25,7 @@ import java.util.List;
import static org.junit.Assert.*;
-public class JdbcAddressRepositoryTest {
+public class JdbcAddressRepositoryTest extends TestBase {
public static final String CONTACT_A = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt";
public static final String CONTACT_B = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj";
public static final String CONTACT_C = "BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke";
diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java
index 8b23566..3954766 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java
@@ -34,7 +34,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
import static ch.dissem.bitmessage.utils.UnixTime.now;
import static org.junit.Assert.*;
-public class JdbcInventoryTest {
+public class JdbcInventoryTest extends TestBase {
private TestJdbcConfig config;
private Inventory inventory;
diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
index b99e44f..3dceb24 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
@@ -25,7 +25,6 @@ import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.MessageRepository;
-import ch.dissem.bitmessage.utils.Security;
import org.junit.Before;
import org.junit.Test;
@@ -33,10 +32,11 @@ import java.util.Arrays;
import java.util.List;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
+import static ch.dissem.bitmessage.utils.Singleton.security;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-public class JdbcMessageRepositoryTest {
+public class JdbcMessageRepositoryTest extends TestBase {
private BitmessageAddress contactA;
private BitmessageAddress contactB;
private BitmessageAddress identity;
@@ -54,7 +54,11 @@ public class JdbcMessageRepositoryTest {
config.reset();
addressRepo = new JdbcAddressRepository(config);
repo = new JdbcMessageRepository(config);
- new InternalContext(new BitmessageContext.Builder().addressRepo(addressRepo).messageRepo(repo));
+ new InternalContext(new BitmessageContext.Builder()
+ .security(security())
+ .addressRepo(addressRepo)
+ .messageRepo(repo)
+ );
BitmessageAddress tmp = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000));
contactA = new BitmessageAddress(tmp.getAddress());
@@ -120,7 +124,7 @@ public class JdbcMessageRepositoryTest {
@Test
public void testSave() throws Exception {
Plaintext message = new Plaintext.Builder(MSG)
- .IV(new InventoryVector(Security.randomBytes(32)))
+ .IV(new InventoryVector(security().randomBytes(32)))
.from(identity)
.to(contactA)
.message("Subject", "Message")
@@ -143,7 +147,7 @@ public class JdbcMessageRepositoryTest {
public void testUpdate() throws Exception {
List messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
Plaintext message = messages.get(0);
- message.setInventoryVector(new InventoryVector(Security.randomBytes(32)));
+ message.setInventoryVector(new InventoryVector(security().randomBytes(32)));
repo.save(message);
messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java
index d668822..ca4bcd7 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java
@@ -27,7 +27,7 @@ import java.util.List;
import static ch.dissem.bitmessage.utils.UnixTime.now;
import static org.junit.Assert.assertEquals;
-public class JdbcNodeRegistryTest {
+public class JdbcNodeRegistryTest extends TestBase {
private TestJdbcConfig config;
private NodeRegistry registry;
diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java
new file mode 100644
index 0000000..c6baaa2
--- /dev/null
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.repository;
+
+import ch.dissem.bitmessage.InternalContext;
+import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
+import ch.dissem.bitmessage.security.bc.BouncySecurity;
+import ch.dissem.bitmessage.utils.Singleton;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Created by chris on 20.07.15.
+ */
+public class TestBase {
+ static {
+ BouncySecurity security = new BouncySecurity();
+ Singleton.initialize(security);
+ InternalContext ctx = mock(InternalContext.class);
+ when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine());
+ security.setContext(ctx);
+ }
+}
diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java
index 909ba75..99a26c0 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java
@@ -17,7 +17,8 @@
package ch.dissem.bitmessage.repository;
/**
- * Created by chris on 02.06.15.
+ * JdbcConfig to be used for tests. Uses an in-memory database and adds a useful {@link #reset()} method resetting
+ * the database.
*/
public class TestJdbcConfig extends JdbcConfig {
public TestJdbcConfig() {
diff --git a/security-bc/build.gradle b/security-bc/build.gradle
new file mode 100644
index 0000000..48f14d1
--- /dev/null
+++ b/security-bc/build.gradle
@@ -0,0 +1,18 @@
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.project {
+ name 'Jabit Spongy Security'
+ artifactId = 'jabit-security-spongy'
+ description 'The Security implementation using spongy castle (needed for Android)'
+ }
+ }
+ }
+}
+
+dependencies {
+ compile project(':domain')
+ compile 'org.bouncycastle:bcprov-jdk15on:1.52'
+ testCompile 'junit:junit:4.11'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+}
diff --git a/security-bc/src/main/java/ch/dissem/bitmessage/security/bc/BouncySecurity.java b/security-bc/src/main/java/ch/dissem/bitmessage/security/bc/BouncySecurity.java
new file mode 100644
index 0000000..a125049
--- /dev/null
+++ b/security-bc/src/main/java/ch/dissem/bitmessage/security/bc/BouncySecurity.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.security.bc;
+
+import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
+import ch.dissem.bitmessage.ports.AbstractSecurity;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.ec.CustomNamedCurves;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.paddings.PKCS7Padding;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECPoint;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+
+/**
+ * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google),
+ * this is the Bouncycastle implementation.
+ */
+public class BouncySecurity extends AbstractSecurity {
+ private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
+
+ static {
+ java.security.Security.addProvider(new BouncyCastleProvider());
+ }
+
+ public BouncySecurity() {
+ super("BC");
+ }
+
+ @Override
+ public byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector) {
+ BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
+
+ CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
+
+ cipher.init(encrypt, params);
+
+ byte[] buffer = new byte[cipher.getOutputSize(data.length)];
+ int length = cipher.processBytes(data, 0, data.length, buffer, 0);
+ try {
+ length += cipher.doFinal(buffer, length);
+ } catch (InvalidCipherTextException e) {
+ throw new IllegalArgumentException(e);
+ }
+ if (length < buffer.length) {
+ return Arrays.copyOfRange(buffer, 0, length);
+ }
+ return buffer;
+ }
+
+ @Override
+ public byte[] createPublicKey(byte[] privateKey) {
+ return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize().getEncoded(false);
+ }
+
+ private ECPoint keyToPoint(byte[] publicKey) {
+ BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33));
+ BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65));
+ return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y);
+ }
+
+ @Override
+ public boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) {
+ try {
+ ECParameterSpec spec = new ECParameterSpec(
+ EC_CURVE_PARAMETERS.getCurve(),
+ EC_CURVE_PARAMETERS.getG(),
+ EC_CURVE_PARAMETERS.getN(),
+ EC_CURVE_PARAMETERS.getH(),
+ EC_CURVE_PARAMETERS.getSeed()
+ );
+
+ ECPoint Q = keyToPoint(pubkey.getSigningKey());
+ KeySpec keySpec = new ECPublicKeySpec(Q, spec);
+ PublicKey publicKey = KeyFactory.getInstance("ECDSA", "BC").generatePublic(keySpec);
+
+ Signature sig = Signature.getInstance("ECDSA", "BC");
+ sig.initVerify(publicKey);
+ sig.update(data);
+ return sig.verify(signature);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public byte[] getSignature(byte[] data, PrivateKey privateKey) {
+ try {
+ ECParameterSpec spec = new ECParameterSpec(
+ EC_CURVE_PARAMETERS.getCurve(),
+ EC_CURVE_PARAMETERS.getG(),
+ EC_CURVE_PARAMETERS.getN(),
+ EC_CURVE_PARAMETERS.getH(),
+ EC_CURVE_PARAMETERS.getSeed()
+ );
+
+ BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey());
+ KeySpec keySpec = new ECPrivateKeySpec(d, spec);
+ java.security.PrivateKey privKey = KeyFactory.getInstance("ECDSA", "BC").generatePrivate(keySpec);
+
+ Signature sig = Signature.getInstance("ECDSA", "BC");
+ sig.initSign(privKey);
+ sig.update(data);
+ return sig.sign();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public byte[] multiply(byte[] K, byte[] r) {
+ return keyToPoint(K).multiply(keyToBigInt(r)).normalize().getEncoded(false);
+ }
+
+ @Override
+ public byte[] createPoint(byte[] x, byte[] y) {
+ return EC_CURVE_PARAMETERS.getCurve().createPoint(
+ new BigInteger(1, x),
+ new BigInteger(1, y)
+ ).getEncoded(false);
+ }
+}
diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java b/security-bc/src/test/java/ch/dissem/bitmessage/security/SecurityTest.java
similarity index 60%
rename from domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java
rename to security-bc/src/test/java/ch/dissem/bitmessage/security/SecurityTest.java
index 9b97eb1..94f0ede 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java
+++ b/security-bc/src/test/java/ch/dissem/bitmessage/security/SecurityTest.java
@@ -1,36 +1,25 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.utils;
+package ch.dissem.bitmessage.security;
+import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
+import ch.dissem.bitmessage.security.bc.BouncySecurity;
+import ch.dissem.bitmessage.utils.Singleton;
+import ch.dissem.bitmessage.utils.UnixTime;
import org.junit.Test;
import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.security.KeyPairGenerator;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
/**
- * Created by chris on 10.04.15.
+ * Created by chris on 19.07.15.
*/
public class SecurityTest {
public static final byte[] TEST_VALUE = "teststring".getBytes();
@@ -42,29 +31,39 @@ public class SecurityTest {
public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
+ "cd566972b5e50104011a92b59fa8e0b1234851ae");
+ private static BouncySecurity security;
+
+ public SecurityTest() {
+ security = new BouncySecurity();
+ Singleton.initialize(security);
+ InternalContext ctx = mock(InternalContext.class);
+ when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine());
+ security.setContext(ctx);
+ }
+
@Test
public void testRipemd160() {
- assertArrayEquals(TEST_RIPEMD160, Security.ripemd160(TEST_VALUE));
+ assertArrayEquals(TEST_RIPEMD160, security.ripemd160(TEST_VALUE));
}
@Test
public void testSha1() {
- assertArrayEquals(TEST_SHA1, Security.sha1(TEST_VALUE));
+ assertArrayEquals(TEST_SHA1, security.sha1(TEST_VALUE));
}
@Test
public void testSha512() {
- assertArrayEquals(TEST_SHA512, Security.sha512(TEST_VALUE));
+ assertArrayEquals(TEST_SHA512, security.sha512(TEST_VALUE));
}
@Test
public void testChaining() {
- assertArrayEquals(TEST_SHA512, Security.sha512("test".getBytes(), "string".getBytes()));
+ assertArrayEquals(TEST_SHA512, security.sha512("test".getBytes(), "string".getBytes()));
}
@Test
public void testDoubleHash() {
- assertArrayEquals(Security.sha512(TEST_SHA512), Security.doubleSha512(TEST_VALUE));
+ assertArrayEquals(security.sha512(TEST_SHA512), security.doubleSha512(TEST_VALUE));
}
@Test(expected = IOException.class)
@@ -75,7 +74,7 @@ public class SecurityTest {
.objectType(0)
.payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
- Security.checkProofOfWork(objectMessage, 1000, 1000);
+ security.checkProofOfWork(objectMessage, 1000, 1000);
}
@Test
@@ -86,14 +85,7 @@ public class SecurityTest {
.objectType(0)
.payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
- Security.doProofOfWork(objectMessage, new MultiThreadedPOWEngine(), 1000, 1000);
- Security.checkProofOfWork(objectMessage, 1000, 1000);
+ security.doProofOfWork(objectMessage, 1000, 1000);
+ security.checkProofOfWork(objectMessage, 1000, 1000);
}
-
- @Test
- public void testECIES() throws Exception {
- KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECIES", "BC");
-// kpg.initialize();
- kpg.generateKeyPair();
- }
-}
+}
\ No newline at end of file
diff --git a/security-sc/build.gradle b/security-sc/build.gradle
new file mode 100644
index 0000000..bfbdcab
--- /dev/null
+++ b/security-sc/build.gradle
@@ -0,0 +1,17 @@
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.project {
+ name 'Jabit Spongy Security'
+ artifactId = 'jabit-security-spongy'
+ description 'The Security implementation using spongy castle (needed for Android)'
+ }
+ }
+ }
+}
+
+dependencies {
+ compile project(':domain')
+ compile 'com.madgag.spongycastle:prov:1.52.0.0'
+ testCompile 'junit:junit:4.11'
+}
diff --git a/security-sc/src/main/java/ch/dissem/bitmessage/security/sc/SpongySecurity.java b/security-sc/src/main/java/ch/dissem/bitmessage/security/sc/SpongySecurity.java
new file mode 100644
index 0000000..70a0743
--- /dev/null
+++ b/security-sc/src/main/java/ch/dissem/bitmessage/security/sc/SpongySecurity.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.security.sc;
+
+import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
+import ch.dissem.bitmessage.ports.AbstractSecurity;
+import org.spongycastle.asn1.x9.X9ECParameters;
+import org.spongycastle.crypto.BufferedBlockCipher;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.crypto.ec.CustomNamedCurves;
+import org.spongycastle.crypto.engines.AESEngine;
+import org.spongycastle.crypto.modes.CBCBlockCipher;
+import org.spongycastle.crypto.paddings.PKCS7Padding;
+import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.crypto.params.ParametersWithIV;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.jce.spec.ECParameterSpec;
+import org.spongycastle.jce.spec.ECPrivateKeySpec;
+import org.spongycastle.jce.spec.ECPublicKeySpec;
+import org.spongycastle.math.ec.ECPoint;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+
+/**
+ * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google),
+ * this is the Spongycastle implementation.
+ */
+public class SpongySecurity extends AbstractSecurity {
+ private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
+
+ static {
+ java.security.Security.addProvider(new BouncyCastleProvider());
+ }
+
+ public SpongySecurity() {
+ super("SC");
+ }
+
+ @Override
+ public byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector) {
+ BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
+
+ CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
+
+ cipher.init(encrypt, params);
+
+ byte[] buffer = new byte[cipher.getOutputSize(data.length)];
+ int length = cipher.processBytes(data, 0, data.length, buffer, 0);
+ try {
+ length += cipher.doFinal(buffer, length);
+ } catch (InvalidCipherTextException e) {
+ throw new IllegalArgumentException(e);
+ }
+ if (length < buffer.length) {
+ return Arrays.copyOfRange(buffer, 0, length);
+ }
+ return buffer;
+ }
+
+ @Override
+ public byte[] createPublicKey(byte[] privateKey) {
+ return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize().getEncoded(false);
+ }
+
+ private ECPoint keyToPoint(byte[] publicKey) {
+ BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33));
+ BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65));
+ return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y);
+ }
+
+ @Override
+ public boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) {
+ try {
+ ECParameterSpec spec = new ECParameterSpec(
+ EC_CURVE_PARAMETERS.getCurve(),
+ EC_CURVE_PARAMETERS.getG(),
+ EC_CURVE_PARAMETERS.getN(),
+ EC_CURVE_PARAMETERS.getH(),
+ EC_CURVE_PARAMETERS.getSeed()
+ );
+
+ ECPoint Q = keyToPoint(pubkey.getSigningKey());
+ KeySpec keySpec = new ECPublicKeySpec(Q, spec);
+ PublicKey publicKey = KeyFactory.getInstance("ECDSA", "SC").generatePublic(keySpec);
+
+ Signature sig = Signature.getInstance("ECDSA", "SC");
+ sig.initVerify(publicKey);
+ sig.update(data);
+ return sig.verify(signature);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public byte[] getSignature(byte[] data, PrivateKey privateKey) {
+ try {
+ ECParameterSpec spec = new ECParameterSpec(
+ EC_CURVE_PARAMETERS.getCurve(),
+ EC_CURVE_PARAMETERS.getG(),
+ EC_CURVE_PARAMETERS.getN(),
+ EC_CURVE_PARAMETERS.getH(),
+ EC_CURVE_PARAMETERS.getSeed()
+ );
+
+ BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey());
+ KeySpec keySpec = new ECPrivateKeySpec(d, spec);
+ java.security.PrivateKey privKey = KeyFactory.getInstance("ECDSA", "SC").generatePrivate(keySpec);
+
+ Signature sig = Signature.getInstance("ECDSA", "SC");
+ sig.initSign(privKey);
+ sig.update(data);
+ return sig.sign();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public byte[] multiply(byte[] K, byte[] r) {
+ return keyToPoint(K).multiply(keyToBigInt(r)).normalize().getEncoded(false);
+ }
+
+ @Override
+ public byte[] createPoint(byte[] x, byte[] y) {
+ return EC_CURVE_PARAMETERS.getCurve().createPoint(
+ new BigInteger(1, x),
+ new BigInteger(1, y)
+ ).getEncoded(false);
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 6452ec0..2d3e1f6 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -9,3 +9,7 @@ include 'repositories'
include 'demo'
include 'wif'
+
+include 'security-sc'
+
+include 'security-bc'
diff --git a/wif/build.gradle b/wif/build.gradle
index 393928f..5b32a21 100644
--- a/wif/build.gradle
+++ b/wif/build.gradle
@@ -15,4 +15,5 @@ dependencies {
compile 'org.ini4j:ini4j:0.5.4'
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(':security-bc')
}
diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java
index 26b6d63..b41d645 100644
--- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java
+++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java
@@ -19,7 +19,6 @@ package ch.dissem.bitmessage.wif;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.utils.Base58;
-import ch.dissem.bitmessage.utils.Security;
import org.ini4j.Ini;
import org.ini4j.Profile;
@@ -27,6 +26,7 @@ import java.io.*;
import java.util.Collection;
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
+import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* @author Christian Basler
@@ -73,7 +73,7 @@ public class WifExporter {
byte[] result = new byte[37];
result[0] = (byte) 0x80;
System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE);
- byte[] hash = Security.doubleSha256(result, PRIVATE_KEY_SIZE + 1);
+ byte[] hash = security().doubleSha256(result, PRIVATE_KEY_SIZE + 1);
System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4);
return Base58.encode(result);
}
diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java
index 45bbb4d..e88eaa4 100644
--- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java
+++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java
@@ -21,7 +21,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Base58;
-import ch.dissem.bitmessage.utils.Security;
import org.ini4j.Ini;
import org.ini4j.Profile;
import org.slf4j.Logger;
@@ -34,6 +33,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
/**
* @author Christian Basler
*/
@@ -84,7 +85,7 @@ public class WifImporter {
if (bytes.length != 37)
throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
- byte[] hash = Security.doubleSha256(bytes, 33);
+ byte[] hash = security().doubleSha256(bytes, 33);
for (int i = 0; i < 4; i++) {
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
}
diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java
index 8cb9264..5ed9025 100644
--- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java
+++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java
@@ -18,6 +18,7 @@ package ch.dissem.bitmessage.wif;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.ports.*;
+import ch.dissem.bitmessage.security.bc.BouncySecurity;
import org.junit.Before;
import org.junit.Test;
@@ -34,6 +35,7 @@ public class WifExporterTest {
@Before
public void setUp() throws Exception {
ctx = new BitmessageContext.Builder()
+ .security(new BouncySecurity())
.networkHandler(mock(NetworkHandler.class))
.inventory(mock(Inventory.class))
.messageRepo(mock(MessageRepository.class))
diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java
index 2973efc..862b3e3 100644
--- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java
+++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java
@@ -19,6 +19,7 @@ package ch.dissem.bitmessage.wif;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.ports.*;
+import ch.dissem.bitmessage.security.bc.BouncySecurity;
import org.junit.Before;
import org.junit.Test;
@@ -37,6 +38,7 @@ public class WifImporterTest {
@Before
public void setUp() throws Exception {
ctx = new BitmessageContext.Builder()
+ .security(new BouncySecurity())
.networkHandler(mock(NetworkHandler.class))
.inventory(mock(Inventory.class))
.messageRepo(mock(MessageRepository.class))
From 4911c268c2373192f6e1d6e24feed8624a6526c9 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Wed, 5 Aug 2015 19:55:53 +0200
Subject: [PATCH 03/42] Changed repositories to work with SQLDroid, which seems
to have very limited support for blobs, at least it didn't work when I used
stream.
---
.../ch/dissem/bitmessage/utils/BytesTest.java | 21 ++++++++++++++++++-
.../repository/JdbcAddressRepository.java | 16 +++++++-------
.../bitmessage/repository/JdbcHelper.java | 3 +--
.../bitmessage/repository/JdbcInventory.java | 9 ++++----
.../repository/JdbcMessageRepository.java | 5 +++--
5 files changed, 37 insertions(+), 17 deletions(-)
diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
index 52eb106..1af8d37 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
@@ -16,6 +16,7 @@
package ch.dissem.bitmessage.utils;
+import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
@@ -31,7 +32,7 @@ public class BytesTest {
@Test
public void ensureExpandsCorrectly() {
byte[] source = {1};
- byte[] expected = {0,1};
+ byte[] expected = {0, 1};
assertArrayEquals(expected, Bytes.expand(source, 2));
}
@@ -53,6 +54,24 @@ public class BytesTest {
}
}
+ /**
+ * This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored.
+ */
+ @Test
+ @Ignore
+ public void testLowerThanSingleByte() {
+ byte[] a = new byte[1];
+ byte[] b = new byte[1];
+ for (int i = 0; i < 255; i++) {
+ for (int j = 0; j < 255; j++) {
+ System.out.println("a = " + i + "\tb = " + j);
+ a[0] = (byte) i;
+ b[0] = (byte) j;
+ assertEquals(i < j, Bytes.lt(a, b));
+ }
+ }
+ }
+
@Test
public void testLowerThan() {
for (int i = 0; i < 1000; i++) {
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java
index f1e55d6..099690d 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java
@@ -96,16 +96,17 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key, subscribed FROM Address WHERE " + where);
while (rs.next()) {
BitmessageAddress address;
- Blob privateKeyBlob = rs.getBlob("private_key");
- if (privateKeyBlob != null) {
- PrivateKey privateKey = PrivateKey.read(privateKeyBlob.getBinaryStream());
+
+ byte[] privateKeyBytes = rs.getBytes("private_key");
+ if (privateKeyBytes != null) {
+ PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream(privateKeyBytes));
address = new BitmessageAddress(privateKey);
} else {
address = new BitmessageAddress(rs.getString("address"));
- Blob publicKeyBlob = rs.getBlob("public_key");
- if (publicKeyBlob != null) {
+ byte[] publicKeyBytes = rs.getBytes("public_key");
+ if (publicKeyBytes != null) {
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(),
- publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length(), false);
+ new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, false);
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
pubkey = new V4Pubkey((V3Pubkey) pubkey);
}
@@ -179,8 +180,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
if (data != null) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
data.writeUnencrypted(out);
- ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
- ps.setBlob(parameterIndex, in);
+ ps.setBytes(parameterIndex, out.toByteArray());
} else {
ps.setBlob(parameterIndex, (Blob) null);
}
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
index 4fbea30..b6e7461 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
@@ -82,8 +82,7 @@ abstract class JdbcHelper {
if (data != null) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
data.write(os);
- byte[] bytes = os.toByteArray();
- ps.setBinaryStream(parameterIndex, new ByteArrayInputStream(bytes), bytes.length);
+ ps.setBytes(parameterIndex, os.toByteArray());
} else {
ps.setBlob(parameterIndex, (Blob) null);
}
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
index c7e8873..6a9e35c 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
@@ -24,6 +24,7 @@ import ch.dissem.bitmessage.ports.Inventory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.ByteArrayInputStream;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
@@ -78,8 +79,8 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'");
if (rs.next()) {
- Blob data = rs.getBlob("data");
- return Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length());
+ byte[] data = rs.getBytes("data");
+ return Factory.getObjectMessage(rs.getInt("version"), new ByteArrayInputStream(data), data.length);
} else {
LOG.info("Object requested that we don't have. IV: " + vector);
return null;
@@ -107,8 +108,8 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
ResultSet rs = stmt.executeQuery(query.toString());
List result = new LinkedList<>();
while (rs.next()) {
- Blob data = rs.getBlob("data");
- result.add(Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()));
+ byte[] data = rs.getBytes("data");
+ result.add(Factory.getObjectMessage(rs.getInt("version"), new ByteArrayInputStream(data), data.length));
}
return result;
} catch (Exception e) {
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
index 278163f..a2476e5 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
@@ -25,6 +25,7 @@ import ch.dissem.bitmessage.ports.MessageRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
@@ -106,9 +107,9 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
ResultSet rs = stmt.executeQuery("SELECT id, iv, type, sender, recipient, data, sent, received, status FROM Message WHERE " + where);
while (rs.next()) {
byte[] iv = rs.getBytes("iv");
- Blob data = rs.getBlob("data");
+ byte[] data = rs.getBytes("data");
Plaintext.Type type = Plaintext.Type.valueOf(rs.getString("type"));
- Plaintext.Builder builder = Plaintext.readWithoutSignature(type, data.getBinaryStream());
+ Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new ByteArrayInputStream(data));
long id = rs.getLong("id");
builder.id(id);
builder.IV(new InventoryVector(iv));
From f89d1a342ed3d596bae43c7c4ac08d47de6e88ca Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 28 Aug 2015 13:48:01 +0200
Subject: [PATCH 04/42] Fixed a few problems: - some bugs that creeped in when
I moved security into its own adapter - improved some DB code as it doesn't
work in Android anyway - all entities should be serializable (very useful in
Android)
---
demo/build.gradle | 2 ++
.../dissem/bitmessage/demo/Application.java | 3 ++
.../java/ch/dissem/bitmessage/demo/Main.java | 3 ++
.../ch/dissem/bitmessage/InternalContext.java | 2 +-
.../dissem/bitmessage/entity/Plaintext.java | 14 +++++++--
.../dissem/bitmessage/entity/Streamable.java | 5 ++--
.../entity/payload/ObjectPayload.java | 1 +
.../bitmessage/entity/valueobject/Label.java | 3 +-
.../bitmessage/ports}/MemoryNodeRegistry.java | 3 +-
.../bitmessage/ports/NetworkHandler.java | 3 ++
.../ch/dissem/bitmessage/utils/Bytes.java | 8 ++---
.../ch/dissem/bitmessage/utils/Encode.java | 2 ++
.../src/main/resources/nodes.txt | 0
.../bitmessage/entity/SerializationTest.java | 29 +++++++++++++++----
.../networking/DefaultNetworkHandler.java | 6 ++++
.../repository/JdbcAddressRepository.java | 15 +++++-----
.../bitmessage/repository/JdbcHelper.java | 4 +--
.../bitmessage/repository/JdbcInventory.java | 9 +++---
.../repository/JdbcMessageRepository.java | 8 +++--
.../repository/JdbcNodeRegistryTest.java | 1 +
20 files changed, 83 insertions(+), 38 deletions(-)
rename {repositories/src/main/java/ch/dissem/bitmessage/repository => domain/src/main/java/ch/dissem/bitmessage/ports}/MemoryNodeRegistry.java (98%)
rename {repositories => domain}/src/main/resources/nodes.txt (100%)
diff --git a/demo/build.gradle b/demo/build.gradle
index 8d6414c..cba7d93 100644
--- a/demo/build.gradle
+++ b/demo/build.gradle
@@ -22,8 +22,10 @@ dependencies {
compile project(':domain')
compile project(':networking')
compile project(':repositories')
+ compile project(':security-bc')
compile project(':wif')
compile 'org.slf4j:slf4j-simple:1.7.12'
compile 'args4j:args4j:2.32'
+ compile 'com.h2database:h2:1.4.187'
testCompile 'junit:junit:4.11'
}
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
index 416e7b7..2f6f9fd 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
@@ -21,7 +21,9 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
+import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.repository.*;
+import ch.dissem.bitmessage.security.bc.BouncySecurity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,6 +48,7 @@ public class Application {
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
.networkHandler(new DefaultNetworkHandler())
+ .security(new BouncySecurity())
.port(48444)
.build();
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
index 7f934a3..ac90e88 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
@@ -18,7 +18,9 @@ package ch.dissem.bitmessage.demo;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
+import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.repository.*;
+import ch.dissem.bitmessage.security.bc.BouncySecurity;
import ch.dissem.bitmessage.wif.WifExporter;
import ch.dissem.bitmessage.wif.WifImporter;
import org.kohsuke.args4j.CmdLineException;
@@ -50,6 +52,7 @@ public class Main {
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
.networkHandler(new DefaultNetworkHandler())
+ .security(new BouncySecurity())
.port(48444)
.build();
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 03fef80..05ccd51 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -81,7 +81,7 @@ public class InternalContext {
streams.add(1L);
}
- init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
+ init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine, security);
}
private void init(Object... objects) {
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index e73849f..eb0a60f 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -19,9 +19,9 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.factory.Factory;
-import ch.dissem.bitmessage.ports.Security;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
+import ch.dissem.bitmessage.utils.UnixTime;
import java.io.*;
import java.util.*;
@@ -29,7 +29,7 @@ import java.util.*;
/**
* The unencrypted message to be sent by 'msg' or 'broadcast'.
*/
-public class Plaintext implements Streamable, Serializable {
+public class Plaintext implements Streamable {
private final Type type;
private final BitmessageAddress from;
private final long encoding;
@@ -64,6 +64,7 @@ public class Plaintext implements Streamable, Serializable {
public static Plaintext read(Type type, InputStream in) throws IOException {
return readWithoutSignature(type, in)
.signature(Decode.varBytes(in))
+ .received(UnixTime.now())
.build();
}
@@ -132,6 +133,15 @@ public class Plaintext implements Streamable, Serializable {
this.signature = signature;
}
+ public boolean isUnread() {
+ for (Label label : labels) {
+ if (label.getType() == Label.Type.UNREAD) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public void write(OutputStream out, boolean includeSignature) throws IOException {
Encode.varInt(from.getVersion(), out);
Encode.varInt(from.getStream(), out);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
index 0601a44..cc12050 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
@@ -16,14 +16,13 @@
package ch.dissem.bitmessage.entity;
-import ch.dissem.bitmessage.ports.Security;
-
import java.io.IOException;
import java.io.OutputStream;
+import java.io.Serializable;
/**
* An object that can be written to an {@link OutputStream}
*/
-public interface Streamable {
+public interface Streamable extends Serializable {
void write(OutputStream stream) throws IOException;
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
index ef42718..0ca45cd 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
@@ -21,6 +21,7 @@ import ch.dissem.bitmessage.entity.Streamable;
import java.io.IOException;
import java.io.OutputStream;
+import java.io.Serializable;
/**
* The payload of an 'object' command. This is shared by the network.
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java
index e1bd8f2..7c37973 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java
@@ -16,9 +16,10 @@
package ch.dissem.bitmessage.entity.valueobject;
+import java.io.Serializable;
import java.util.Objects;
-public class Label {
+public class Label implements Serializable {
private Object id;
private String label;
private Type type;
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/MemoryNodeRegistry.java b/domain/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
similarity index 98%
rename from repositories/src/main/java/ch/dissem/bitmessage/repository/MemoryNodeRegistry.java
rename to domain/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
index 15a4134..04fa2f9 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/MemoryNodeRegistry.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package ch.dissem.bitmessage.repository;
+package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
-import ch.dissem.bitmessage.ports.NodeRegistry;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
index 4b73629..e07bd79 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
@@ -21,6 +21,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.utils.Property;
import java.io.IOException;
+import java.net.InetAddress;
/**
* Handles incoming messages
@@ -30,6 +31,8 @@ public interface NetworkHandler {
void stop();
+ void synchronize(InetAddress trustedHost, int port, MessageListener listener) throws IOException;
+
void offer(InventoryVector iv);
Property getNetworkStatus();
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
index 31a3dcc..8107eb0 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
@@ -16,12 +16,6 @@
package ch.dissem.bitmessage.utils;
-import ch.dissem.bitmessage.entity.Streamable;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.Arrays;
-
/**
* A helper class for working with byte arrays interpreted as unsigned big endian integers.
* This is one part due to the fact that Java doesn't support unsigned numbers, and another
@@ -91,6 +85,8 @@ public class Bytes {
if (a < 0) return b < 0 && a < b;
if (b < 0) return a >= 0 || a < b;
return a < b;
+ // This would be easier to understand, but is (slightly) slower:
+ // return (a & 0xff) < (b & 0xff);
}
/**
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
index 095fb78..a78d03f 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
@@ -117,6 +117,8 @@ public class Encode {
* @throws IOException if an I/O error occurs.
*/
public static byte[] bytes(Streamable streamable) throws IOException {
+ if (streamable == null) return null;
+
ByteArrayOutputStream stream = new ByteArrayOutputStream();
streamable.write(stream);
return stream.toByteArray();
diff --git a/repositories/src/main/resources/nodes.txt b/domain/src/main/resources/nodes.txt
similarity index 100%
rename from repositories/src/main/resources/nodes.txt
rename to domain/src/main/resources/nodes.txt
diff --git a/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
index 7e56f95..de0c807 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
@@ -17,21 +17,21 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.*;
+import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.factory.Factory;
+import ch.dissem.bitmessage.utils.TestBase;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
+import java.io.*;
+import java.util.Arrays;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-public class SerializationTest {
+public class SerializationTest extends TestBase {
@Test
public void ensureGetPubkeyIsDeserializedAndSerializedCorrectly() throws IOException {
doTest("V2GetPubkey.payload", 2, GetPubkey.class);
@@ -75,7 +75,7 @@ public class SerializationTest {
}
@Test
- public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws IOException, DecryptionFailedException {
+ public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws Exception {
Plaintext p1 = new Plaintext.Builder(MSG)
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.to(TestUtils.loadContact())
@@ -99,4 +99,21 @@ public class SerializationTest {
assertArrayEquals(data, out.toByteArray());
assertEquals(expectedPayloadType.getCanonicalName(), object.getPayload().getClass().getCanonicalName());
}
+
+ @Test
+ public void ensureSystemSerializationWorks() throws Exception {
+ Plaintext plaintext = new Plaintext.Builder(MSG)
+ .from(TestUtils.loadContact())
+ .to(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
+ .labels(Arrays.asList(new Label("Test", Label.Type.INBOX, 0)))
+ .message("Test", "Test Test.\nTest")
+ .build();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(out);
+ oos.writeObject(plaintext);
+
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(in);
+ assertEquals(plaintext, ois.readObject());
+ }
}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index 558d097..e73e8ed 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -27,6 +27,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
@@ -149,6 +150,11 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
}
}
+ @Override
+ public void synchronize(InetAddress trustedHost, int port, MessageListener listener) throws IOException {
+ startConnection(new Connection(ctx, CLIENT, new Socket(trustedHost, port), listener, requestedObjects));
+ }
+
private void startConnection(Connection c) {
synchronized (connections) {
// prevent connecting twice to the same node
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java
index 099690d..337d50a 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.sql.*;
import java.util.Arrays;
import java.util.LinkedList;
@@ -97,16 +98,16 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
while (rs.next()) {
BitmessageAddress address;
- byte[] privateKeyBytes = rs.getBytes("private_key");
- if (privateKeyBytes != null) {
- PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream(privateKeyBytes));
+ InputStream privateKeyStream = rs.getBinaryStream("private_key");
+ if (privateKeyStream != null) {
+ PrivateKey privateKey = PrivateKey.read(privateKeyStream);
address = new BitmessageAddress(privateKey);
} else {
address = new BitmessageAddress(rs.getString("address"));
- byte[] publicKeyBytes = rs.getBytes("public_key");
- if (publicKeyBytes != null) {
+ Blob publicKeyBlob = rs.getBlob("public_key");
+ if (publicKeyBlob != null) {
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(),
- new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, false);
+ publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length(), false);
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
pubkey = new V4Pubkey((V3Pubkey) pubkey);
}
@@ -182,7 +183,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
data.writeUnencrypted(out);
ps.setBytes(parameterIndex, out.toByteArray());
} else {
- ps.setBlob(parameterIndex, (Blob) null);
+ ps.setBytes(parameterIndex, null);
}
}
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
index b6e7461..601ce29 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
@@ -21,10 +21,8 @@ import ch.dissem.bitmessage.entity.payload.ObjectType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.sql.Blob;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@@ -84,7 +82,7 @@ abstract class JdbcHelper {
data.write(os);
ps.setBytes(parameterIndex, os.toByteArray());
} else {
- ps.setBlob(parameterIndex, (Blob) null);
+ ps.setBytes(parameterIndex, null);
}
}
}
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
index 6a9e35c..98034ee 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
@@ -53,6 +53,7 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
}
return result;
}
+
private List getFullInventory(long... streams) {
List result = new LinkedList<>();
try (Connection connection = config.getConnection()) {
@@ -79,8 +80,8 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'");
if (rs.next()) {
- byte[] data = rs.getBytes("data");
- return Factory.getObjectMessage(rs.getInt("version"), new ByteArrayInputStream(data), data.length);
+ Blob data = rs.getBlob("data");
+ return Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length());
} else {
LOG.info("Object requested that we don't have. IV: " + vector);
return null;
@@ -108,8 +109,8 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
ResultSet rs = stmt.executeQuery(query.toString());
List result = new LinkedList<>();
while (rs.next()) {
- byte[] data = rs.getBytes("data");
- result.add(Factory.getObjectMessage(rs.getInt("version"), new ByteArrayInputStream(data), data.length));
+ Blob data = rs.getBlob("data");
+ result.add(Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()));
}
return result;
} catch (Exception e) {
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
index a2476e5..ec89e68 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.sql.*;
import java.util.ArrayList;
import java.util.Collection;
@@ -107,9 +108,9 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
ResultSet rs = stmt.executeQuery("SELECT id, iv, type, sender, recipient, data, sent, received, status FROM Message WHERE " + where);
while (rs.next()) {
byte[] iv = rs.getBytes("iv");
- byte[] data = rs.getBytes("data");
+ InputStream data = rs.getBinaryStream("data");
Plaintext.Type type = Plaintext.Type.valueOf(rs.getString("type"));
- Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new ByteArrayInputStream(data));
+ Plaintext.Builder builder = Plaintext.readWithoutSignature(type, data);
long id = rs.getLong("id");
builder.id(id);
builder.IV(new InventoryVector(iv));
@@ -191,7 +192,8 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private void insert(Connection connection, Plaintext message) throws SQLException, IOException {
PreparedStatement ps = connection.prepareStatement(
- "INSERT INTO Message (iv, type, sender, recipient, data, sent, received, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
+ "INSERT INTO Message (iv, type, sender, recipient, data, sent, received, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ Statement.RETURN_GENERATED_KEYS);
ps.setBytes(1, message.getInventoryVector() != null ? message.getInventoryVector().getHash() : null);
ps.setString(2, message.getType().name());
ps.setString(3, message.getFrom().getAddress());
diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java
index ca4bcd7..044c44c 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java
@@ -17,6 +17,7 @@
package ch.dissem.bitmessage.repository;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
+import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.ports.NodeRegistry;
import org.junit.Before;
import org.junit.Test;
From d67c932fb211c555971ba9be6283f87834e7c0d8 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Thu, 24 Sep 2015 08:09:20 +0200
Subject: [PATCH 05/42] Added synchronization code and unit test.
Synchronisation fails if the trusted host has no new messages - this needs to
be fixed (but shouldn't be an issue for real world applications)
---
.../dissem/bitmessage/BitmessageContext.java | 40 +++--
.../ch/dissem/bitmessage/InternalContext.java | 31 ++--
.../ch/dissem/bitmessage/MessageCallback.java | 52 +++++++
.../bitmessage/ports/NetworkHandler.java | 16 +-
.../ch/dissem/bitmessage/utils/Property.java | 19 +++
networking/build.gradle | 4 +-
.../bitmessage/networking/Connection.java | 65 +++++---
.../networking/DefaultNetworkHandler.java | 16 +-
.../networking/DefaultNetworkHandlerTest.java | 64 --------
.../networking/NetworkHandlerTest.java | 147 ++++++++++++++++++
.../bitmessage/networking/TestInventory.java | 76 +++++++++
.../networking/TestNodeRegistry.java | 44 ++++++
networking/src/test/resources/V1Msg.payload | Bin 0 -> 444 bytes
.../src/test/resources/V4Pubkey.payload | Bin 0 -> 396 bytes
.../src/test/resources/V5Broadcast.payload | Bin 0 -> 428 bytes
15 files changed, 457 insertions(+), 117 deletions(-)
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/MessageCallback.java
delete mode 100644 networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java
create mode 100644 networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
create mode 100644 networking/src/test/java/ch/dissem/bitmessage/networking/TestInventory.java
create mode 100644 networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java
create mode 100644 networking/src/test/resources/V1Msg.payload
create mode 100644 networking/src/test/resources/V4Pubkey.payload
create mode 100644 networking/src/test/resources/V5Broadcast.payload
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index d7c04b5..2cd851e 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -21,13 +21,13 @@ import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
+import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Property;
-import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -179,19 +179,6 @@ public class BitmessageContext {
);
}
- private void send(long stream, ObjectPayload payload, long timeToLive) {
- long expires = UnixTime.now(+timeToLive);
- LOG.info("Expires at " + expires);
- ObjectMessage object = new ObjectMessage.Builder()
- .stream(stream)
- .expiresTime(expires)
- .payload(payload)
- .build();
- ctx.getSecurity().doProofOfWork(object, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
- ctx.getInventory().storeObject(object);
- ctx.getNetworkHandler().offer(object.getInventoryVector());
- }
-
public void startup(Listener listener) {
this.listener = listener;
ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener));
@@ -280,6 +267,7 @@ public class BitmessageContext {
MessageRepository messageRepo;
ProofOfWorkEngine proofOfWorkEngine;
Security security;
+ MessageCallback messageCallback;
public Builder() {
}
@@ -319,6 +307,11 @@ public class BitmessageContext {
return this;
}
+ public Builder messageCallback(MessageCallback callback) {
+ this.messageCallback = callback;
+ return this;
+ }
+
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
this.proofOfWorkEngine = proofOfWorkEngine;
return this;
@@ -333,6 +326,25 @@ public class BitmessageContext {
if (proofOfWorkEngine == null) {
proofOfWorkEngine = new MultiThreadedPOWEngine();
}
+ if (messageCallback == null) {
+ messageCallback = new MessageCallback() {
+ @Override
+ public void proofOfWorkStarted(ObjectPayload message) {
+ }
+
+ @Override
+ public void proofOfWorkCompleted(ObjectPayload message) {
+ }
+
+ @Override
+ public void messageOffered(ObjectPayload message, InventoryVector iv) {
+ }
+
+ @Override
+ public void messageAcknowledged(InventoryVector iv) {
+ }
+ };
+ }
return new BitmessageContext(this);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 05ccd51..403e9c4 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -49,12 +49,13 @@ public class InternalContext {
private final AddressRepository addressRepository;
private final MessageRepository messageRepository;
private final ProofOfWorkEngine proofOfWorkEngine;
+ private final MessageCallback messageCallback;
private final TreeSet streams = new TreeSet<>();
private final int port;
- private long networkNonceTrialsPerByte = 1000;
- private long networkExtraBytes = 1000;
- private long clientNonce;
+ private final long clientNonce;
+ private final long networkNonceTrialsPerByte = 1000;
+ private final long networkExtraBytes = 1000;
public InternalContext(BitmessageContext.Builder builder) {
this.security = builder.security;
@@ -65,11 +66,11 @@ public class InternalContext {
this.messageRepository = builder.messageRepo;
this.proofOfWorkEngine = builder.proofOfWorkEngine;
this.clientNonce = security.randomNonce();
+ this.messageCallback = builder.messageCallback;
+ this.port = builder.port;
Singleton.initialize(security);
- port = builder.port;
-
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
for (BitmessageAddress address : addressRepository.getIdentities()) {
streams.add(address.getStream());
@@ -81,7 +82,10 @@ public class InternalContext {
streams.add(1L);
}
- init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine, security);
+ init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
+ for (BitmessageAddress identity : addressRepository.getIdentities()) {
+ streams.add(identity.getStream());
+ }
}
private void init(Object... objects) {
@@ -169,7 +173,9 @@ public class InternalContext {
} else if (payload instanceof Encrypted) {
object.encrypt(to.getPubkey());
}
+ messageCallback.proofOfWorkStarted(payload);
security.doProofOfWork(object, nonceTrialsPerByte, extraBytes);
+ messageCallback.proofOfWorkCompleted(payload);
if (payload instanceof PlaintextHolder) {
Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
plaintext.setInventoryVector(object.getInventoryVector());
@@ -177,6 +183,7 @@ public class InternalContext {
}
inventory.storeObject(object);
networkHandler.offer(object.getInventoryVector());
+ messageCallback.messageOffered(payload, object.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -193,16 +200,13 @@ public class InternalContext {
.build();
response.sign(identity.getPrivateKey());
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
+ messageCallback.proofOfWorkStarted(identity.getPubkey());
security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
- if (response.isSigned()) {
- response.sign(identity.getPrivateKey());
- }
- if (response instanceof Encrypted) {
- response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
- }
+ messageCallback.proofOfWorkCompleted(identity.getPubkey());
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
// TODO: save that the pubkey was just sent, and on which stream!
+ messageCallback.messageOffered(identity.getPubkey(), response.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -216,9 +220,12 @@ public class InternalContext {
.expiresTime(expires)
.payload(new GetPubkey(contact))
.build();
+ messageCallback.proofOfWorkStarted(response.getPayload());
security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
+ messageCallback.proofOfWorkCompleted(response.getPayload());
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
+ messageCallback.messageOffered(response.getPayload(), response.getInventoryVector());
}
public long getClientNonce() {
diff --git a/domain/src/main/java/ch/dissem/bitmessage/MessageCallback.java b/domain/src/main/java/ch/dissem/bitmessage/MessageCallback.java
new file mode 100644
index 0000000..d09ff97
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/MessageCallback.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage;
+
+import ch.dissem.bitmessage.entity.payload.ObjectPayload;
+import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
+
+/**
+ * Callback for message sending events, mostly so the user can be notified when POW is done.
+ */
+public interface MessageCallback {
+ /**
+ * Called before calculation of proof of work begins.
+ */
+ void proofOfWorkStarted(ObjectPayload message);
+
+ /**
+ * Called after calculation of proof of work finished.
+ */
+ void proofOfWorkCompleted(ObjectPayload message);
+
+ /**
+ * Called once the message is offered to the network. Please note that this doesn't mean the message was sent,
+ * if the client is not connected to the network it's just stored in the inventory.
+ *
+ * Also, please note that this is where the original payload as well as the {@link InventoryVector} of the sent
+ * message is available. If the callback needs the IV for some reason, it should be retrieved here. (Plaintext
+ * and Broadcast messages will have their IV property set automatically though.)
+ *
+ */
+ void messageOffered(ObjectPayload message, InventoryVector iv);
+
+ /**
+ * This isn't called yet, as ACK messages aren't being processed yet. Also, this is only relevant for Plaintext
+ * messages.
+ */
+ void messageAcknowledged(InventoryVector iv);
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
index e07bd79..fd44358 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
@@ -27,12 +27,24 @@ import java.net.InetAddress;
* Handles incoming messages
*/
public interface NetworkHandler {
+ /**
+ * Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
+ */
+ Thread synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds);
+
+ /**
+ * Start a full network node, accepting incoming connections and relaying objects.
+ */
void start(MessageListener listener);
+ /**
+ * Stop the full network node.
+ */
void stop();
- void synchronize(InetAddress trustedHost, int port, MessageListener listener) throws IOException;
-
+ /**
+ * Offer new objects to up to 8 random nodes.
+ */
void offer(InventoryVector iv);
Property getNetworkStatus();
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java
index 6fa0ec4..72abe58 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java
@@ -35,6 +35,25 @@ public class Property {
this.properties = properties;
}
+ public String getName() {
+ return name;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public Property getProperty(String name) {
+ for (Property p : properties) {
+ if (name == null) {
+ if (p.name == null) return p;
+ } else {
+ if (name.equals(p.name)) return p;
+ }
+ }
+ return null;
+ }
+
@Override
public String toString() {
return toString("");
diff --git a/networking/build.gradle b/networking/build.gradle
index 07d268e..6e9366c 100644
--- a/networking/build.gradle
+++ b/networking/build.gradle
@@ -12,6 +12,8 @@ uploadArchives {
dependencies {
compile project(':domain')
- testCompile 'org.slf4j:slf4j-simple:1.7.12'
testCompile 'junit:junit:4.11'
+ testCompile 'org.slf4j:slf4j-simple:1.7.12'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(':security-bc')
}
\ No newline at end of file
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
index d8054ff..bb8e62c 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
@@ -54,43 +55,55 @@ public class Connection implements Runnable {
private final static Logger LOG = LoggerFactory.getLogger(Connection.class);
private static final int CONNECT_TIMEOUT = 5000;
private final ConcurrentMap ivCache;
- private InternalContext ctx;
- private Mode mode;
+ private final InternalContext ctx;
+ private final Mode mode;
+ private final Socket socket;
+ private final MessageListener listener;
+ private final NetworkAddress host;
+ private final NetworkAddress node;
+ private final Queue sendingQueue = new ConcurrentLinkedDeque<>();
+ private final Map requestedObjects;
+ private final long syncTimeout;
+
private State state;
- private Socket socket;
private InputStream in;
private OutputStream out;
- private MessageListener listener;
private int version;
private long[] streams;
- private NetworkAddress host;
- private NetworkAddress node;
- private Queue sendingQueue = new ConcurrentLinkedDeque<>();
- private ConcurrentMap requestedObjects;
public Connection(InternalContext context, Mode mode, Socket socket, MessageListener listener,
ConcurrentMap requestedObjectsMap) throws IOException {
- this(context, mode, listener, requestedObjectsMap);
- this.socket = socket;
- this.node = new NetworkAddress.Builder().ip(socket.getInetAddress()).port(socket.getPort()).stream(1).build();
+ this(context, mode, listener, socket, requestedObjectsMap,
+ new NetworkAddress.Builder().ip(socket.getInetAddress()).port(socket.getPort()).stream(1).build(),
+ 0);
}
public Connection(InternalContext context, Mode mode, NetworkAddress node, MessageListener listener,
ConcurrentMap requestedObjectsMap) {
- this(context, mode, listener, requestedObjectsMap);
- this.socket = new Socket();
- this.node = node;
+ this(context, mode, listener, new Socket(), requestedObjectsMap,
+ node, 0);
}
- private Connection(InternalContext context, Mode mode, MessageListener listener,
- ConcurrentMap requestedObjectsMap) {
+ private Connection(InternalContext context, Mode mode, MessageListener listener, Socket socket,
+ Map requestedObjectsMap, NetworkAddress node, long syncTimeout) {
this.ctx = context;
this.mode = mode;
this.state = CONNECTING;
this.listener = listener;
+ this.socket = socket;
this.requestedObjects = requestedObjectsMap;
this.host = new NetworkAddress.Builder().ipv6(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).port(0).build();
- ivCache = new ConcurrentHashMap<>();
+ this.node = node;
+ this.syncTimeout = (syncTimeout > 0 ? UnixTime.now(+syncTimeout) : 0);
+ this.ivCache = new ConcurrentHashMap<>();
+ }
+
+ public static Connection sync(InternalContext ctx, InetAddress address, int port, MessageListener listener,
+ long timeoutInSeconds) throws IOException {
+ return new Connection(ctx, Mode.CLIENT, listener, new Socket(address, port),
+ new HashMap(),
+ new NetworkAddress.Builder().ip(address).port(port).stream(1).build(),
+ timeoutInSeconds);
}
public Mode getMode() {
@@ -168,7 +181,7 @@ public class Connection implements Runnable {
+ msg.getPayload().getCommand() + "'");
}
}
- if (socket.isClosed()) state = DISCONNECTED;
+ if (socket.isClosed() || syncFinished(msg)) disconnect();
} catch (SocketTimeoutException ignore) {
if (state == ACTIVE) {
sendQueue();
@@ -184,13 +197,27 @@ public class Connection implements Runnable {
}
}
+ @SuppressWarnings("RedundantIfStatement")
+ private boolean syncFinished(NetworkMessage msg) {
+ if (syncTimeout == 0 || state != ACTIVE) {
+ return false;
+ }
+ if (syncTimeout < UnixTime.now()) {
+ return true;
+ }
+ if (!(msg.getPayload() instanceof Addr) && requestedObjects.isEmpty() && sendingQueue.isEmpty()) {
+ return true;
+ }
+ return false;
+ }
+
private void activateConnection() {
LOG.info("Successfully established connection with node " + node);
state = ACTIVE;
sendAddresses();
sendInventory();
node.setTime(UnixTime.now());
- ctx.getNodeRegistry().offerAddresses(Arrays.asList(node));
+ ctx.getNodeRegistry().offerAddresses(Collections.singletonList(node));
}
private void sendQueue() {
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index e73e8ed..aead8e8 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -66,6 +66,17 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
this.ctx = context;
}
+ @Override
+ public Thread synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds) {
+ try {
+ Thread t = new Thread(Connection.sync(ctx, trustedHost, port, listener, timeoutInSeconds));
+ t.start();
+ return t;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Override
public void start(final MessageListener listener) {
if (listener == null) {
@@ -150,11 +161,6 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
}
}
- @Override
- public void synchronize(InetAddress trustedHost, int port, MessageListener listener) throws IOException {
- startConnection(new Connection(ctx, CLIENT, new Socket(trustedHost, port), listener, requestedObjects));
- }
-
private void startConnection(Connection c) {
synchronized (connections) {
// prevent connecting twice to the same node
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java
deleted file mode 100644
index 3bec993..0000000
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.networking;
-
-import ch.dissem.bitmessage.entity.NetworkMessage;
-import ch.dissem.bitmessage.entity.Version;
-import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
-import ch.dissem.bitmessage.utils.UnixTime;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- * FIXME: there really should be sensible tests for the network handler
- */
-public class DefaultNetworkHandlerTest {
- private NetworkAddress localhost = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(8444).build();
-
- // void start(MessageListener listener);
- // void stop();
- // void offer(InventoryVector iv);
- // Property getNetworkStatus();
-
- @Ignore
- @Test(expected = InterruptedException.class)
- public void testSendMessage() throws Exception {
- final Thread baseThread = Thread.currentThread();
- DefaultNetworkHandler net = new DefaultNetworkHandler();
-// net.setListener(localhost, new NetworkHandler.MessageListener() {
-// @Override
-// public void receive(ObjectPayload payload) {
-// System.out.println(payload);
-// baseThread.interrupt();
-// }
-// });
- NetworkMessage ver = new NetworkMessage(
- new Version.Builder()
- .version(3)
- .services(1)
- .timestamp(UnixTime.now())
- .addrFrom(localhost)
- .addrRecv(localhost)
- .nonce(-1)
- .userAgent("Test")
- .streams(1, 2)
- .build()
- );
-// net.send(localhost, ver);
- Thread.sleep(20000);
- }
-}
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
new file mode 100644
index 0000000..cd8a743
--- /dev/null
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.networking;
+
+import ch.dissem.bitmessage.BitmessageContext;
+import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
+import ch.dissem.bitmessage.ports.AddressRepository;
+import ch.dissem.bitmessage.ports.MessageRepository;
+import ch.dissem.bitmessage.ports.NetworkHandler;
+import ch.dissem.bitmessage.security.bc.BouncySecurity;
+import ch.dissem.bitmessage.utils.Property;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.net.InetAddress;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+/**
+ * FIXME: there really should be sensible tests for the network handler
+ */
+public class NetworkHandlerTest {
+ private static NetworkAddress localhost = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(6001).build();
+
+ private static TestInventory peerInventory;
+ private static TestInventory nodeInventory;
+
+ private static BitmessageContext node;
+ private static NetworkHandler networkHandler;
+
+ @BeforeClass
+ public static void setUp() {
+ peerInventory = new TestInventory();
+ BitmessageContext peer = new BitmessageContext.Builder()
+ .addressRepo(Mockito.mock(AddressRepository.class))
+ .inventory(peerInventory)
+ .messageRepo(Mockito.mock(MessageRepository.class))
+ .port(6001)
+ .nodeRegistry(new TestNodeRegistry())
+ .networkHandler(new DefaultNetworkHandler())
+ .security(new BouncySecurity())
+ .build();
+ peer.startup(Mockito.mock(BitmessageContext.Listener.class));
+
+ nodeInventory = new TestInventory();
+ networkHandler = new DefaultNetworkHandler();
+ node = new BitmessageContext.Builder()
+ .addressRepo(Mockito.mock(AddressRepository.class))
+ .inventory(nodeInventory)
+ .messageRepo(Mockito.mock(MessageRepository.class))
+ .port(6002)
+ .nodeRegistry(new TestNodeRegistry(localhost))
+ .networkHandler(networkHandler)
+ .security(new BouncySecurity())
+ .build();
+ }
+
+ @Test(timeout = 20_000)
+ public void ensureNodesAreConnecting() {
+ try {
+ node.startup(Mockito.mock(BitmessageContext.Listener.class));
+ Property status;
+ do {
+ Thread.yield();
+ status = node.status().getProperty("network").getProperty("connections").getProperty("stream 0");
+ } while (status == null);
+ assertEquals(1, status.getProperty("outgoing").getValue());
+ } finally {
+ shutdown(node);
+ }
+ }
+
+ @Test(timeout = 5_000)
+ public void ensureObjectsAreSynchronizedIfBothHaveObjects() throws Exception {
+ peerInventory.init(
+ "V4Pubkey.payload",
+ "V5Broadcast.payload"
+ );
+
+ nodeInventory.init(
+ "V1Msg.payload"
+ );
+
+ Thread t = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ mock(NetworkHandler.MessageListener.class),
+ 10);
+ t.join();
+ assertEquals(3, nodeInventory.getInventory().size());
+ assertEquals(3, peerInventory.getInventory().size());
+ }
+
+ @Test(timeout = 5_000)
+ public void ensureObjectsAreSynchronizedIfOnlyPeerHasObjects() throws Exception {
+ peerInventory.init(
+ "V4Pubkey.payload",
+ "V5Broadcast.payload"
+ );
+
+ nodeInventory.init();
+
+ Thread t = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ mock(NetworkHandler.MessageListener.class),
+ 10);
+ t.join();
+ assertEquals(2, nodeInventory.getInventory().size());
+ assertEquals(2, peerInventory.getInventory().size());
+ }
+
+ @Test(timeout = 5_000)
+ public void ensureObjectsAreSynchronizedIfOnlyNodeHasObjects() throws Exception {
+ peerInventory.init();
+
+ nodeInventory.init(
+ "V1Msg.payload"
+ );
+
+ Thread t = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ mock(NetworkHandler.MessageListener.class),
+ 10);
+ t.join();
+ assertEquals(1, nodeInventory.getInventory().size());
+ assertEquals(1, peerInventory.getInventory().size());
+ }
+
+ private void shutdown(BitmessageContext node) {
+ node.shutdown();
+ do {
+ Thread.yield();
+ } while (node.isRunning());
+ }
+}
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/TestInventory.java b/networking/src/test/java/ch/dissem/bitmessage/networking/TestInventory.java
new file mode 100644
index 0000000..040e730
--- /dev/null
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/TestInventory.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.networking;
+
+import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.entity.payload.ObjectType;
+import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
+import ch.dissem.bitmessage.ports.Inventory;
+import ch.dissem.bitmessage.utils.TestUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TestInventory implements Inventory {
+ private final Map inventory;
+
+ public TestInventory() {
+ this.inventory = new HashMap<>();
+ }
+
+ @Override
+ public List getInventory(long... streams) {
+ return new ArrayList<>(inventory.keySet());
+ }
+
+ @Override
+ public List getMissing(List offer, long... streams) {
+ return offer;
+ }
+
+ @Override
+ public ObjectMessage getObject(InventoryVector vector) {
+ return inventory.get(vector);
+ }
+
+ @Override
+ public List getObjects(long stream, long version, ObjectType... types) {
+ return new ArrayList<>(inventory.values());
+ }
+
+ @Override
+ public void storeObject(ObjectMessage object) {
+ inventory.put(object.getInventoryVector(), object);
+ }
+
+ @Override
+ public void cleanup() {
+
+ }
+
+ public void init(String... resources) throws IOException {
+ inventory.clear();
+ for (String resource : resources) {
+ int version = Integer.parseInt(resource.substring(1, 2));
+ ObjectMessage obj = TestUtils.loadObjectMessage(version, resource);
+ inventory.put(obj.getInventoryVector(), obj);
+ }
+ }
+}
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java b/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java
new file mode 100644
index 0000000..c3abd58
--- /dev/null
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.networking;
+
+import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
+import ch.dissem.bitmessage.ports.NodeRegistry;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Empty {@link NodeRegistry} that doesn't do anything, but shouldn't break things either.
+ */
+class TestNodeRegistry implements NodeRegistry {
+ private List nodes;
+
+ public TestNodeRegistry(NetworkAddress... nodes) {
+ this.nodes = Arrays.asList(nodes);
+ }
+
+ @Override
+ public List getKnownAddresses(int limit, long... streams) {
+ return nodes;
+ }
+
+ @Override
+ public void offerAddresses(List addresses) {
+ // Ignore
+ }
+}
diff --git a/networking/src/test/resources/V1Msg.payload b/networking/src/test/resources/V1Msg.payload
new file mode 100644
index 0000000000000000000000000000000000000000..ddf21cda61a888ea022b4b8424eb96ee1db39ffe
GIT binary patch
literal 444
zcmV;t0Ym-(00008xvxtA00010gc-#E000630k}!J&+7f>uEI8@F)kNn;2iFat;p{^0*meHzJ*{^Fy|8tEC&7fCXYCRC6p;-
zrwSGz0Ho|#|o
zEVfmjrCC-W%N^r5)7#?V5j#CZa%OaP0g&+7Iut0XVpU6GrzwGJMd0*0#;F)6j2?3m
zpz&%fvq&-208*9RwkBU10Rk0w2I?uhUA$B$(U5H_f%duH^Cj_3xbpYV?c%~y>8-%+dazRVTC$s5mxXUXECZIKhWTo
mu6-%(T=mLftk~cJZQV_Vr9WEM=TwI`1+jj(Azs6mYB0@}ZPQNx
literal 0
HcmV?d00001
diff --git a/networking/src/test/resources/V4Pubkey.payload b/networking/src/test/resources/V4Pubkey.payload
new file mode 100644
index 0000000000000000000000000000000000000000..a5e4c5c40944e8ffc38b369e40840c06a416c5aa
GIT binary patch
literal 396
zcmV;70dxKU00000xAyk{00010UeGiE000350Z5r#d*j`5Pg%2ba1!PSq98oPb(^t%
z;KgzK_V$mQZm9inp(ffPq;aqrDrn3?C<4j=Akdap1po|b4j_f0notM>d(L9Ml{C2@
z+T1h%bp#E0>i{5sJWl0^@?*BHMYKflgKudScHd!TX)VzSU6-cwk+pgTmYfiz1uQCj
zSs4!l9plyrnujt2%tbA6X)Hz_q)G?EPXO!Uy0#j8MQVhpFP4*Qu>!6gP@)wYpVarh
zgpUg62HESXiAQQMO_}jTWWG}erMJvUwXEpY1uLsPa(u$}|D#H;5*xal`le2&=b|6X
zXDuFEZ4@AwqBs+8&pZ<&P7N~hBU_TcS#YQ9yYuq}#O&r(M5b62y}r$6<(Ba$=f(7#
zTcA@q?ut~DeC+hIc@%8p@Hu+Tckgr{-rc17p<4cAWEv#WU`aSrVyLq*B74o@+$o~b
q!`TVqn-`}!5^p-`%#4j-0g7a3xT-Zo<53UgE9%*VnnKWkIVoM&N4W9;
literal 0
HcmV?d00001
diff --git a/networking/src/test/resources/V5Broadcast.payload b/networking/src/test/resources/V5Broadcast.payload
new file mode 100644
index 0000000000000000000000000000000000000000..87c8c047cf284e7fe9a9f47dbbfa49c3817bbd67
GIT binary patch
literal 428
zcmV;d0aN|}0000137ZE100010mbs?@000980Z5r#d*j`5Pg%2ba1!PSq98oPb(^t%
z;KgzK_V$mQZr&z5B~$A7VIv`j|E{Z8{Q}AWAl#f{6t$GZv!n%i@Sw{$TJpn{a!1eo
z=z%8?zJ0DxK>#4bE!e}!EtnzSQlhiRA31jD
zOO*qpGJH6?%S3d~7}HaC-c+n%AQ()G1O?D561V0JBm9DO>u)Qr=g+aPQVh?!%*W*81aLJ~yN;T{l*>cnUb
z*B%Nj&XS3pk4Wlw@7$u`pga`dcDrVb$+tBC{(ijJ-tx`97<96j=y~}-_OHR%gtS~T6Mu$Ih4{viO7qx3xfPsp%4y?
WTaKw&M!dRV|MQaOU7}MPXm%~gpv-;%
literal 0
HcmV?d00001
From 7fb837645fcb7c4df79252415db54cd0f5fd5794 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 25 Sep 2015 23:35:31 +0200
Subject: [PATCH 06/42] Synchronisation now shouldn't fail if the trusted host
has no new messages - fixed tests for Gradle builds - fixed SerializationTest
---
domain/build.gradle | 13 +++++++++++++
.../bitmessage/entity/SerializationTest.java | 16 +++++++++++-----
networking/build.gradle | 1 +
.../dissem/bitmessage/networking/Connection.java | 13 ++++++++++---
.../networking/NetworkHandlerTest.java | 2 +-
5 files changed, 36 insertions(+), 9 deletions(-)
diff --git a/domain/build.gradle b/domain/build.gradle
index 6bcbe6d..1880c6b 100644
--- a/domain/build.gradle
+++ b/domain/build.gradle
@@ -10,6 +10,19 @@ uploadArchives {
}
}
+configurations {
+ testArtifacts.extendsFrom testRuntime
+}
+
+task testJar(type: Jar) {
+ classifier = 'test'
+ from sourceSets.test.output
+}
+
+artifacts {
+ testArtifacts testJar
+}
+
dependencies {
compile 'org.slf4j:slf4j-api:1.7.12'
testCompile 'junit:junit:4.11'
diff --git a/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
index de0c807..03b7bc5 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
+++ b/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
@@ -18,18 +18,17 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.valueobject.Label;
-import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.TestBase;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
import java.io.*;
-import java.util.Arrays;
+import java.lang.reflect.Field;
+import java.util.Collections;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
public class SerializationTest extends TestBase {
@Test
@@ -87,6 +86,12 @@ public class SerializationTest extends TestBase {
p1.write(out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
Plaintext p2 = Plaintext.read(MSG, in);
+
+ // Received is automatically set on deserialization, so we'll need to set it to 0
+ Field received = Plaintext.class.getDeclaredField("received");
+ received.setAccessible(true);
+ received.set(p2, 0L);
+
assertEquals(p1, p2);
}
@@ -95,6 +100,7 @@ public class SerializationTest extends TestBase {
InputStream in = new ByteArrayInputStream(data);
ObjectMessage object = Factory.getObjectMessage(version, in, data.length);
ByteArrayOutputStream out = new ByteArrayOutputStream();
+ assertNotNull(object);
object.write(out);
assertArrayEquals(data, out.toByteArray());
assertEquals(expectedPayloadType.getCanonicalName(), object.getPayload().getClass().getCanonicalName());
@@ -105,7 +111,7 @@ public class SerializationTest extends TestBase {
Plaintext plaintext = new Plaintext.Builder(MSG)
.from(TestUtils.loadContact())
.to(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
- .labels(Arrays.asList(new Label("Test", Label.Type.INBOX, 0)))
+ .labels(Collections.singletonList(new Label("Test", Label.Type.INBOX, 0)))
.message("Test", "Test Test.\nTest")
.build();
ByteArrayOutputStream out = new ByteArrayOutputStream();
diff --git a/networking/build.gradle b/networking/build.gradle
index 6e9366c..06bd5d0 100644
--- a/networking/build.gradle
+++ b/networking/build.gradle
@@ -15,5 +15,6 @@ dependencies {
testCompile 'junit:junit:4.11'
testCompile 'org.slf4j:slf4j-simple:1.7.12'
testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(path: ':domain', configuration: 'testArtifacts')
testCompile project(':security-bc')
}
\ No newline at end of file
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
index bb8e62c..400bada 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -25,7 +25,6 @@ import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener;
-import ch.dissem.bitmessage.utils.DebugUtils;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -70,6 +69,7 @@ public class Connection implements Runnable {
private OutputStream out;
private int version;
private long[] streams;
+ private int readTimeoutCounter;
public Connection(InternalContext context, Mode mode, Socket socket, MessageListener listener,
ConcurrentMap requestedObjectsMap) throws IOException {
@@ -185,6 +185,7 @@ public class Connection implements Runnable {
} catch (SocketTimeoutException ignore) {
if (state == ACTIVE) {
sendQueue();
+ if (syncFinished(null)) disconnect();
}
}
}
@@ -205,7 +206,13 @@ public class Connection implements Runnable {
if (syncTimeout < UnixTime.now()) {
return true;
}
- if (!(msg.getPayload() instanceof Addr) && requestedObjects.isEmpty() && sendingQueue.isEmpty()) {
+ if (msg == null) {
+ readTimeoutCounter++;
+ return readTimeoutCounter > 1;
+ }
+ readTimeoutCounter = 0;
+ if (!(msg.getPayload() instanceof Addr) && !(msg.getPayload() instanceof GetData)
+ && requestedObjects.isEmpty() && sendingQueue.isEmpty()) {
return true;
}
return false;
@@ -311,9 +318,9 @@ public class Connection implements Runnable {
ctx.getNetworkHandler().offer(objectMessage.getInventoryVector());
} catch (InsufficientProofOfWorkException e) {
LOG.warn(e.getMessage());
+ // DebugUtils.saveToFile(objectMessage); // this line must not be committed active
} catch (IOException e) {
LOG.error("Stream " + objectMessage.getStream() + ", object type " + objectMessage.getType() + ": " + e.getMessage(), e);
- DebugUtils.saveToFile(objectMessage);
} finally {
requestedObjects.remove(objectMessage.getInventoryVector());
}
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
index cd8a743..7beca24 100644
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -122,7 +122,7 @@ public class NetworkHandlerTest {
assertEquals(2, peerInventory.getInventory().size());
}
- @Test(timeout = 5_000)
+ @Test(timeout = 10_000)
public void ensureObjectsAreSynchronizedIfOnlyNodeHasObjects() throws Exception {
peerInventory.init();
From c3fdee79ca10e6698febc9e61fe24dd58cec452f Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Tue, 29 Sep 2015 07:13:27 +0200
Subject: [PATCH 07/42] Some bugfixes and added findMessages by sender
---
.../java/ch/dissem/bitmessage/ports/MessageRepository.java | 2 ++
.../dissem/bitmessage/networking/DefaultNetworkHandler.java | 3 ++-
.../dissem/bitmessage/repository/JdbcMessageRepository.java | 5 +++++
security-bc/build.gradle | 6 +++---
4 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
index b698a97..6d8970b 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
@@ -34,6 +34,8 @@ public interface MessageRepository {
List findMessages(Status status, BitmessageAddress recipient);
+ List findMessages(BitmessageAddress sender);
+
void save(Plaintext message);
void remove(Plaintext message);
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index aead8e8..b6b3c69 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -221,7 +221,8 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
i++;
}
return new Property("network", null,
- new Property("connectionManager", connectionManager.isAlive() ? "running" : "stopped"),
+ new Property("connectionManager",
+ connectionManager != null && connectionManager.isAlive() ? "running" : "stopped"),
new Property("connections", null, streamProperties)
);
}
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
index ec89e68..759012b 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
@@ -101,6 +101,11 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
return find("status='" + status.name() + "'");
}
+ @Override
+ public List findMessages(BitmessageAddress sender) {
+ return find("sender='" + sender.getAddress() + "'");
+ }
+
private List find(String where) {
List result = new LinkedList<>();
try (Connection connection = config.getConnection()) {
diff --git a/security-bc/build.gradle b/security-bc/build.gradle
index 48f14d1..ff37994 100644
--- a/security-bc/build.gradle
+++ b/security-bc/build.gradle
@@ -2,9 +2,9 @@ uploadArchives {
repositories {
mavenDeployer {
pom.project {
- name 'Jabit Spongy Security'
- artifactId = 'jabit-security-spongy'
- description 'The Security implementation using spongy castle (needed for Android)'
+ name 'Jabit Bouncy Security'
+ artifactId = 'jabit-security-bouncy'
+ description 'The Security implementation using bouncy castle'
}
}
}
From f9ff22bebe0da06aee591bc3b4f1fc1877d6780a Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Wed, 7 Oct 2015 21:50:41 +0200
Subject: [PATCH 08/42] Synchronisation API and related refactorings /
improvements -> lets you synchronize with the Bitmessage network without
staying connected
---
.../dissem/bitmessage/demo/Application.java | 26 +++++----
.../dissem/bitmessage/BitmessageContext.java | 26 +++++++--
.../ch/dissem/bitmessage/InternalContext.java | 2 +-
.../ch/dissem/bitmessage/ports/Inventory.java | 2 +
.../bitmessage/ports/MemoryNodeRegistry.java | 55 ++++++++++---------
.../bitmessage/ports/MessageRepository.java | 2 +
domain/src/main/resources/nodes.txt | 8 ++-
.../bitmessage/networking/Connection.java | 2 +
.../networking/NetworkHandlerTest.java | 6 +-
.../bitmessage/networking/TestInventory.java | 5 ++
.../bitmessage/repository/JdbcInventory.java | 18 +++++-
.../repository/JdbcMessageRepository.java | 40 +++++++++++++-
.../repository/JdbcInventoryTest.java | 11 ++++
13 files changed, 154 insertions(+), 49 deletions(-)
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
index 2f6f9fd..d93c3c8 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
@@ -22,7 +22,10 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
-import ch.dissem.bitmessage.repository.*;
+import ch.dissem.bitmessage.repository.JdbcAddressRepository;
+import ch.dissem.bitmessage.repository.JdbcConfig;
+import ch.dissem.bitmessage.repository.JdbcInventory;
+import ch.dissem.bitmessage.repository.JdbcMessageRepository;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,18 +53,19 @@ public class Application {
.networkHandler(new DefaultNetworkHandler())
.security(new BouncySecurity())
.port(48444)
+ .listener(new BitmessageContext.Listener() {
+ @Override
+ public void receive(Plaintext plaintext) {
+ try {
+ System.out.println(new String(plaintext.getMessage(), "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
+ })
.build();
- ctx.startup(new BitmessageContext.Listener() {
- @Override
- public void receive(Plaintext plaintext) {
- try {
- System.out.println(new String(plaintext.getMessage(), "UTF-8"));
- } catch (UnsupportedEncodingException e) {
- LOG.error(e.getMessage(), e);
- }
- }
- });
+ ctx.startup();
scanner = new Scanner(System.in);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 2cd851e..0b3ad1d 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -31,6 +31,7 @@ import ch.dissem.bitmessage.utils.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.net.InetAddress;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -62,10 +63,14 @@ public class BitmessageContext {
private final InternalContext ctx;
- private Listener listener;
+ private final Listener listener;
+ private final NetworkHandler.MessageListener networkListener;
private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder);
+ listener = builder.listener;
+ networkListener = new DefaultMessageListener(ctx, listener);
+
// As this thread is used for parts that do POW, which itself uses parallel threads, only
// one should be executed at any time.
pool = Executors.newFixedThreadPool(1);
@@ -179,15 +184,22 @@ public class BitmessageContext {
);
}
- public void startup(Listener listener) {
- this.listener = listener;
- ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener));
+ public void startup() {
+ ctx.getNetworkHandler().start(networkListener);
}
public void shutdown() {
ctx.getNetworkHandler().stop();
}
+ public void synchronize(InetAddress host, int port, long timeoutInSeconds) {
+ ctx.getNetworkHandler().synchronize(host, port, networkListener, timeoutInSeconds);
+ }
+
+ public void cleanup() {
+ ctx.getInventory().cleanup();
+ }
+
public boolean isRunning() {
return ctx.getNetworkHandler().isRunning();
}
@@ -268,6 +280,7 @@ public class BitmessageContext {
ProofOfWorkEngine proofOfWorkEngine;
Security security;
MessageCallback messageCallback;
+ Listener listener;
public Builder() {
}
@@ -317,6 +330,11 @@ public class BitmessageContext {
return this;
}
+ public Builder listener(Listener listener) {
+ this.listener = listener;
+ return this;
+ }
+
public BitmessageContext build() {
nonNull("inventory", inventory);
nonNull("nodeRegistry", nodeRegistry);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 403e9c4..6d969bd 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -82,7 +82,7 @@ public class InternalContext {
streams.add(1L);
}
- init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
+ init(security, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
for (BitmessageAddress identity : addressRepository.getIdentities()) {
streams.add(identity.getStream());
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java b/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
index 934a4d0..a10d50f 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
@@ -36,5 +36,7 @@ public interface Inventory {
void storeObject(ObjectMessage object);
+ boolean contains(ObjectMessage object);
+
void cleanup();
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java b/domain/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
index 04fa2f9..9c97b87 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
@@ -34,38 +34,43 @@ import static java.util.Collections.newSetFromMap;
public class MemoryNodeRegistry implements NodeRegistry {
private static final Logger LOG = LoggerFactory.getLogger(MemoryNodeRegistry.class);
- private final Map> stableNodes = new HashMap<>();
+ private final Map> stableNodes = new ConcurrentHashMap<>();
private final Map> knownNodes = new ConcurrentHashMap<>();
public MemoryNodeRegistry() {
- try (InputStream in = getClass().getClassLoader().getResourceAsStream("nodes.txt")) {
- Scanner scanner = new Scanner(in);
- long stream = 0;
- Set streamSet = null;
- while (scanner.hasNext()) {
- try {
- String line = scanner.nextLine().trim();
- if (line.startsWith("#") || line.isEmpty()) {
- // Ignore
- continue;
- }
- if (line.startsWith("[stream")) {
- stream = Long.parseLong(line.substring(8, line.lastIndexOf(']')));
- streamSet = new HashSet<>();
- stableNodes.put(stream, streamSet);
- } else if (streamSet != null) {
- int portIndex = line.lastIndexOf(':');
- InetAddress inetAddress = InetAddress.getByName(line.substring(0, portIndex));
- int port = Integer.valueOf(line.substring(portIndex + 1));
- streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build());
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try (InputStream in = getClass().getClassLoader().getResourceAsStream("nodes.txt")) {
+ Scanner scanner = new Scanner(in);
+ long stream = 0;
+ Set streamSet = null;
+ while (scanner.hasNext()) {
+ try {
+ String line = scanner.nextLine().trim();
+ if (line.startsWith("#") || line.isEmpty()) {
+ // Ignore
+ continue;
+ }
+ if (line.startsWith("[stream")) {
+ stream = Long.parseLong(line.substring(8, line.lastIndexOf(']')));
+ streamSet = new HashSet<>();
+ stableNodes.put(stream, streamSet);
+ } else if (streamSet != null) {
+ int portIndex = line.lastIndexOf(':');
+ InetAddress inetAddress = InetAddress.getByName(line.substring(0, portIndex));
+ int port = Integer.valueOf(line.substring(portIndex + 1));
+ streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build());
+ }
+ } catch (IOException e) {
+ LOG.warn(e.getMessage(), e);
+ }
}
} catch (IOException e) {
- LOG.warn(e.getMessage(), e);
+ throw new RuntimeException(e);
}
}
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ }, "node registry initializer").start();
}
@Override
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
index 6d8970b..af7b2bc 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
@@ -28,6 +28,8 @@ public interface MessageRepository {
List
*/
- Thread synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds);
+ Future> synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds);
/**
* Start a full network node, accepting incoming connections and relaying objects.
diff --git a/domain/src/main/resources/nodes.txt b/domain/src/main/resources/nodes.txt
index e0a334e..9466a85 100644
--- a/domain/src/main/resources/nodes.txt
+++ b/domain/src/main/resources/nodes.txt
@@ -1,18 +1,8 @@
[stream 1]
-5.45.99.75:8444
-75.167.159.54:8444
-95.165.168.168:8444
-85.180.139.241:8444
-178.62.12.187:8448
-
-[2604:2000:1380:9f:82e:148b:2746:d0c7]:8080
-158.222.211.81:8080
-24.188.198.204:8111
-109.147.204.113:1195
-178.11.46.221:8444
-
dissem.ch:8444
+bootstrap8080.bitmessage.org:8080
+bootstrap8444.bitmessage.org:8444
[stream 2]
# none yet
\ No newline at end of file
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
index b2c2e40..8ed2fb4 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -235,10 +235,9 @@ public class Connection {
ObjectMessage objectMessage = (ObjectMessage) messagePayload;
try {
if (ctx.getInventory().contains(objectMessage)) {
- LOG.debug("Received object " + objectMessage.getInventoryVector() + " - already in inventory");
+ LOG.trace("Received object " + objectMessage.getInventoryVector() + " - already in inventory");
break;
}
- LOG.debug("Received object " + objectMessage.getInventoryVector());
security().checkProofOfWork(objectMessage, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
listener.receive(objectMessage);
ctx.getInventory().storeObject(objectMessage);
@@ -294,7 +293,6 @@ public class Connection {
}
public void offer(InventoryVector iv) {
- LOG.debug("Offering " + iv + " to node " + node.toString());
sendingQueue.offer(new Inv.Builder()
.addInventoryVector(iv)
.build());
@@ -321,14 +319,7 @@ public class Connection {
private synchronized void initSocket(Socket socket) throws IOException {
if (!socketInitialized) {
if (!socket.isConnected()) {
- LOG.debug("Trying to connect to node " + node);
- socket.connect(new InetSocketAddress(node.toInetAddress(), node.getPort()), CONNECT_TIMEOUT);
- }
- socket.setSoTimeout(READ_TIMEOUT);
- in = socket.getInputStream();
- out = socket.getOutputStream();
- if (!socket.isConnected()) {
- LOG.debug("Trying to connect to node " + node);
+ LOG.trace("Trying to connect to node " + node);
socket.connect(new InetSocketAddress(node.toInetAddress(), node.getPort()), CONNECT_TIMEOUT);
}
socket.setSoTimeout(READ_TIMEOUT);
@@ -359,6 +350,7 @@ public class Connection {
send(new Version.Builder().defaults().addrFrom(host).addrRecv(node).build());
}
while (state != DISCONNECTED) {
+ Thread.sleep(100);
try {
NetworkMessage msg = Factory.getNetworkMessage(version, in);
if (msg == null)
@@ -413,15 +405,13 @@ public class Connection {
if (syncFinished(null)) disconnect();
}
}
- Thread.yield();
}
- } catch (IOException | NodeException e) {
- disconnect();
- LOG.debug("Reader disconnected from node " + node + ": " + e.getMessage());
+ } catch (InterruptedException | IOException | NodeException e) {
+ LOG.trace("Reader disconnected from node " + node + ": " + e.getMessage());
} catch (RuntimeException e) {
- LOG.debug("Reader disconnecting from node " + node + " due to error: " + e.getMessage(), e);
- disconnect();
+ LOG.trace("Reader disconnecting from node " + node + " due to error: " + e.getMessage(), e);
} finally {
+ disconnect();
try {
socket.close();
} catch (Exception e) {
@@ -438,14 +428,13 @@ public class Connection {
initSocket(socket);
while (state != DISCONNECTED) {
if (sendingQueue.size() > 0) {
- LOG.debug("Sending " + sendingQueue.size() + " messages to node " + node);
send(sendingQueue.poll());
} else {
Thread.sleep(100);
}
}
} catch (IOException | InterruptedException e) {
- LOG.debug("Writer disconnected from node " + node + ": " + e.getMessage());
+ LOG.trace("Writer disconnected from node " + node + ": " + e.getMessage());
disconnect();
}
}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index 7b49978..3944378 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -32,10 +32,7 @@ import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.*;
import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT;
import static ch.dissem.bitmessage.networking.Connection.Mode.SERVER;
@@ -58,7 +55,14 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
private ConcurrentMap requestedObjects = new ConcurrentHashMap<>();
public DefaultNetworkHandler() {
- pool = Executors.newCachedThreadPool();
+ pool = Executors.newCachedThreadPool(new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = Executors.defaultThreadFactory().newThread(r);
+ thread.setPriority(Thread.MIN_PRIORITY);
+ return thread;
+ }
+ });
}
@Override
@@ -67,14 +71,12 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
}
@Override
- public Thread synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds) {
+ public Future> synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds) {
try {
Connection connection = Connection.sync(ctx, trustedHost, port, listener, timeoutInSeconds);
- Thread tr = new Thread(connection.getReader());
- Thread tw = new Thread(connection.getWriter());
- tr.start();
- tw.start();
- return tr;
+ Future> reader = pool.submit(connection.getReader());
+ pool.execute(connection.getWriter());
+ return reader;
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -143,8 +145,10 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
for (NetworkAddress address : addresses) {
startConnection(new Connection(ctx, CLIENT, address, listener, requestedObjects));
}
+ Thread.sleep(10000);
+ } else {
+ Thread.sleep(30000);
}
- Thread.sleep(30000);
} catch (InterruptedException e) {
running = false;
} catch (Exception e) {
@@ -204,7 +208,6 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
}
}
}
- LOG.debug(target.size() + " connections available to offer " + iv);
List randomSubset = Collections.selectRandom(NETWORK_MAGIC_NUMBER, target);
for (Connection connection : randomSubset) {
connection.offer(iv);
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
index 74b3ff1..9fb2ea5 100644
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -29,6 +29,7 @@ import org.junit.Test;
import org.mockito.Mockito;
import java.net.InetAddress;
+import java.util.concurrent.Future;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
@@ -116,10 +117,10 @@ public class NetworkHandlerTest {
"V1Msg.payload"
);
- Thread t = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ Future> future = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
mock(NetworkHandler.MessageListener.class),
10);
- t.join();
+ future.get();
assertInventorySize(3, nodeInventory);
assertInventorySize(3, peerInventory);
}
@@ -133,10 +134,10 @@ public class NetworkHandlerTest {
nodeInventory.init();
- Thread t = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ Future> future = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
mock(NetworkHandler.MessageListener.class),
10);
- t.join();
+ future.get();
assertInventorySize(2, nodeInventory);
assertInventorySize(2, peerInventory);
}
@@ -149,10 +150,10 @@ public class NetworkHandlerTest {
"V1Msg.payload"
);
- Thread t = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ Future> future = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
mock(NetworkHandler.MessageListener.class),
10);
- t.join();
+ future.get();
assertInventorySize(1, nodeInventory);
assertInventorySize(1, peerInventory);
}
From 99266712faeff0b392557512f73a751dd4fa7d7b Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Sat, 28 Nov 2015 20:27:05 +0100
Subject: [PATCH 27/42] Some extensions for server POW
---
.../dissem/bitmessage/BitmessageContext.java | 18 ++-
.../ch/dissem/bitmessage/InternalContext.java | 6 +
.../bitmessage/entity/BitmessageAddress.java | 2 +-
.../bitmessage/entity/CustomMessage.java | 68 +++++++++
.../bitmessage/entity/MessagePayload.java | 2 +-
.../bitmessage/entity/payload/Broadcast.java | 1 -
.../bitmessage/entity/payload/CryptoBox.java | 9 +-
.../bitmessage/factory/V3MessageFactory.java | 6 +
.../ports/CustomCommandHandler.java | 27 ++++
.../ch/dissem/bitmessage/utils/Encode.java | 16 +-
extensions/build.gradle | 36 +++++
.../extensions/CryptoCustomMessage.java | 139 ++++++++++++++++++
.../extensions/pow/ProofOfWorkRequest.java | 86 +++++++++++
.../extensions/CryptoCustomMessageTest.java | 58 ++++++++
.../bitmessage/networking/Connection.java | 6 +
.../bitmessage/repository/JdbcHelper.java | 2 +-
settings.gradle | 2 +
17 files changed, 472 insertions(+), 12 deletions(-)
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java
create mode 100644 extensions/build.gradle
create mode 100644 extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
create mode 100644 extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
create mode 100644 extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 9f4d9a3..9d4abd7 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -16,9 +16,7 @@
package ch.dissem.bitmessage;
-import ch.dissem.bitmessage.entity.BitmessageAddress;
-import ch.dissem.bitmessage.entity.ObjectMessage;
-import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.*;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
@@ -297,6 +295,7 @@ public class BitmessageContext {
ProofOfWorkEngine proofOfWorkEngine;
Security security;
MessageCallback messageCallback;
+ CustomCommandHandler customCommandHandler;
Listener listener;
int connectionLimit = 150;
long connectionTTL = 12 * HOUR;
@@ -344,6 +343,11 @@ public class BitmessageContext {
return this;
}
+ public Builder customCommandHandler(CustomCommandHandler handler) {
+ this.customCommandHandler = handler;
+ return this;
+ }
+
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
this.proofOfWorkEngine = proofOfWorkEngine;
return this;
@@ -392,6 +396,14 @@ public class BitmessageContext {
}
};
}
+ if (customCommandHandler == null) {
+ customCommandHandler = new CustomCommandHandler() {
+ @Override
+ public MessagePayload handle(CustomMessage request) {
+ throw new RuntimeException("Received custom request, but no custom command handler configured.");
+ }
+ };
+ }
return new BitmessageContext(this);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 7a89978..95cd8d8 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -50,6 +50,7 @@ public class InternalContext {
private final MessageRepository messageRepository;
private final ProofOfWorkEngine proofOfWorkEngine;
private final MessageCallback messageCallback;
+ private final CustomCommandHandler customCommandHandler;
private final TreeSet streams = new TreeSet<>();
private final int port;
@@ -69,6 +70,7 @@ public class InternalContext {
this.proofOfWorkEngine = builder.proofOfWorkEngine;
this.clientNonce = security.randomNonce();
this.messageCallback = builder.messageCallback;
+ this.customCommandHandler = builder.customCommandHandler;
this.port = builder.port;
this.connectionLimit = builder.connectionLimit;
this.connectionTTL = builder.connectionTTL;
@@ -263,6 +265,10 @@ public class InternalContext {
return connectionLimit;
}
+ public CustomCommandHandler getCustomCommandHandler() {
+ return customCommandHandler;
+ }
+
public interface ContextHolder {
void setContext(InternalContext context);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
index 0441e6e..931776c 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
@@ -87,7 +87,7 @@ public class BitmessageAddress implements Serializable {
}
}
- BitmessageAddress(Pubkey publicKey) {
+ public BitmessageAddress(Pubkey publicKey) {
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
this.pubkey = publicKey;
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
new file mode 100644
index 0000000..b31c9f5
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.entity;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import static ch.dissem.bitmessage.utils.Decode.bytes;
+
+/**
+ * @author Christian Basler
+ */
+public class CustomMessage implements MessagePayload {
+ private final byte[] data;
+
+ public CustomMessage() {
+ this.data = null;
+ }
+
+ public CustomMessage(byte[] data) {
+ this.data = data;
+ }
+
+ public static MessagePayload read(InputStream in, int length) throws IOException {
+ return new CustomMessage(bytes(in, length));
+ }
+
+ @Override
+ public Command getCommand() {
+ return Command.CUSTOM;
+ }
+
+ public byte[] getData() throws IOException {
+ if (data != null) {
+ return data;
+ } else {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ write(out);
+ return out.toByteArray();
+ }
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ if (data != null) {
+ out.write(data);
+ } else {
+ throw new RuntimeException("Tried to write custom message without data. " +
+ "Programmer: did you forget to override #write()?");
+ }
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
index e6f6f0a..994952b 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
@@ -23,6 +23,6 @@ public interface MessagePayload extends Streamable {
Command getCommand();
enum Command {
- VERSION, VERACK, ADDR, INV, GETDATA, OBJECT
+ VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
}
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
index bf5ff7f..47bf539 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
@@ -21,7 +21,6 @@ import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
-import ch.dissem.bitmessage.ports.Security;
import java.io.IOException;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
index a870b32..fe45ac5 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
@@ -38,7 +38,14 @@ public class CryptoBox implements Streamable {
private final byte[] mac;
private byte[] encrypted;
+ private long addressVersion;
+
+
public CryptoBox(Streamable data, byte[] K) throws IOException {
+ this(Encode.bytes(data), K);
+ }
+
+ public CryptoBox(byte[] data, byte[] K) throws IOException {
curveType = 0x02CA;
// 1. The destination public key is called K.
@@ -58,7 +65,7 @@ public class CryptoBox implements Streamable {
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
- encrypted = security().crypt(true, Encode.bytes(data), key_e, initializationVector);
+ encrypted = security().crypt(true, data, key_e, initializationVector);
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
mac = calculateMac(key_m);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
index 8dca6d2..9e15b3d 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
@@ -73,12 +73,18 @@ class V3MessageFactory {
return parseGetData(stream);
case "object":
return readObject(stream, length);
+ case "custom":
+ return readCustom(stream, length);
default:
LOG.debug("Unknown command: " + command);
return null;
}
}
+ private static MessagePayload readCustom(InputStream in, int length) throws IOException {
+ return CustomMessage.read(in, length);
+ }
+
public static ObjectMessage readObject(InputStream in, int length) throws IOException {
AccessCounter counter = new AccessCounter();
byte nonce[] = Decode.bytes(in, 8, counter);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java
new file mode 100644
index 0000000..8e49586
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.ports;
+
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.MessagePayload;
+
+/**
+ * @author Christian Basler
+ */
+public interface CustomCommandHandler {
+ MessagePayload handle(CustomMessage request);
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
index a78d03f..2cdc262 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
@@ -103,15 +103,23 @@ public class Encode {
inc(counter, 8);
}
- public static void varString(String value, OutputStream stream) throws IOException {
+ public static void varString(String value, OutputStream out) throws IOException {
byte[] bytes = value.getBytes("utf-8");
- // FIXME: technically, it says the length in characters, but I think this one might be correct
+ // Technically, it says the length in characters, but I think this one might be correct.
+ // It doesn't really matter, as only ASCII characters are being used.
// see also Decode#varString()
- varInt(bytes.length, stream);
- stream.write(bytes);
+ varInt(bytes.length, out);
+ out.write(bytes);
+ }
+
+ public static void varBytes(byte[] data, OutputStream out) throws IOException {
+ varInt(data.length, out);
+ out.write(data);
}
/**
+ * Serializes a {@link Streamable} object and returns the byte array.
+ *
* @param streamable the object to be serialized
* @return an array of bytes representing the given streamable object.
* @throws IOException if an I/O error occurs.
diff --git a/extensions/build.gradle b/extensions/build.gradle
new file mode 100644
index 0000000..0ae9fd4
--- /dev/null
+++ b/extensions/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.project {
+ name 'Jabit Extensions'
+ artifactId = 'jabit-extensions'
+ description 'Protocol extensions used for some extended features, e.g. server and mobile client.'
+ }
+ }
+ }
+}
+
+dependencies {
+ compile project(':domain')
+ testCompile 'junit:junit:4.11'
+ testCompile 'org.slf4j:slf4j-simple:1.7.12'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(path: ':domain', configuration: 'testArtifacts')
+ testCompile project(':security-bc')
+}
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
new file mode 100644
index 0000000..5d82f4d
--- /dev/null
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.extensions;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.Streamable;
+import ch.dissem.bitmessage.entity.payload.CryptoBox;
+import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.exception.DecryptionFailedException;
+import ch.dissem.bitmessage.factory.Factory;
+import ch.dissem.bitmessage.utils.Encode;
+
+import java.io.*;
+
+import static ch.dissem.bitmessage.utils.Decode.*;
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
+/**
+ * A {@link CustomMessage} implementation that contains signed and encrypted data.
+ *
+ * @author Christian Basler
+ */
+public class CryptoCustomMessage extends CustomMessage {
+ private final Reader dataReader;
+ private CryptoBox container;
+ private BitmessageAddress sender;
+ private T data;
+
+ public CryptoCustomMessage(T data) throws IOException {
+ this.data = data;
+ this.dataReader = null;
+ }
+
+ private CryptoCustomMessage(CryptoBox container, Reader dataReader) {
+ this.container = container;
+ this.dataReader = dataReader;
+ }
+
+ public static CryptoCustomMessage read(byte[] data, Reader dataReader) throws IOException {
+ CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data), data.length);
+ return new CryptoCustomMessage<>(cryptoBox, dataReader);
+ }
+
+ public BitmessageAddress getSender() {
+ return sender;
+ }
+
+ public void signAndEncrypt(BitmessageAddress identity, byte[] publicKey) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ Encode.varInt(identity.getVersion(), out);
+ Encode.varInt(identity.getStream(), out);
+ Encode.int32(identity.getPubkey().getBehaviorBitfield(), out);
+ out.write(identity.getPubkey().getSigningKey(), 1, 64);
+ out.write(identity.getPubkey().getEncryptionKey(), 1, 64);
+ if (identity.getVersion() >= 3) {
+ Encode.varInt(identity.getPubkey().getNonceTrialsPerByte(), out);
+ Encode.varInt(identity.getPubkey().getExtraBytes(), out);
+ }
+
+ data.write(out);
+ Encode.varBytes(security().getSignature(out.toByteArray(), identity.getPrivateKey()), out);
+ container = new CryptoBox(out.toByteArray(), publicKey);
+ }
+
+ public T decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
+ SignatureCheckingInputStream in = new SignatureCheckingInputStream(container.decrypt(privateKey));
+
+ long addressVersion = varInt(in);
+ long stream = varInt(in);
+ int behaviorBitfield = int32(in);
+ byte[] publicSigningKey = bytes(in, 64);
+ byte[] publicEncryptionKey = bytes(in, 64);
+ long nonceTrialsPerByte = addressVersion >= 3 ? varInt(in) : 0;
+ long extraBytes = addressVersion >= 3 ? varInt(in) : 0;
+
+ sender = new BitmessageAddress(Factory.createPubkey(
+ addressVersion,
+ stream,
+ publicSigningKey,
+ publicEncryptionKey,
+ nonceTrialsPerByte,
+ extraBytes,
+ behaviorBitfield
+ ));
+
+ data = dataReader.read(sender, in);
+
+ in.checkSignature(sender.getPubkey());
+
+ return data;
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ container.write(out);
+ }
+
+ public interface Reader {
+ T read(BitmessageAddress sender, InputStream in) throws IOException;
+ }
+
+ private class SignatureCheckingInputStream extends InputStream {
+ private final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ private final InputStream wrapped;
+
+ private SignatureCheckingInputStream(InputStream wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public int read() throws IOException {
+ int read = wrapped.read();
+ if (read >= 0) out.write(read);
+ return read;
+ }
+
+ public void checkSignature(Pubkey pubkey) throws IOException, RuntimeException {
+ if (!security().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) {
+ throw new RuntimeException("Signature check failed");
+ }
+ }
+ }
+}
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
new file mode 100644
index 0000000..b247b59
--- /dev/null
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.extensions.pow;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.Streamable;
+import ch.dissem.bitmessage.utils.Encode;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import static ch.dissem.bitmessage.utils.Decode.*;
+
+/**
+ * @author Christian Basler
+ */
+public class ProofOfWorkRequest implements Streamable {
+ private final BitmessageAddress sender;
+ private final byte[] initialHash;
+ private final Request request;
+ private final byte[] data;
+
+ private ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request, byte[] data) {
+ this.sender = sender;
+ this.initialHash = initialHash;
+ this.request = request;
+ this.data = data;
+ }
+
+ public static ProofOfWorkRequest read(BitmessageAddress client, InputStream in) throws IOException {
+ return new ProofOfWorkRequest(
+ client,
+ bytes(in, 64),
+ Request.valueOf(varString(in)),
+ varBytes(in)
+ );
+ }
+
+ public BitmessageAddress getSender() {
+ return sender;
+ }
+
+ public byte[] getInitialHash() {
+ return initialHash;
+ }
+
+ public Request getRequest() {
+ return request;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ out.write(initialHash);
+ Encode.varString(request.name(), out);
+ Encode.varBytes(data, out);
+ }
+
+ public enum Request {
+ CALCULATE,
+ QUERY,
+ ERROR,
+ OK,
+ QUEUED,
+ CALCULATING,
+ COMPLETE
+ }
+}
diff --git a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
new file mode 100644
index 0000000..98e97a1
--- /dev/null
+++ b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.extensions;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.payload.GenericPayload;
+import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
+import ch.dissem.bitmessage.utils.TestBase;
+import ch.dissem.bitmessage.utils.TestUtils;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static ch.dissem.bitmessage.utils.Singleton.security;
+import static org.junit.Assert.assertEquals;
+
+public class CryptoCustomMessageTest extends TestBase {
+ @Test
+ public void testEncryptThenDecrypt() throws Exception {
+ PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
+ BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey);
+
+ GenericPayload payloadBefore = new GenericPayload(0, 1, security().randomBytes(100));
+ CryptoCustomMessage messageBefore = new CryptoCustomMessage<>(payloadBefore);
+ messageBefore.signAndEncrypt(sendingIdentity, security().createPublicKey(sendingIdentity.getPublicDecryptionKey()));
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ messageBefore.write(out);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+
+ CryptoCustomMessage messageAfter = CryptoCustomMessage.read(out.toByteArray(), new CryptoCustomMessage.Reader() {
+ @Override
+ public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException {
+ return GenericPayload.read(0, in, 1, 100);
+ }
+ });
+ GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey());
+
+ assertEquals(payloadBefore, payloadAfter);
+ }
+}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
index 8ed2fb4..a95adba 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -259,6 +259,12 @@ public class Connection {
LOG.debug("Received " + addr.getAddresses().size() + " addresses.");
ctx.getNodeRegistry().offerAddresses(addr.getAddresses());
break;
+ case CUSTOM:
+ MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) messagePayload);
+ if (response != null) {
+ send(response);
+ }
+ break;
case VERACK:
case VERSION:
throw new RuntimeException("Unexpectedly received '" + messagePayload.getCommand() + "' command");
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
index 601ce29..d583a71 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
@@ -31,7 +31,7 @@ import static ch.dissem.bitmessage.utils.Strings.hex;
/**
* Helper class that does Flyway migration, provides JDBC connections and some helper methods.
*/
-abstract class JdbcHelper {
+public abstract class JdbcHelper {
private static final Logger LOG = LoggerFactory.getLogger(JdbcHelper.class);
protected final JdbcConfig config;
diff --git a/settings.gradle b/settings.gradle
index 2d3e1f6..adecd45 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,3 +13,5 @@ include 'wif'
include 'security-sc'
include 'security-bc'
+
+include 'extensions'
\ No newline at end of file
From 991a0e5f869a6c6087901ff405175e2039e69457 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Wed, 2 Dec 2015 17:45:50 +0100
Subject: [PATCH 28/42] Some improvements for custom message handling
---
.../bitmessage/entity/CustomMessage.java | 13 ++++++---
.../bitmessage/ports/NetworkHandler.java | 14 ++++++++-
.../extensions/pow/ProofOfWorkRequest.java | 12 ++++----
gradle/wrapper/gradle-wrapper.properties | 2 +-
.../bitmessage/networking/Connection.java | 12 ++++----
.../networking/DefaultNetworkHandler.java | 29 +++++++++++++++++--
.../bitmessage/wif/WifExporterTest.java | 16 +++++-----
7 files changed, 72 insertions(+), 26 deletions(-)
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
index b31c9f5..63f9663 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -16,10 +16,7 @@
package ch.dissem.bitmessage.entity;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.*;
import static ch.dissem.bitmessage.utils.Decode.bytes;
@@ -65,4 +62,12 @@ public class CustomMessage implements MessagePayload {
"Programmer: did you forget to override #write()?");
}
}
+
+ public static CustomMessage error(String message) {
+ try {
+ return new CustomMessage(("ERROR\n" + message).getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
index e2fd170..909d3dd 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
@@ -16,6 +16,7 @@
package ch.dissem.bitmessage.ports;
+import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.utils.Property;
@@ -34,7 +35,18 @@ public interface NetworkHandler {
* An implementation should disconnect if either the timeout is reached or the returned thread is interrupted.
*
*/
- Future> synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds);
+ Future> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds);
+
+ /**
+ * Send a custom message to a specific node (that should implement handling for this message type) and returns
+ * the response, which in turn is expected to be a {@link CustomMessage}.
+ *
+ * @param server the node's address
+ * @param port the node's port
+ * @param request the request
+ * @return the response
+ */
+ CustomMessage send(InetAddress server, int port, CustomMessage request);
/**
* Start a full network node, accepting incoming connections and relaying objects.
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
index b247b59..2ef2f9e 100644
--- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
import static ch.dissem.bitmessage.utils.Decode.*;
/**
@@ -35,7 +36,11 @@ public class ProofOfWorkRequest implements Streamable {
private final Request request;
private final byte[] data;
- private ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request, byte[] data) {
+ public ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request) {
+ this(sender, initialHash, request, new byte[0]);
+ }
+
+ public ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request, byte[] data) {
this.sender = sender;
this.initialHash = initialHash;
this.request = request;
@@ -76,11 +81,8 @@ public class ProofOfWorkRequest implements Streamable {
public enum Request {
CALCULATE,
- QUERY,
- ERROR,
- OK,
- QUEUED,
CALCULATING,
+ QUERY,
COMPLETE
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 66e6c70..94f382d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
index a95adba..4d14fe2 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -260,11 +260,6 @@ public class Connection {
ctx.getNodeRegistry().offerAddresses(addr.getAddresses());
break;
case CUSTOM:
- MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) messagePayload);
- if (response != null) {
- send(response);
- }
- break;
case VERACK:
case VERSION:
throw new RuntimeException("Unexpectedly received '" + messagePayload.getCommand() + "' command");
@@ -400,6 +395,13 @@ public class Connection {
break;
}
break;
+ case CUSTOM:
+ MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) msg.getPayload());
+ if (response != null) {
+ send(response);
+ }
+ disconnect();
+ break;
default:
throw new NodeException("Command 'version' or 'verack' expected, but was '"
+ msg.getPayload().getCommand() + "'");
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index 3944378..e934bdf 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -18,8 +18,12 @@ package ch.dissem.bitmessage.networking;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.InternalContext.ContextHolder;
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
+import ch.dissem.bitmessage.exception.NodeException;
+import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.NetworkHandler;
import ch.dissem.bitmessage.utils.Collections;
import ch.dissem.bitmessage.utils.Property;
@@ -71,9 +75,9 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
}
@Override
- public Future> synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds) {
+ public Future> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds) {
try {
- Connection connection = Connection.sync(ctx, trustedHost, port, listener, timeoutInSeconds);
+ Connection connection = Connection.sync(ctx, server, port, listener, timeoutInSeconds);
Future> reader = pool.submit(connection.getReader());
pool.execute(connection.getWriter());
return reader;
@@ -82,6 +86,27 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
}
}
+ @Override
+ public CustomMessage send(InetAddress server, int port, CustomMessage request) {
+ try (Socket socket = new Socket(server, port)) {
+ socket.setSoTimeout(Connection.READ_TIMEOUT);
+ new NetworkMessage(request).write(socket.getOutputStream());
+ NetworkMessage networkMessage = Factory.getNetworkMessage(3, socket.getInputStream());
+ if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) {
+ return (CustomMessage) networkMessage.getPayload();
+ } else {
+ if (networkMessage == null) {
+ throw new NodeException("No response from node " + server);
+ } else {
+ throw new NodeException("Unexpected response from node " +
+ server + ": " + networkMessage.getPayload().getCommand());
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Override
public void start(final MessageListener listener) {
if (listener == null) {
diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java
index 5ed9025..a2ee560 100644
--- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java
+++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java
@@ -72,14 +72,14 @@ public class WifExporterTest {
@Test
public void testAddIdentity() throws Exception {
- String expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]\n" +
- "label = Nuked Address\n" +
- "enabled = true\n" +
- "decoy = false\n" +
- "noncetrialsperbyte = 320\n" +
- "payloadlengthextrabytes = 14000\n" +
- "privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9\n" +
- "privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck\n\n";
+ String expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]" + System.lineSeparator() +
+ "label = Nuked Address" + System.lineSeparator() +
+ "enabled = true" + System.lineSeparator() +
+ "decoy = false" + System.lineSeparator() +
+ "noncetrialsperbyte = 320" + System.lineSeparator() +
+ "payloadlengthextrabytes = 14000" + System.lineSeparator() +
+ "privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9" + System.lineSeparator() +
+ "privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck" + System.lineSeparator() + System.lineSeparator();
importer = new WifImporter(ctx, expected);
exporter.addIdentity(importer.getIdentities().get(0));
assertEquals(expected, exporter.toString());
From ab6a3c56dd2a72d3cfc28bb5aeea3a573e7ab183 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Tue, 8 Dec 2015 20:27:32 +0100
Subject: [PATCH 29/42] The POW callback is now a service and its state stored.
The proof of work engine therefore just has to remember its initial hash
making server based POW easier.
---
.../dissem/bitmessage/demo/Application.java | 6 +-
.../java/ch/dissem/bitmessage/demo/Main.java | 1 +
.../dissem/bitmessage/BitmessageContext.java | 32 +++++----
.../bitmessage/DefaultMessageListener.java | 16 ++---
.../ch/dissem/bitmessage/InternalContext.java | 69 +++++--------------
.../dissem/bitmessage/ProofOfWorkService.java | 62 +++++++++++++++++
.../bitmessage/entity/CustomMessage.java | 23 +++++--
.../bitmessage/entity/ObjectMessage.java | 6 +-
.../dissem/bitmessage/entity/Plaintext.java | 9 +++
.../bitmessage/ports/AbstractSecurity.java | 9 ++-
.../bitmessage/ports/MessageRepository.java | 2 +
.../ports/MultiThreadedPOWEngine.java | 6 +-
.../bitmessage/ports/ProofOfWorkEngine.java | 2 +-
.../ports/ProofOfWorkRepository.java | 16 +++++
.../ch/dissem/bitmessage/ports/Security.java | 2 +
.../bitmessage/ports/SimplePOWEngine.java | 2 +-
.../ch/dissem/bitmessage/utils/Decode.java | 8 ++-
.../ch/dissem/bitmessage/utils/Numbers.java | 10 +++
.../ports/ProofOfWorkEngineTest.java | 4 +-
.../extensions/CryptoCustomMessage.java | 3 +
.../repository/JdbcMessageRepository.java | 26 +++++--
.../repository/JdbcProofOfWorkRepository.java | 69 +++++++++++++++++++
.../migration/V2.0__Update_table_message.sql | 2 +
.../db/migration/V2.1__Create_table_POW.sql | 5 ++
.../bitmessage/security/SecurityTest.java | 2 +-
25 files changed, 289 insertions(+), 103 deletions(-)
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/utils/Numbers.java
create mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java
create mode 100644 repositories/src/main/resources/db/migration/V2.0__Update_table_message.sql
create mode 100644 repositories/src/main/resources/db/migration/V2.1__Create_table_POW.sql
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
index a065de9..72da50d 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
@@ -22,10 +22,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
-import ch.dissem.bitmessage.repository.JdbcAddressRepository;
-import ch.dissem.bitmessage.repository.JdbcConfig;
-import ch.dissem.bitmessage.repository.JdbcInventory;
-import ch.dissem.bitmessage.repository.JdbcMessageRepository;
+import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,6 +47,7 @@ public class Application {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
+ .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.networkHandler(new DefaultNetworkHandler())
.security(new BouncySecurity())
.port(48444)
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
index ac90e88..6dbfc14 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
@@ -51,6 +51,7 @@ public class Main {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
+ .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.networkHandler(new DefaultNetworkHandler())
.security(new BouncySecurity())
.port(48444)
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 9d4abd7..def0a4f 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -75,7 +75,7 @@ public class BitmessageContext {
}
public AddressRepository addresses() {
- return ctx.getAddressRepo();
+ return ctx.getAddressRepository();
}
public MessageRepository messages() {
@@ -90,7 +90,7 @@ public class BitmessageContext {
ctx.getNetworkExtraBytes(),
features
));
- ctx.getAddressRepo().save(identity);
+ ctx.getAddressRepository().save(identity);
pool.submit(new Runnable() {
@Override
public void run() {
@@ -102,6 +102,7 @@ public class BitmessageContext {
public void addDistributedMailingList(String address, String alias) {
// TODO
+ throw new RuntimeException("not implemented");
}
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
@@ -120,9 +121,7 @@ public class BitmessageContext {
from,
from,
Factory.getBroadcast(from, msg),
- +2 * DAY,
- 0,
- 0
+ +2 * DAY
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
@@ -159,9 +158,7 @@ public class BitmessageContext {
from,
to,
new Msg(msg),
- +2 * DAY,
- ctx.getNonceTrialsPerByte(to),
- ctx.getExtraBytes(to)
+ +2 * DAY
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
@@ -176,9 +173,7 @@ public class BitmessageContext {
requestingIdentity,
address,
new GetPubkey(address),
- +28 * DAY,
- ctx.getNetworkNonceTrialsPerByte(),
- ctx.getNetworkExtraBytes()
+ +28 * DAY
);
}
@@ -220,7 +215,7 @@ public class BitmessageContext {
}
public void addContact(BitmessageAddress contact) {
- ctx.getAddressRepo().save(contact);
+ ctx.getAddressRepository().save(contact);
tryToFindMatchingPubkey(contact);
if (contact.getPubkey() == null) {
ctx.requestPubkey(contact);
@@ -237,7 +232,7 @@ public class BitmessageContext {
v4Pubkey.decrypt(address.getPublicDecryptionKey());
if (object.isSignatureValid(v4Pubkey)) {
address.setPubkey(v4Pubkey);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
break;
} else {
LOG.info("Found pubkey for " + address + " but signature is invalid");
@@ -246,7 +241,7 @@ public class BitmessageContext {
} else {
if (Arrays.equals(pubkey.getRipe(), address.getRipe())) {
address.setPubkey(pubkey);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
break;
}
}
@@ -258,7 +253,7 @@ public class BitmessageContext {
public void addSubscribtion(BitmessageAddress address) {
address.setSubscribed(true);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
tryToFindBroadcastsForAddress(address);
}
@@ -292,6 +287,7 @@ public class BitmessageContext {
NetworkHandler networkHandler;
AddressRepository addressRepo;
MessageRepository messageRepo;
+ ProofOfWorkRepository proofOfWorkRepository;
ProofOfWorkEngine proofOfWorkEngine;
Security security;
MessageCallback messageCallback;
@@ -333,6 +329,11 @@ public class BitmessageContext {
return this;
}
+ public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) {
+ this.proofOfWorkRepository = proofOfWorkRepository;
+ return this;
+ }
+
public Builder security(Security security) {
this.security = security;
return this;
@@ -374,6 +375,7 @@ public class BitmessageContext {
nonNull("networkHandler", networkHandler);
nonNull("addressRepo", addressRepo);
nonNull("messageRepo", messageRepo);
+ nonNull("proofOfWorkRepo", proofOfWorkRepository);
if (proofOfWorkEngine == null) {
proofOfWorkEngine = new MultiThreadedPOWEngine();
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index e069704..eb22f03 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -69,7 +69,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
- BitmessageAddress identity = ctx.getAddressRepo().findIdentity(getPubkey.getRipeTag());
+ BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
if (identity != null && identity.getPrivateKey() != null) {
LOG.info("Got pubkey request for identity " + identity);
// FIXME: only send pubkey if it wasn't sent in the last 28 days
@@ -82,17 +82,17 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
try {
if (pubkey instanceof V4Pubkey) {
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
- address = ctx.getAddressRepo().findContact(v4Pubkey.getTag());
+ address = ctx.getAddressRepository().findContact(v4Pubkey.getTag());
if (address != null) {
v4Pubkey.decrypt(address.getPublicDecryptionKey());
}
} else {
- address = ctx.getAddressRepo().findContact(pubkey.getRipe());
+ address = ctx.getAddressRepository().findContact(pubkey.getRipe());
}
if (address != null) {
address.setPubkey(pubkey);
LOG.info("Got pubkey for contact " + address);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
List messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
LOG.info("Sending " + messages.size() + " messages for contact " + address);
for (Plaintext msg : messages) {
@@ -102,9 +102,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
msg.getFrom(),
msg.getTo(),
new Msg(msg),
- +2 * DAY,
- ctx.getNonceTrialsPerByte(msg.getTo()),
- ctx.getExtraBytes(msg.getTo())
+ +2 * DAY
);
msg.setStatus(SENT);
ctx.getMessageRepository().save(msg);
@@ -115,7 +113,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
protected void receive(ObjectMessage object, Msg msg) throws IOException {
- for (BitmessageAddress identity : ctx.getAddressRepo().getIdentities()) {
+ for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) {
try {
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
msg.getPlaintext().setTo(identity);
@@ -136,7 +134,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException {
byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null;
- for (BitmessageAddress subscription : ctx.getAddressRepo().getSubscriptions(broadcast.getVersion())) {
+ for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) {
if (tag != null && !Arrays.equals(tag, subscription.getTag())) {
continue;
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 95cd8d8..d139de5 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -48,9 +48,11 @@ public class InternalContext {
private final NetworkHandler networkHandler;
private final AddressRepository addressRepository;
private final MessageRepository messageRepository;
+ private final ProofOfWorkRepository proofOfWorkRepository;
private final ProofOfWorkEngine proofOfWorkEngine;
private final MessageCallback messageCallback;
private final CustomCommandHandler customCommandHandler;
+ private final ProofOfWorkService proofOfWorkService;
private final TreeSet streams = new TreeSet<>();
private final int port;
@@ -67,6 +69,8 @@ public class InternalContext {
this.networkHandler = builder.networkHandler;
this.addressRepository = builder.addressRepo;
this.messageRepository = builder.messageRepo;
+ this.proofOfWorkRepository = builder.proofOfWorkRepository;
+ this.proofOfWorkService = new ProofOfWorkService();
this.proofOfWorkEngine = builder.proofOfWorkEngine;
this.clientNonce = security.randomNonce();
this.messageCallback = builder.messageCallback;
@@ -88,7 +92,9 @@ public class InternalContext {
streams.add(1L);
}
- init(security, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
+ init(security, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
+ proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine,
+ messageCallback, customCommandHandler);
for (BitmessageAddress identity : addressRepository.getIdentities()) {
streams.add(identity.getStream());
}
@@ -118,7 +124,7 @@ public class InternalContext {
return networkHandler;
}
- public AddressRepository getAddressRepo() {
+ public AddressRepository getAddressRepository() {
return addressRepository;
}
@@ -126,6 +132,10 @@ public class InternalContext {
return messageRepository;
}
+ public ProofOfWorkRepository getProofOfWorkRepository() {
+ return proofOfWorkRepository;
+ }
+
public ProofOfWorkEngine getProofOfWorkEngine() {
return proofOfWorkEngine;
}
@@ -147,22 +157,12 @@ public class InternalContext {
return networkNonceTrialsPerByte;
}
- public long getNonceTrialsPerByte(BitmessageAddress address) {
- long nonceTrialsPerByte = address.getPubkey().getNonceTrialsPerByte();
- return networkNonceTrialsPerByte > nonceTrialsPerByte ? networkNonceTrialsPerByte : nonceTrialsPerByte;
- }
-
public long getNetworkExtraBytes() {
return networkExtraBytes;
}
- public long getExtraBytes(BitmessageAddress address) {
- long extraBytes = address.getPubkey().getExtraBytes();
- return networkExtraBytes > extraBytes ? networkExtraBytes : extraBytes;
- }
-
public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
- final long timeToLive, final long nonceTrialsPerByte, final long extraBytes) {
+ final long timeToLive) {
try {
if (to == null) to = from;
long expires = UnixTime.now(+timeToLive);
@@ -181,22 +181,7 @@ public class InternalContext {
object.encrypt(to.getPubkey());
}
messageCallback.proofOfWorkStarted(payload);
- security.doProofOfWork(object, nonceTrialsPerByte, extraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- object.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(payload);
- if (payload instanceof PlaintextHolder) {
- Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
- plaintext.setInventoryVector(object.getInventoryVector());
- messageRepository.save(plaintext);
- }
- inventory.storeObject(object);
- networkHandler.offer(object.getInventoryVector());
- messageCallback.messageOffered(payload, object.getInventoryVector());
- }
- });
+ proofOfWorkService.doProofOfWork(to, object);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -214,18 +199,8 @@ public class InternalContext {
response.sign(identity.getPrivateKey());
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
messageCallback.proofOfWorkStarted(identity.getPubkey());
- security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- response.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(identity.getPubkey());
- inventory.storeObject(response);
- networkHandler.offer(response.getInventoryVector());
- // TODO: save that the pubkey was just sent, and on which stream!
- messageCallback.messageOffered(identity.getPubkey(), response.getInventoryVector());
- }
- });
+ // TODO: remember that the pubkey is just about to be sent, and on which stream!
+ proofOfWorkService.doProofOfWork(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -240,17 +215,7 @@ public class InternalContext {
.payload(new GetPubkey(contact))
.build();
messageCallback.proofOfWorkStarted(response.getPayload());
- security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- response.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(response.getPayload());
- inventory.storeObject(response);
- networkHandler.offer(response.getInventoryVector());
- messageCallback.messageOffered(response.getPayload(), response.getInventoryVector());
- }
- });
+ proofOfWorkService.doProofOfWork(response);
}
public long getClientNonce() {
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
new file mode 100644
index 0000000..3cf46ef
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -0,0 +1,62 @@
+package ch.dissem.bitmessage;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.PlaintextHolder;
+import ch.dissem.bitmessage.ports.MessageRepository;
+import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
+import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
+import ch.dissem.bitmessage.ports.Security;
+
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
+/**
+ * @author Christian Basler
+ */
+public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
+ private Security security;
+ private InternalContext ctx;
+ private ProofOfWorkRepository powRepo;
+ private MessageRepository messageRepo;
+
+ public void doProofOfWork(ObjectMessage object) {
+ doProofOfWork(null, object);
+ }
+
+ public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
+ long nonceTrialsPerByte = recipient == null ? 0 : recipient.getPubkey().getNonceTrialsPerByte();
+ long extraBytes = recipient == null ? 0 : recipient.getPubkey().getExtraBytes();
+
+ powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
+ if (object.getPayload() instanceof PlaintextHolder){
+ Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
+ plaintext.setInitialHash(security.getInitialHash(object));
+ messageRepo.save(plaintext);
+ }
+ security.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this);
+ }
+
+ @Override
+ public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
+ ObjectMessage object = powRepo.getObject(initialHash);
+ object.setNonce(nonce);
+// messageCallback.proofOfWorkCompleted(payload);
+ Plaintext plaintext = messageRepo.getMessage(initialHash);
+ if (plaintext != null) {
+ plaintext.setInventoryVector(object.getInventoryVector());
+ messageRepo.save(plaintext);
+ }
+ ctx.getInventory().storeObject(object);
+ ctx.getNetworkHandler().offer(object.getInventoryVector());
+// messageCallback.messageOffered(payload, object.getInventoryVector());
+ }
+
+ @Override
+ public void setContext(InternalContext ctx) {
+ this.ctx = ctx;
+ this.security = security();
+ this.powRepo = ctx.getProofOfWorkRepository();
+ this.messageRepo = ctx.getMessageRepository();
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
index 63f9663..a5caf3a 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -16,26 +16,36 @@
package ch.dissem.bitmessage.entity;
+import ch.dissem.bitmessage.utils.AccessCounter;
+import ch.dissem.bitmessage.utils.Encode;
+
import java.io.*;
import static ch.dissem.bitmessage.utils.Decode.bytes;
+import static ch.dissem.bitmessage.utils.Decode.varString;
/**
* @author Christian Basler
*/
public class CustomMessage implements MessagePayload {
+ public static final String COMMAND_ERROR = "ERROR";
+
+ private final String command;
private final byte[] data;
- public CustomMessage() {
+ public CustomMessage(String command) {
+ this.command = command;
this.data = null;
}
- public CustomMessage(byte[] data) {
+ public CustomMessage(String command, byte[] data) {
+ this.command = command;
this.data = data;
}
public static MessagePayload read(InputStream in, int length) throws IOException {
- return new CustomMessage(bytes(in, length));
+ AccessCounter counter = new AccessCounter();
+ return new CustomMessage(varString(in, counter), bytes(in, length - counter.length()));
}
@Override
@@ -56,6 +66,7 @@ public class CustomMessage implements MessagePayload {
@Override
public void write(OutputStream out) throws IOException {
if (data != null) {
+ Encode.varString(command, out);
out.write(data);
} else {
throw new RuntimeException("Tried to write custom message without data. " +
@@ -63,9 +74,13 @@ public class CustomMessage implements MessagePayload {
}
}
+ public boolean isError() {
+ return COMMAND_ERROR.equals(command);
+ }
+
public static CustomMessage error(String message) {
try {
- return new CustomMessage(("ERROR\n" + message).getBytes("UTF-8"));
+ return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
index 128084e..9e89c42 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
@@ -156,7 +156,11 @@ public class ObjectMessage implements MessagePayload {
@Override
public void write(OutputStream out) throws IOException {
- out.write(nonce);
+ if (nonce != null) {
+ out.write(nonce);
+ } else {
+ out.write(new byte[8]);
+ }
out.write(getPayloadBytesWithoutNonce());
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index eb0a60f..fbd5d48 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -44,6 +44,7 @@ public class Plaintext implements Streamable {
private Long received;
private Set