diff --git a/.travis.yml b/.travis.yml
index a98b760..9bcf999 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,2 +1,3 @@
language: java
-
+jdk:
+ - oraclejdk8
diff --git a/Bitmessage.uml b/Bitmessage.uml
index 997ab34..598f220 100644
--- a/Bitmessage.uml
+++ b/Bitmessage.uml
@@ -3,325 +3,1306 @@
JAVA
- ch.dissem.bitmessage.entity.Encrypted
- ch.dissem.bitmessage.ports.Inventory
- ch.dissem.bitmessage.entity.payload.V4Pubkey
- ch.dissem.bitmessage.entity.Addr
- ch.dissem.bitmessage.entity.payload.Broadcast
- ch.dissem.bitmessage.factory.Factory
- ch.dissem.bitmessage.entity.valueobject.NetworkAddress
- ch.dissem.bitmessage.entity.payload.V2Pubkey
- ch.dissem.bitmessage.ports.AddressRepository
- ch.dissem.bitmessage.entity.payload.V3Pubkey
- ch.dissem.bitmessage.entity.payload.ObjectPayload
- ch.dissem.bitmessage.entity.MessagePayload
- ch.dissem.bitmessage.entity.NetworkMessage
- ch.dissem.bitmessage.entity.Version
- ch.dissem.bitmessage.BitmessageContext
- ch.dissem.bitmessage.ports.ProofOfWorkEngine
- ch.dissem.bitmessage.entity.BitmessageAddress
- ch.dissem.bitmessage.entity.payload.UnencryptedMessage
- ch.dissem.bitmessage.factory.V3MessageFactory
- ch.dissem.bitmessage.entity.payload.CryptoBox
- ch.dissem.bitmessage.entity.valueobject.InventoryVector
- ch.dissem.bitmessage.entity.payload.V5Broadcast
- ch.dissem.bitmessage.entity.valueobject.PrivateKey
- ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
- ch.dissem.bitmessage.entity.Inv
- ch.dissem.bitmessage.entity.payload.Pubkey
- ch.dissem.bitmessage.entity.payload.GetPubkey
- ch.dissem.bitmessage.entity.Streamable
- ch.dissem.bitmessage.entity.payload.ObjectType
- ch.dissem.bitmessage.entity.ObjectMessage
- ch.dissem.bitmessage.entity.payload.GenericPayload
- ch.dissem.bitmessage.ports.NetworkHandler
- ch.dissem.bitmessage.entity.VerAck
- ch.dissem.bitmessage.entity.GetData
- ch.dissem.bitmessage.entity.payload.Msg
- ch.dissem.bitmessage.ports.NodeRegistry
- ch.dissem.bitmessage.entity.payload.V4Broadcast
+ ch.dissem.bitmessage.entity.valueobject.Label.Type
+ ch.dissem.bitmessage.networking.Connection.WriterRunnable
+ ch.dissem.bitmessage.entity.valueobject.NetworkAddress
+ ch.dissem.bitmessage.factory.V3MessageFactory
+ ch.dissem.bitmessage.ProofOfWorkService
+ ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
+ ch.dissem.bitmessage.entity.ObjectMessage
+ ch.dissem.bitmessage.repository.JdbcHelper
+ ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
+ ch.dissem.bitmessage.utils.AccessCounter
+ ch.dissem.bitmessage.MessageCallback
+ ch.dissem.bitmessage.networking.Connection.ReaderRunnable
+ ch.dissem.bitmessage.entity.payload.V2Pubkey
+ ch.dissem.bitmessage.extensions.CryptoCustomMessage.SignatureCheckingInputStream
+ ch.dissem.bitmessage.entity.Plaintext
+ ch.dissem.bitmessage.InternalContext
+ ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
+ ch.dissem.bitmessage.ports.NetworkHandler.MessageListener
+ ch.dissem.bitmessage.entity.payload.Msg
+ ch.dissem.bitmessage.networking.Connection.Mode
+ ch.dissem.bitmessage.utils.Singleton
+ ch.dissem.bitmessage.ports.Cryptography
+ ch.dissem.bitmessage.ports.ProofOfWorkRepository
+ ch.dissem.bitmessage.repository.JdbcMessageRepository
+ ch.dissem.bitmessage.repository.JdbcInventory
+ ch.dissem.bitmessage.exception.NodeException
+ ch.dissem.bitmessage.entity.payload.GetPubkey
+ ch.dissem.bitmessage.entity.GetData
+ ch.dissem.bitmessage.entity.Addr
+ ch.dissem.bitmessage.InternalContext.ContextHolder
+ ch.dissem.bitmessage.entity.CustomMessage
+ ch.dissem.bitmessage.DefaultMessageListener
+ ch.dissem.bitmessage.ports.MultiThreadedPOWEngine.Worker
+ ch.dissem.bitmessage.ports.MultiThreadedPOWEngine.CallbackWrapper
+ ch.dissem.bitmessage.ports.CustomCommandHandler
+ ch.dissem.bitmessage.utils.Property
+ ch.dissem.bitmessage.repository.JdbcAddressRepository
+ ch.dissem.bitmessage.BitmessageContext
+ ch.dissem.bitmessage.entity.VerAck
+ ch.dissem.bitmessage.repository.JdbcConfig
+ ch.dissem.bitmessage.ports.MemoryNodeRegistry
+ ch.dissem.bitmessage.entity.valueobject.InventoryVector
+ ch.dissem.bitmessage.entity.payload.V4Pubkey
+ ch.dissem.bitmessage.entity.payload.V5Broadcast
+ ch.dissem.bitmessage.entity.Inv
+ ch.dissem.bitmessage.ports.AddressRepository
+ ch.dissem.bitmessage.entity.payload.Pubkey
+ ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request
+ ch.dissem.bitmessage.ports.MessageRepository
+ ch.dissem.bitmessage.wif.WifExporter
+ ch.dissem.bitmessage.entity.valueobject.PrivateKey
+ ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
+ ch.dissem.bitmessage.extensions.CryptoCustomMessage.Reader
+ ch.dissem.bitmessage.entity.payload.V3Pubkey
+ ch.dissem.bitmessage.entity.payload.Pubkey.Feature
+ ch.dissem.bitmessage.ports.NetworkHandler
+ ch.dissem.bitmessage.ports.AbstractCryptography
+ ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
+ ch.dissem.bitmessage.ports.SimplePOWEngine
+ ch.dissem.bitmessage.ports.NodeRegistry
+ ch.dissem.bitmessage.wif.WifImporter
+ ch.dissem.bitmessage.entity.MessagePayload.Command
+ ch.dissem.bitmessage.entity.NetworkMessage
+ ch.dissem.bitmessage.entity.Plaintext.Encoding
+ ch.dissem.bitmessage.entity.Plaintext.Type
+ ch.dissem.bitmessage.entity.payload.CryptoBox
+ ch.dissem.bitmessage.factory.Factory
+ ch.dissem.bitmessage.networking.DefaultNetworkHandler
+ ch.dissem.bitmessage.repository.JdbcProofOfWorkRepository
+ ch.dissem.bitmessage.entity.BitmessageAddress
+ ch.dissem.bitmessage.networking.Connection.State
+ ch.dissem.bitmessage.entity.payload.V4Broadcast
+ ch.dissem.bitmessage.entity.Encrypted
+ ch.dissem.bitmessage.networking.Connection
+ ch.dissem.bitmessage.ports.ProofOfWorkEngine
+ ch.dissem.bitmessage.entity.MessagePayload
+ ch.dissem.bitmessage.entity.Streamable
+ ch.dissem.bitmessage.BitmessageContext.Listener
+ ch.dissem.bitmessage.ports.Inventory
+ ch.dissem.bitmessage.ports.ProofOfWorkEngine.Callback
+ ch.dissem.bitmessage.entity.payload.GenericPayload
+ ch.dissem.bitmessage.entity.Version
+ ch.dissem.bitmessage.entity.payload.ObjectPayload
+ ch.dissem.bitmessage.entity.payload.Broadcast
+ ch.dissem.bitmessage.extensions.CryptoCustomMessage
+ ch.dissem.bitmessage.exception.DecryptionFailedException
+ ch.dissem.bitmessage.entity.Plaintext.Status
+ ch.dissem.bitmessage.entity.PlaintextHolder
+ ch.dissem.bitmessage.entity.valueobject.Label
+ ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
- Fields
- Methods
- Properties
+ Inner Classes
- All
+ Production
protected
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..34c84ba
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+# Contributing
+
+We love pull requests from everyone. Please be nice and forgive us
+if we can't process your request right away.
+
+Fork, then clone the repo:
+
+ git clone git@github.com:your-username/Jabit.git
+
+Make sure the tests pass:
+
+ ./gradlew test
+
+Make your change. Add tests for your change. Make the tests pass:
+
+ ./gradlew test
+
+Push to your fork and [submit a pull request][pr].
+
+[pr]: https://github.com/Dissem/Jabit/compare/
+
+Unfortunately we can't always answer right away, so we ask you to have
+some patience. Then we may suggest some changes or improvements or
+alternatives.
diff --git a/README.md b/README.md
index eb5cdfc..fb45cc6 100644
--- a/README.md
+++ b/README.md
@@ -29,18 +29,21 @@ Setup
Add Jabit as Gradle dependency:
```Gradle
-compile 'ch.dissem.jabit:jabit-domain:0.2.0'
+compile 'ch.dissem.jabit:jabit-core:0.2.0'
```
Unless you want to implement your own, also add the following:
```Gradle
compile 'ch.dissem.jabit:jabit-networking:0.2.0'
compile 'ch.dissem.jabit:jabit-repositories:0.2.0'
+compile 'ch.dissem.jabit:jabit-cryptography-bc:0.2.0'
```
And if you want to import from or export to the Wallet Import Format (used by PyBitmessage) you might also want to add:
```Gradle
compile 'ch.dissem.jabit:jabit-wif:0.2.0'
```
+For Android clients use `jabit-cryptography-sc` instead of `jabit-cryptography-bc`.
+
Usage
-----
@@ -53,6 +56,7 @@ BitmessageContext ctx = new BitmessageContext.Builder()
.messageRepo(new JdbcMessageRepository(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.networkHandler(new NetworkNode())
+ .cryptography(new BouncyCryptography())
.build();
```
This creates a simple context using a H2 database that will be created in the user's home directory. Next you'll need to
diff --git a/build.gradle b/build.gradle
index 7bf5c3f..dc918be 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ subprojects {
sourceCompatibility = 1.7
group = 'ch.dissem.jabit'
- version = '0.2.0'
+ version = '1.0.0'
ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
@@ -13,6 +13,12 @@ subprojects {
mavenCentral()
}
+ test {
+ testLogging {
+ exceptionFormat = 'full'
+ }
+ }
+
task javadocJar(type: Jar) {
classifier = 'javadoc'
from javadoc
@@ -37,11 +43,6 @@ subprojects {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
- if (!hasProperty('ossrhUsername')) {
- ext.ossrhUsername = 'dummy'
- ext.ossrhPassword = 'dummy'
- }
-
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
diff --git a/core/build.gradle b/core/build.gradle
new file mode 100644
index 0000000..8754cb3
--- /dev/null
+++ b/core/build.gradle
@@ -0,0 +1,31 @@
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.project {
+ name 'Jabit Core'
+ artifactId = 'jabit-core'
+ description 'A Java implementation of the Bitmessage protocol. This is the core part. You\'ll either need the networking and repositories modules, too, or implement your own.'
+ }
+ }
+ }
+}
+
+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'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(':cryptography-bc')
+}
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
new file mode 100644
index 0000000..28e8483
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -0,0 +1,515 @@
+/*
+ * 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.*;
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.*;
+
+import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
+import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
+import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
+import static ch.dissem.bitmessage.utils.UnixTime.DAY;
+import static ch.dissem.bitmessage.utils.UnixTime.HOUR;
+import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
+
+/**
+ *
Use this class if you want to create a Bitmessage client.
+ * You'll need the Builder to create a BitmessageContext, and set the following properties:
+ *
+ * addressRepo
+ * inventory
+ * nodeRegistry
+ * networkHandler
+ * messageRepo
+ * streams
+ *
+ * The default implementations in the different module builds can be used.
+ * The port defaults to 8444 (the default Bitmessage port)
+ */
+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 final Listener listener;
+ private final NetworkHandler.MessageListener networkListener;
+
+ private final boolean sendPubkeyOnIdentityCreation;
+
+ 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);
+
+ sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
+
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ ctx.getProofOfWorkService().doMissingProofOfWork();
+ }
+ }, 30_000); // After 30 seconds
+ }
+
+ public AddressRepository addresses() {
+ return ctx.getAddressRepository();
+ }
+
+ public MessageRepository messages() {
+ return ctx.getMessageRepository();
+ }
+
+ public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
+ final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
+ shorter,
+ ctx.getStreams()[0],
+ ctx.getNetworkNonceTrialsPerByte(),
+ ctx.getNetworkExtraBytes(),
+ features
+ ));
+ ctx.getAddressRepository().save(identity);
+ if (sendPubkeyOnIdentityCreation) {
+ pool.submit(new Runnable() {
+ @Override
+ public void run() {
+ ctx.sendPubkey(identity, identity.getStream());
+ }
+ });
+ }
+ return identity;
+ }
+
+ 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) {
+ 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
+ );
+ msg.setStatus(SENT);
+ msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
+ ctx.getMessageRepository().save(msg);
+ }
+ });
+ }
+
+ 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.");
+ }
+ pool.submit(new Runnable() {
+ @Override
+ public void run() {
+ Plaintext msg = new Plaintext.Builder(MSG)
+ .from(from)
+ .to(to)
+ .message(subject, message)
+ .labels(messages().getLabels(Label.Type.SENT))
+ .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
+ );
+ msg.setStatus(SENT);
+ msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
+ ctx.getMessageRepository().save(msg);
+ }
+ }
+ });
+ }
+
+ public void send(final Plaintext msg) {
+ if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) {
+ throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
+ }
+ pool.submit(new Runnable() {
+ @Override
+ public void run() {
+ BitmessageAddress to = msg.getTo();
+ if (to.getPubkey() == null) {
+ tryToFindMatchingPubkey(to);
+ }
+ if (to.getPubkey() == null) {
+ LOG.info("Public key is missing from recipient. Requesting.");
+ requestPubkey(msg.getFrom(), 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(
+ msg.getFrom(),
+ to,
+ new Msg(msg),
+ +2 * DAY
+ );
+ msg.setStatus(SENT);
+ msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
+ ctx.getMessageRepository().save(msg);
+ }
+ }
+ });
+ }
+
+ private void requestPubkey(BitmessageAddress requestingIdentity, BitmessageAddress address) {
+ ctx.send(
+ requestingIdentity,
+ address,
+ new GetPubkey(address),
+ +28 * DAY
+ );
+ }
+
+ public void startup() {
+ ctx.getNetworkHandler().start(networkListener);
+ }
+
+ public void shutdown() {
+ ctx.getNetworkHandler().stop();
+ }
+
+ /**
+ * @param host a trusted node that must be reliable (it's used for every synchronization)
+ * @param port of the trusted host, default is 8444
+ * @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even
+ * if not all objects were fetched
+ * @param wait waits for the synchronization thread to finish
+ */
+ public void synchronize(InetAddress host, int port, long timeoutInSeconds, boolean wait) {
+ Future> future = ctx.getNetworkHandler().synchronize(host, port, networkListener, timeoutInSeconds);
+ if (wait) {
+ try {
+ future.get();
+ } catch (InterruptedException e) {
+ LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.");
+ future.cancel(true);
+ } catch (CancellationException | ExecutionException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ public CustomMessage send(InetAddress server, int port, CustomMessage request) {
+ return ctx.getNetworkHandler().send(server, port, request);
+ }
+
+ public void cleanup() {
+ ctx.getInventory().cleanup();
+ }
+
+ public boolean isRunning() {
+ return ctx.getNetworkHandler().isRunning();
+ }
+
+ public void addContact(BitmessageAddress contact) {
+ ctx.getAddressRepository().save(contact);
+ tryToFindMatchingPubkey(contact);
+ if (contact.getPubkey() == null) {
+ ctx.requestPubkey(contact);
+ }
+ }
+
+ private void tryToFindMatchingPubkey(BitmessageAddress address) {
+ for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) {
+ try {
+ Pubkey pubkey = (Pubkey) object.getPayload();
+ if (address.getVersion() == 4) {
+ V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
+ if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) {
+ v4Pubkey.decrypt(address.getPublicDecryptionKey());
+ if (object.isSignatureValid(v4Pubkey)) {
+ address.setPubkey(v4Pubkey);
+ ctx.getAddressRepository().save(address);
+ break;
+ } else {
+ LOG.info("Found pubkey for " + address + " but signature is invalid");
+ }
+ }
+ } else {
+ if (Arrays.equals(pubkey.getRipe(), address.getRipe())) {
+ address.setPubkey(pubkey);
+ ctx.getAddressRepository().save(address);
+ break;
+ }
+ }
+ } catch (Exception e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ }
+ }
+
+ public void addSubscribtion(BitmessageAddress address) {
+ address.setSubscribed(true);
+ ctx.getAddressRepository().save(address);
+ tryToFindBroadcastsForAddress(address);
+ }
+
+ private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
+ for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) {
+ try {
+ Broadcast broadcast = (Broadcast) object.getPayload();
+ broadcast.decrypt(address);
+ listener.receive(broadcast.getPlaintext());
+ } catch (DecryptionFailedException ignore) {
+ } catch (Exception e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ }
+ }
+
+ public Property status() {
+ return new Property("status", null,
+ ctx.getNetworkHandler().getNetworkStatus()
+ );
+ }
+
+ /**
+ * Returns the {@link InternalContext} - normally you wouldn't need it,
+ * unless you are doing something crazy with the protocol.
+ */
+ public InternalContext internals() {
+ return ctx;
+ }
+
+ public interface Listener {
+ void receive(Plaintext plaintext);
+ }
+
+ public static final class Builder {
+ int port = 8444;
+ Inventory inventory;
+ NodeRegistry nodeRegistry;
+ NetworkHandler networkHandler;
+ AddressRepository addressRepo;
+ MessageRepository messageRepo;
+ ProofOfWorkRepository proofOfWorkRepository;
+ ProofOfWorkEngine proofOfWorkEngine;
+ Cryptography cryptography;
+ MessageCallback messageCallback;
+ CustomCommandHandler customCommandHandler;
+ Listener listener;
+ int connectionLimit = 150;
+ long connectionTTL = 30 * MINUTE;
+ boolean sendPubkeyOnIdentityCreation = true;
+ long pubkeyTTL = 28;
+
+ public Builder() {
+ }
+
+ public Builder port(int port) {
+ this.port = port;
+ return this;
+ }
+
+ public Builder inventory(Inventory inventory) {
+ this.inventory = inventory;
+ return this;
+ }
+
+ public Builder nodeRegistry(NodeRegistry nodeRegistry) {
+ this.nodeRegistry = nodeRegistry;
+ return this;
+ }
+
+ public Builder networkHandler(NetworkHandler networkHandler) {
+ this.networkHandler = networkHandler;
+ return this;
+ }
+
+ public Builder addressRepo(AddressRepository addressRepo) {
+ this.addressRepo = addressRepo;
+ return this;
+ }
+
+ public Builder messageRepo(MessageRepository messageRepo) {
+ this.messageRepo = messageRepo;
+ return this;
+ }
+
+ public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) {
+ this.proofOfWorkRepository = proofOfWorkRepository;
+ return this;
+ }
+
+ public Builder cryptography(Cryptography cryptography) {
+ this.cryptography = cryptography;
+ return this;
+ }
+
+ public Builder messageCallback(MessageCallback callback) {
+ this.messageCallback = callback;
+ return this;
+ }
+
+ public Builder customCommandHandler(CustomCommandHandler handler) {
+ this.customCommandHandler = handler;
+ return this;
+ }
+
+ public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
+ this.proofOfWorkEngine = proofOfWorkEngine;
+ return this;
+ }
+
+ public Builder listener(Listener listener) {
+ this.listener = listener;
+ return this;
+ }
+
+ public Builder connectionLimit(int connectionLimit) {
+ this.connectionLimit = connectionLimit;
+ return this;
+ }
+
+ public Builder connectionTTL(int hours) {
+ this.connectionTTL = hours * HOUR;
+ return this;
+ }
+
+ /**
+ * By default a client will send the public key when an identity is being created. On weaker devices
+ * this behaviour might not be desirable.
+ */
+ public Builder doNotSendPubkeyOnIdentityCreation() {
+ this.sendPubkeyOnIdentityCreation = false;
+ return this;
+ }
+
+ /**
+ * Time to live in seconds for public keys the client sends. Defaults to the maximum of 28 days,
+ * but on weak devices smaller values might be desirable.
+ *
+ * Please be aware that this might cause some problems where you can't receive a message (the
+ * sender can't receive your public key) in some special situations. Also note that it's probably
+ * not a good idea to set it too low.
+ *
+ */
+ public Builder pubkeyTTL(long days) {
+ if (days < 0 || days > 28 * DAY) throw new IllegalArgumentException("TTL must be between 1 and 28 days");
+ this.pubkeyTTL = days;
+ return this;
+ }
+
+ public BitmessageContext build() {
+ nonNull("inventory", inventory);
+ nonNull("nodeRegistry", nodeRegistry);
+ nonNull("networkHandler", networkHandler);
+ nonNull("addressRepo", addressRepo);
+ nonNull("messageRepo", messageRepo);
+ nonNull("proofOfWorkRepo", proofOfWorkRepository);
+ 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) {
+ }
+ };
+ }
+ 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);
+ }
+
+ private void nonNull(String name, Object o) {
+ if (o == null) throw new IllegalStateException(name + " must not be null");
+ }
+ }
+
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
similarity index 76%
rename from domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
rename to core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index 105cc96..b2366e4 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -69,9 +69,10 @@ 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.debug("Got pubkey request for identity " + identity);
+ LOG.info("Got pubkey request for identity " + identity);
+ // FIXME: only send pubkey if it wasn't sent in the last 28 days
ctx.sendPubkey(identity, object.getStream());
}
}
@@ -81,40 +82,42 @@ 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.debug("Got pubkey for contact " + address);
- ctx.getAddressRepo().save(address);
- List messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
- LOG.debug("Sending " + messages.size() + " messages for contact " + address);
- for (Plaintext msg : messages) {
- msg.setStatus(DOING_PROOF_OF_WORK);
- ctx.getMessageRepository().save(msg);
- ctx.send(
- msg.getFrom(),
- msg.getTo(),
- new Msg(msg),
- +2 * DAY,
- ctx.getNonceTrialsPerByte(msg.getTo()),
- ctx.getExtraBytes(msg.getTo())
- );
- msg.setStatus(SENT);
- ctx.getMessageRepository().save(msg);
- }
+ updatePubkey(address, pubkey);
}
} catch (DecryptionFailedException ignore) {
}
}
+ private void updatePubkey(BitmessageAddress address, Pubkey pubkey){
+ address.setPubkey(pubkey);
+ LOG.info("Got pubkey for contact " + 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) {
+ msg.setStatus(DOING_PROOF_OF_WORK);
+ ctx.getMessageRepository().save(msg);
+ ctx.send(
+ msg.getFrom(),
+ msg.getTo(),
+ new Msg(msg),
+ +2 * DAY
+ );
+ msg.setStatus(SENT);
+ ctx.getMessageRepository().save(msg);
+ }
+ }
+
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);
@@ -126,6 +129,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
msg.getPlaintext().setInventoryVector(object.getInventoryVector());
ctx.getMessageRepository().save(msg.getPlaintext());
listener.receive(msg.getPlaintext());
+ updatePubkey(msg.getPlaintext().getFrom(), msg.getPlaintext().getFrom().getPubkey());
}
break;
} catch (DecryptionFailedException ignore) {
@@ -135,7 +139,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;
}
@@ -149,6 +153,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
broadcast.getPlaintext().setInventoryVector(object.getInventoryVector());
ctx.getMessageRepository().save(broadcast.getPlaintext());
listener.receive(broadcast.getPlaintext());
+ updatePubkey(broadcast.getPlaintext().getFrom(), broadcast.getPlaintext().getFrom().getPubkey());
}
} catch (DecryptionFailedException ignore) {
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
similarity index 55%
rename from domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
rename to core/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 8ac64e0..9971b9b 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -16,12 +16,14 @@
package ch.dissem.bitmessage;
-import ch.dissem.bitmessage.entity.*;
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.Encrypted;
+import ch.dissem.bitmessage.entity.ObjectMessage;
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;
@@ -29,8 +31,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.TreeSet;
-import static ch.dissem.bitmessage.utils.UnixTime.DAY;
-
/**
* The internal context should normally only be used for port implementations. If you need it in your client
* implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
@@ -42,32 +42,64 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
public class InternalContext {
private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class);
+ private final Cryptography cryptography;
private final Inventory inventory;
private final NodeRegistry nodeRegistry;
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;
- 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;
+ private final long pubkeyTTL;
+ private long connectionTTL;
+ private int connectionLimit;
public InternalContext(BitmessageContext.Builder builder) {
+ this.cryptography = builder.cryptography;
this.inventory = builder.inventory;
this.nodeRegistry = builder.nodeRegistry;
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.clientNonce = cryptography.randomNonce();
+ this.messageCallback = builder.messageCallback;
+ this.customCommandHandler = builder.customCommandHandler;
+ this.port = builder.port;
+ this.connectionLimit = builder.connectionLimit;
+ this.connectionTTL = builder.connectionTTL;
+ this.pubkeyTTL = builder.pubkeyTTL;
- port = builder.port;
- streams.add(1L); // FIXME
+ Singleton.initialize(cryptography);
- init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
+ // 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(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
+ proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine,
+ messageCallback, customCommandHandler);
+ for (BitmessageAddress identity : addressRepository.getIdentities()) {
+ streams.add(identity.getStream());
+ }
}
private void init(Object... objects) {
@@ -78,6 +110,10 @@ public class InternalContext {
}
}
+ public Cryptography getCryptography() {
+ return cryptography;
+ }
+
public Inventory getInventory() {
return inventory;
}
@@ -90,7 +126,7 @@ public class InternalContext {
return networkHandler;
}
- public AddressRepository getAddressRepo() {
+ public AddressRepository getAddressRepository() {
return addressRepository;
}
@@ -98,10 +134,18 @@ public class InternalContext {
return messageRepository;
}
+ public ProofOfWorkRepository getProofOfWorkRepository() {
+ return proofOfWorkRepository;
+ }
+
public ProofOfWorkEngine getProofOfWorkEngine() {
return proofOfWorkEngine;
}
+ public ProofOfWorkService getProofOfWorkService() {
+ return proofOfWorkService;
+ }
+
public long[] getStreams() {
long[] result = new long[streams.size()];
int i = 0;
@@ -111,14 +155,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;
}
@@ -127,26 +163,17 @@ 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(BitmessageAddress from, BitmessageAddress to, ObjectPayload payload, long timeToLive, long nonceTrialsPerByte, long extraBytes) {
+ public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
+ final long timeToLive) {
try {
if (to == null) to = from;
long expires = UnixTime.now(+timeToLive);
LOG.info("Expires at " + expires);
- ObjectMessage object = new ObjectMessage.Builder()
+ final ObjectMessage object = new ObjectMessage.Builder()
.stream(to.getStream())
.expiresTime(expires)
.payload(payload)
@@ -159,64 +186,58 @@ public class InternalContext {
} else if (payload instanceof Encrypted) {
object.encrypt(to.getPubkey());
}
- Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
- if (payload instanceof PlaintextHolder) {
- Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
- plaintext.setInventoryVector(object.getInventoryVector());
- messageRepository.save(plaintext);
- }
- inventory.storeObject(object);
- networkHandler.offer(object.getInventoryVector());
+ messageCallback.proofOfWorkStarted(payload);
+ proofOfWorkService.doProofOfWork(to, object);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- public void sendPubkey(BitmessageAddress identity, long targetStream) {
+ public void sendPubkey(final BitmessageAddress identity, final long targetStream) {
try {
- long expires = UnixTime.now(+28 * DAY);
+ long expires = UnixTime.now(pubkeyTTL);
LOG.info("Expires at " + expires);
- ObjectMessage response = new ObjectMessage.Builder()
+ final ObjectMessage response = new ObjectMessage.Builder()
.stream(targetStream)
.expiresTime(expires)
.payload(identity.getPubkey())
.build();
response.sign(identity.getPrivateKey());
- response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
- Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
- if (response.isSigned()) {
- response.sign(identity.getPrivateKey());
- }
- if (response instanceof Encrypted) {
- response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
- }
- inventory.storeObject(response);
- networkHandler.offer(response.getInventoryVector());
- // TODO: save that the pubkey was just sent, and on which stream!
+ response.encrypt(cryptography.createPublicKey(identity.getPublicDecryptionKey()));
+ messageCallback.proofOfWorkStarted(identity.getPubkey());
+ // 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);
}
}
- public void requestPubkey(BitmessageAddress contact) {
- long expires = UnixTime.now(+2 * DAY);
+ public void requestPubkey(final BitmessageAddress contact) {
+ long expires = UnixTime.now(+pubkeyTTL);
LOG.info("Expires at " + expires);
- ObjectMessage response = new ObjectMessage.Builder()
+ final ObjectMessage response = new ObjectMessage.Builder()
.stream(contact.getStream())
.expiresTime(expires)
.payload(new GetPubkey(contact))
.build();
- Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
- inventory.storeObject(response);
- networkHandler.offer(response.getInventoryVector());
+ messageCallback.proofOfWorkStarted(response.getPayload());
+ proofOfWorkService.doProofOfWork(response);
}
public long getClientNonce() {
return clientNonce;
}
- public void setClientNonce(long clientNonce) {
- this.clientNonce = clientNonce;
+ public long getConnectionTTL() {
+ return connectionTTL;
+ }
+
+ public int getConnectionLimit() {
+ return connectionLimit;
+ }
+
+ public CustomCommandHandler getCustomCommandHandler() {
+ return customCommandHandler;
}
public interface ContextHolder {
diff --git a/core/src/main/java/ch/dissem/bitmessage/MessageCallback.java b/core/src/main/java/ch/dissem/bitmessage/MessageCallback.java
new file mode 100644
index 0000000..d09ff97
--- /dev/null
+++ b/core/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/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
new file mode 100644
index 0000000..19e231f
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -0,0 +1,82 @@
+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.Cryptography;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
+/**
+ * @author Christian Basler
+ */
+public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
+ private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class);
+
+ private Cryptography cryptography;
+ private InternalContext ctx;
+ private ProofOfWorkRepository powRepo;
+ private MessageRepository messageRepo;
+
+ public void doMissingProofOfWork() {
+ List items = powRepo.getItems();
+ if (items.isEmpty()) return;
+
+ LOG.info("Doing POW for " + items.size() + " tasks.");
+ for (byte[] initialHash : items) {
+ ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
+ cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this);
+ }
+ }
+
+ public void doProofOfWork(ObjectMessage object) {
+ doProofOfWork(null, object);
+ }
+
+ public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
+ long nonceTrialsPerByte = recipient == null ?
+ ctx.getNetworkNonceTrialsPerByte() : recipient.getPubkey().getNonceTrialsPerByte();
+ long extraBytes = recipient == null ?
+ ctx.getNetworkExtraBytes() : recipient.getPubkey().getExtraBytes();
+
+ powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
+ if (object.getPayload() instanceof PlaintextHolder) {
+ Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
+ plaintext.setInitialHash(cryptography.getInitialHash(object));
+ messageRepo.save(plaintext);
+ }
+ cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this);
+ }
+
+ @Override
+ public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
+ ObjectMessage object = powRepo.getItem(initialHash).object;
+ 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.getProofOfWorkRepository().removeObject(initialHash);
+ ctx.getNetworkHandler().offer(object.getInventoryVector());
+// messageCallback.messageOffered(payload, object.getInventoryVector());
+ }
+
+ @Override
+ public void setContext(InternalContext ctx) {
+ this.ctx = ctx;
+ this.cryptography = security();
+ this.powRepo = ctx.getProofOfWorkRepository();
+ this.messageRepo = ctx.getMessageRepository();
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Addr.java b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/Addr.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/Addr.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
similarity index 86%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
index eb348a2..931776c 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
+++ b/core/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) {
@@ -82,7 +87,7 @@ public class BitmessageAddress {
}
}
- BitmessageAddress(Pubkey publicKey) {
+ public BitmessageAddress(Pubkey publicKey) {
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
this.pubkey = publicKey;
}
@@ -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/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
new file mode 100644
index 0000000..5702b6e
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -0,0 +1,96 @@
+/*
+ * 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 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(String command) {
+ this.command = command;
+ this.data = null;
+ }
+
+ public CustomMessage(String command, byte[] data) {
+ this.command = command;
+ this.data = data;
+ }
+
+ public static CustomMessage read(InputStream in, int length) throws IOException {
+ AccessCounter counter = new AccessCounter();
+ return new CustomMessage(varString(in, counter), bytes(in, length - counter.length()));
+ }
+
+ @Override
+ public Command getCommand() {
+ return Command.CUSTOM;
+ }
+
+ public String getCustomCommand() {
+ return command;
+ }
+
+ public byte[] getData() {
+ if (data != null) {
+ return data;
+ } else {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ write(out);
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @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. " +
+ "Programmer: did you forget to override #write()?");
+ }
+ }
+
+ public boolean isError() {
+ return COMMAND_ERROR.equals(command);
+ }
+
+ public static CustomMessage error(String message) {
+ try {
+ 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/Encrypted.java b/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/GetData.java b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
similarity index 92%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/GetData.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
index b62e6e5..e272bbc 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/GetData.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
@@ -44,10 +44,10 @@ public class GetData implements MessagePayload {
}
@Override
- public void write(OutputStream stream) throws IOException {
- Encode.varInt(inventory.size(), stream);
+ public void write(OutputStream out) throws IOException {
+ Encode.varInt(inventory.size(), out);
for (InventoryVector iv : inventory) {
- iv.write(stream);
+ iv.write(out);
}
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Inv.java b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
similarity index 93%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/Inv.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
index df1b380..0135ec0 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Inv.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
@@ -44,10 +44,10 @@ public class Inv implements MessagePayload {
}
@Override
- public void write(OutputStream stream) throws IOException {
- Encode.varInt(inventory.size(), stream);
+ public void write(OutputStream out) throws IOException {
+ Encode.varInt(inventory.size(), out);
for (InventoryVector iv : inventory) {
- iv.write(stream);
+ iv.write(out);
}
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
similarity index 93%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
rename to core/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/core/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/NetworkMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
similarity index 96%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
rename to core/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/core/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/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
similarity index 92%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
index 0ef3340..99b3aec 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
@@ -24,12 +24,13 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
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 +90,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 +116,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,12 +150,16 @@ 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
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/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
similarity index 96%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index 28cebfa..fbd5d48 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -21,6 +21,7 @@ import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
+import ch.dissem.bitmessage.utils.UnixTime;
import java.io.*;
import java.util.*;
@@ -43,6 +44,7 @@ public class Plaintext implements Streamable {
private Long received;
private Set labels;
+ private byte[] initialHash;
private Plaintext(Builder builder) {
id = builder.id;
@@ -63,6 +65,7 @@ public class Plaintext implements Streamable {
public static Plaintext read(Type type, InputStream in) throws IOException {
return readWithoutSignature(type, in)
.signature(Decode.varBytes(in))
+ .received(UnixTime.now())
.build();
}
@@ -131,6 +134,15 @@ public class Plaintext implements Streamable {
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);
@@ -249,6 +261,14 @@ public class Plaintext implements Streamable {
}
}
+ public void setInitialHash(byte[] initialHash) {
+ this.initialHash = initialHash;
+ }
+
+ public byte[] getInitialHash() {
+ return initialHash;
+ }
+
public enum Encoding {
IGNORE(0), TRIVIAL(1), SIMPLE(2);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java b/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
similarity index 91%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
index 9ee4cf9..cc12050 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
@@ -18,10 +18,11 @@ package ch.dissem.bitmessage.entity;
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/VerAck.java b/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/VerAck.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Version.java b/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/Version.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/Version.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
similarity index 94%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
index 2491d85..47bf539 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
@@ -21,11 +21,11 @@ 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 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 +78,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/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
similarity index 68%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
index 2018373..fe45ac5 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
+++ b/core/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,38 @@ 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));
+ private long addressVersion;
+
+
+ public CryptoBox(Streamable data, byte[] K) throws IOException {
+ this(Encode.bytes(data), K);
}
- public CryptoBox(Streamable data, ECPoint K) throws IOException {
+ public CryptoBox(byte[] 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, 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 +75,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 +93,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 +116,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);
@@ -195,7 +164,7 @@ public class CryptoBox implements Streamable {
}
public Builder curveType(int curveType) {
- if (curveType != 0x2CA) LOG.debug("Unexpected curve type " + curveType);
+ if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType);
this.curveType = curveType;
return this;
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
similarity index 98%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
rename to core/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/core/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/payload/ObjectType.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
similarity index 91%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
rename to core/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/core/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/V2Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
similarity index 94%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
index 366d26b..fc67422 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
+++ b/core/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
*/
@@ -37,7 +38,6 @@ public class InventoryVector implements Streamable {
InventoryVector that = (InventoryVector) o;
return Arrays.equals(hash, that.hash);
-
}
@Override
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java
similarity index 96%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java
rename to core/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/core/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/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java
rename to core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
similarity index 79%
rename from domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
rename to core/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/core/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/exception/AddressFormatException.java b/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.java
rename to core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java b/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java
rename to core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java b/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java
rename to core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/exception/NodeException.java b/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/exception/NodeException.java
rename to core/src/main/java/ch/dissem/bitmessage/exception/NodeException.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
similarity index 92%
rename from domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java
rename to core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
index a144bb6..33604ab 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
@@ -23,7 +23,6 @@ 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,6 +31,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 +116,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 +127,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) {
@@ -147,7 +154,7 @@ public class Factory {
}
}
// fallback: just store the message - we don't really care what it is
-// LOG.info("Unexpected object type: " + objectType);
+ LOG.trace("Unexpected object type: " + objectType);
return GenericPayload.read(version, stream, streamNumber, length);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
similarity index 92%
rename from domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
rename to core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
index 57d240d..d13e73e 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
@@ -24,7 +24,6 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.NodeException;
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 +32,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}
@@ -44,6 +44,9 @@ class V3MessageFactory {
findMagic(in);
String command = getCommand(in);
int length = (int) Decode.uint32(in);
+ if (length > 1600003) {
+ throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected.");
+ }
byte[] checksum = Decode.bytes(in, 4);
byte[] payloadBytes = Decode.bytes(in, length);
@@ -73,12 +76,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);
@@ -92,7 +101,7 @@ class V3MessageFactory {
try {
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length);
- } catch (IOException e) {
+ } catch (Exception e) {
LOG.trace("Could not parse object payload - using generic payload instead", e);
payload = new GenericPayload(version, stream, data);
}
@@ -174,7 +183,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;
@@ -185,10 +194,10 @@ class V3MessageFactory {
private static String getCommand(InputStream stream) throws IOException {
byte[] bytes = new byte[12];
- int end = -1;
+ int end = bytes.length;
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) stream.read();
- if (end == -1) {
+ if (end == bytes.length) {
if (bytes[i] == 0) end = i;
} else {
if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command");
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java
new file mode 100644
index 0000000..3cf3f0d
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java
@@ -0,0 +1,183 @@
+/*
+ * 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;
+
+import static ch.dissem.bitmessage.utils.Numbers.max;
+
+/**
+ * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
+ */
+public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder {
+ public static final Logger LOG = LoggerFactory.getLogger(Cryptography.class);
+ private static final SecureRandom RANDOM = new SecureRandom();
+ private static final BigInteger TWO = BigInteger.valueOf(2);
+ private static final BigInteger TWO_POW_64 = TWO.pow(64);
+ private static final BigInteger TWO_POW_16 = TWO.pow(16);
+
+ private final String provider;
+ private InternalContext context;
+
+ protected AbstractCryptography(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, ProofOfWorkEngine.Callback callback) {
+ nonceTrialsPerByte = max(nonceTrialsPerByte, context.getNetworkNonceTrialsPerByte());
+ extraBytes = max(extraBytes, context.getNetworkExtraBytes());
+
+ byte[] initialHash = getInitialHash(object);
+
+ byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
+
+ context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback);
+ }
+
+ 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);
+ }
+ }
+
+ @Override
+ public byte[] getInitialHash(ObjectMessage object) {
+ return sha512(object.getPayloadBytesWithoutNonce());
+ }
+
+ @Override
+ public byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
+ if (nonceTrialsPerByte == 0) nonceTrialsPerByte = context.getNetworkNonceTrialsPerByte();
+ if (extraBytes == 0) extraBytes = context.getNetworkExtraBytes();
+
+ BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
+ 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(TWO_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/AddressRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java
rename to core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java
new file mode 100644
index 0000000..48739ea
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java
@@ -0,0 +1,210 @@
+/*
+ * 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 Cryptography {
+ /**
+ * 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)
+ * @param callback to handle nonce once it's calculated
+ */
+ void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
+ long extraBytes, ProofOfWorkEngine.Callback callback);
+
+ /**
+ * @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;
+
+ byte[] getInitialHash(ObjectMessage object);
+
+ byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
+
+ /**
+ * 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/NetworkHandler.java b/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java
similarity index 57%
rename from domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
rename to core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java
index ed2f38a..8e49586 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java
@@ -16,25 +16,12 @@
package ch.dissem.bitmessage.ports;
-import ch.dissem.bitmessage.entity.ObjectMessage;
-import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
-import ch.dissem.bitmessage.utils.Property;
-
-import java.io.IOException;
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.MessagePayload;
/**
- * Handles incoming messages
+ * @author Christian Basler
*/
-public interface NetworkHandler {
- void start(MessageListener listener);
-
- void stop();
-
- void offer(InventoryVector iv);
-
- Property getNetworkStatus();
-
- interface MessageListener {
- void receive(ObjectMessage object) throws IOException;
- }
+public interface CustomCommandHandler {
+ MessagePayload handle(CustomMessage request);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java b/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
similarity index 67%
rename from domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
rename to core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
index 934a4d0..6af65c1 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
@@ -26,15 +26,32 @@ import java.util.List;
* The Inventory stores and retrieves objects, cleans up outdated objects and can tell which objects are still missing.
*/
public interface Inventory {
+ /**
+ * Returns the IVs of all valid objects we have for the given streams
+ */
List getInventory(long... streams);
+ /**
+ * Returns the IVs of all objects in the offer that we don't have already. Implementations are allowed to
+ * ignore the streams parameter, but it must be set when calling this method.
+ */
List getMissing(List offer, long... streams);
ObjectMessage getObject(InventoryVector vector);
+ /**
+ * This method is mainly used to search for public keys to newly added addresses or broadcasts from new
+ * subscriptions.
+ */
List getObjects(long stream, long version, ObjectType... types);
void storeObject(ObjectMessage object);
+ boolean contains(ObjectMessage object);
+
+ /**
+ * Deletes all objects that expired 5 minutes ago or earlier
+ * (so we don't accidentally request objects we just deleted)
+ */
void cleanup();
}
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/MemoryNodeRegistry.java b/core/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
similarity index 78%
rename from repositories/src/main/java/ch/dissem/bitmessage/repository/MemoryNodeRegistry.java
rename to core/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
index 15a4134..8d43423 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/MemoryNodeRegistry.java
+++ b/core/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;
@@ -35,10 +34,10 @@ 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() {
+ private void loadStableNodes() {
try (InputStream in = getClass().getClassLoader().getResourceAsStream("nodes.txt")) {
Scanner scanner = new Scanner(in);
long stream = 0;
@@ -56,14 +55,21 @@ public class MemoryNodeRegistry implements NodeRegistry {
stableNodes.put(stream, streamSet);
} else if (streamSet != null) {
int portIndex = line.lastIndexOf(':');
- InetAddress inetAddress = InetAddress.getByName(line.substring(0, portIndex));
+ InetAddress[] inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex));
int port = Integer.valueOf(line.substring(portIndex + 1));
- streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build());
+ for (InetAddress inetAddress : inetAddresses) {
+ streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build());
+ }
}
} catch (IOException e) {
LOG.warn(e.getMessage(), e);
}
}
+ if (LOG.isDebugEnabled()) {
+ for (Map.Entry> e : stableNodes.entrySet()) {
+ LOG.debug("Stream " + e.getKey() + ": loaded " + e.getValue().size() + " bootstrap nodes.");
+ }
+ }
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -82,9 +88,16 @@ public class MemoryNodeRegistry implements NodeRegistry {
known.remove(node);
}
}
- } else if (stableNodes.containsKey(stream)) {
- // To reduce load on stable nodes, only return one
- result.add(selectRandom(stableNodes.get(stream)));
+ } else {
+ Set nodes = stableNodes.get(stream);
+ if (nodes == null || nodes.isEmpty()) {
+ loadStableNodes();
+ nodes = stableNodes.get(stream);
+ }
+ if (nodes != null && !nodes.isEmpty()) {
+ // To reduce load on stable nodes, only return one
+ result.add(selectRandom(nodes));
+ }
}
}
return selectRandom(limit, result);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
similarity index 89%
rename from domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
rename to core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
index b698a97..9e949a7 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
@@ -28,12 +28,18 @@ public interface MessageRepository {
List getLabels(Label.Type... types);
+ int countUnread(Label label);
+
+ Plaintext getMessage(byte[] initialHash);
+
List findMessages(Label label);
List findMessages(Status status);
List findMessages(Status status, BitmessageAddress recipient);
+ List findMessages(BitmessageAddress sender);
+
void save(Plaintext message);
void remove(Plaintext message);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java
similarity index 50%
rename from domain/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java
rename to core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java
index 9814c3a..790e3b7 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java
@@ -24,6 +24,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Semaphore;
import static ch.dissem.bitmessage.utils.Bytes.inc;
@@ -31,46 +32,53 @@ import static ch.dissem.bitmessage.utils.Bytes.inc;
* A POW engine using all available CPU cores.
*/
public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
- private static Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);
+ private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);
+ private static final Semaphore semaphore = new Semaphore(1, true);
+ /**
+ * This method will block until all pending nonce calculations are done, but not wait for its own calculation
+ * to finish.
+ * (This implementation becomes very inefficient if multiple nonce are calculated at the same time.)
+ *
+ * @param initialHash the SHA-512 hash of the object to send, sans nonce
+ * @param target the target, representing an unsigned long
+ * @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
+ */
@Override
- public byte[] calculateNonce(byte[] initialHash, byte[] target) {
+ public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
+ try {
+ semaphore.acquire();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ callback = new CallbackWrapper(callback);
int cores = Runtime.getRuntime().availableProcessors();
if (cores > 255) cores = 255;
LOG.info("Doing POW using " + cores + " cores");
- long time = System.currentTimeMillis();
List workers = new ArrayList<>(cores);
for (int i = 0; i < cores; i++) {
- Worker w = new Worker(workers, (byte) cores, i, initialHash, target);
+ Worker w = new Worker(workers, (byte) cores, i, initialHash, target, callback);
workers.add(w);
}
for (Worker w : workers) {
+ // Doing this in the previous loop might cause a ConcurrentModificationException in the worker
+ // if a worker finds a nonce while new ones are still being added.
w.start();
}
- for (Worker w : workers) {
- try {
- w.join();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- if (w.isSuccessful()) {
- LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - time) / 1000) + " seconds");
- return w.getNonce();
- }
- }
- throw new RuntimeException("All workers ended without yielding a nonce - something is seriously broken!");
}
private static class Worker extends Thread {
+ private final Callback callback;
private final byte numberOfCores;
private final List workers;
private final byte[] initialHash;
private final byte[] target;
private final MessageDigest mda;
private final byte[] nonce = new byte[8];
- private boolean successful = false;
- public Worker(List workers, byte numberOfCores, int core, byte[] initialHash, byte[] target) {
+ public Worker(List workers, byte numberOfCores, int core, byte[] initialHash, byte[] target,
+ Callback callback) {
+ this.callback = callback;
this.numberOfCores = numberOfCores;
this.workers = workers;
this.initialHash = initialHash;
@@ -84,14 +92,6 @@ public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
}
}
- public boolean isSuccessful() {
- return successful;
- }
-
- public byte[] getNonce() {
- return nonce;
- }
-
@Override
public void run() {
do {
@@ -99,13 +99,43 @@ public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
mda.update(nonce);
mda.update(initialHash);
if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) {
- successful = true;
- for (Worker w : workers) {
- w.interrupt();
+ synchronized (callback) {
+ if (!Thread.interrupted()) {
+ for (Worker w : workers) {
+ w.interrupt();
+ }
+ // Clear interrupted flag for callback
+ Thread.interrupted();
+ callback.onNonceCalculated(initialHash, nonce);
+ }
}
return;
}
} while (!Thread.interrupted());
}
}
+
+ public static class CallbackWrapper implements Callback {
+ private final Callback callback;
+ private final long startTime;
+ private boolean waiting = true;
+
+ public CallbackWrapper(Callback callback) {
+ this.startTime = System.currentTimeMillis();
+ this.callback = callback;
+ }
+
+ @Override
+ public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
+ // Prevents the callback from being called twice if two nonces are found simultaneously
+ synchronized (this) {
+ if (waiting) {
+ semaphore.release();
+ LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds");
+ waiting = false;
+ callback.onNonceCalculated(initialHash, nonce);
+ }
+ }
+ }
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
new file mode 100644
index 0000000..909d3dd
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
@@ -0,0 +1,73 @@
+/*
+ * 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.ObjectMessage;
+import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
+import ch.dissem.bitmessage.utils.Property;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.concurrent.Future;
+
+/**
+ * Handles incoming messages
+ */
+public interface NetworkHandler {
+ /**
+ * Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
+ *
+ * An implementation should disconnect if either the timeout is reached or the returned thread is interrupted.
+ *
+ */
+ 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.
+ */
+ void start(MessageListener listener);
+
+ /**
+ * Stop the full network node.
+ */
+ void stop();
+
+ /**
+ * Offer new objects to up to 8 random nodes.
+ */
+ void offer(InventoryVector iv);
+
+ Property getNetworkStatus();
+
+ boolean isRunning();
+
+ interface MessageListener {
+ void receive(ObjectMessage object) throws IOException;
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java
rename to core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java
similarity index 61%
rename from domain/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java
rename to core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java
index 31f6657..fc7b4c2 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java
@@ -24,9 +24,17 @@ public interface ProofOfWorkEngine {
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
* smaller than target.
*
- * @param initialHash the SHA-512 hash of the object to send, sans nonce
- * @param target the target, representing an unsigned long
- * @return 8 bytes nonce
+ * @param initialHash the SHA-512 hash of the object to send, sans nonce
+ * @param target the target, representing an unsigned long
+ * @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
+ * sure this is only called once.
*/
- byte[] calculateNonce(byte[] initialHash, byte[] target);
+ void calculateNonce(byte[] initialHash, byte[] target, Callback callback);
+
+ interface Callback {
+ /**
+ * @param nonce 8 bytes nonce
+ */
+ void onNonceCalculated(byte[] initialHash, byte[] nonce);
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
new file mode 100644
index 0000000..739c172
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
@@ -0,0 +1,32 @@
+package ch.dissem.bitmessage.ports;
+
+import ch.dissem.bitmessage.entity.ObjectMessage;
+
+import java.util.List;
+
+/**
+ * Objects that proof of work is currently being done for.
+ *
+ * @author Christian Basler
+ */
+public interface ProofOfWorkRepository {
+ Item getItem(byte[] initialHash);
+
+ List getItems();
+
+ void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
+
+ void removeObject(byte[] initialHash);
+
+ class Item {
+ public final ObjectMessage object;
+ public final long nonceTrialsPerByte;
+ public final long extraBytes;
+
+ public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
+ this.object = object;
+ this.nonceTrialsPerByte = nonceTrialsPerByte;
+ this.extraBytes = extraBytes;
+ }
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java
similarity index 76%
rename from domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java
rename to core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java
index 0d9d392..e8d649b 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java
@@ -23,11 +23,15 @@ 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.
+ *
+ * Warning: implementations probably depend on POW being asynchronous, that's
+ * another reason not to use this one.
+ *
*/
public class SimplePOWEngine implements ProofOfWorkEngine {
@Override
- public byte[] calculateNonce(byte[] initialHash, byte[] target) {
+ public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
byte[] nonce = new byte[8];
MessageDigest mda;
try {
@@ -40,6 +44,6 @@ public class SimplePOWEngine implements ProofOfWorkEngine {
mda.update(nonce);
mda.update(initialHash);
} while (Bytes.lt(target, mda.digest(mda.digest()), 8));
- return nonce;
+ callback.onNonceCalculated(initialHash, nonce);
}
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java b/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java
rename to core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Base58.java b/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/Base58.java
rename to core/src/main/java/ch/dissem/bitmessage/utils/Base58.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java b/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
similarity index 97%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
rename to core/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/core/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/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.java b/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.java
new file mode 100644
index 0000000..775a5f3
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+/**
+ * Waits for a value within a callback method to be set.
+ */
+public class CallbackWaiter {
+ private final long startTime = System.currentTimeMillis();
+ private volatile boolean isSet;
+ private T value;
+ private long time;
+
+ public void setValue(T value) {
+ synchronized (this) {
+ this.time = System.currentTimeMillis() - startTime;
+ this.value = value;
+ this.isSet = true;
+ }
+ }
+
+ public T waitForValue() throws InterruptedException {
+ while (!isSet) {
+ Thread.sleep(100);
+ }
+ synchronized (this) {
+ return value;
+ }
+ }
+
+ public long getTime() {
+ return time;
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Collections.java b/core/src/main/java/ch/dissem/bitmessage/utils/Collections.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/Collections.java
rename to core/src/main/java/ch/dissem/bitmessage/utils/Collections.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java b/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java
rename to core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Decode.java b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
similarity index 94%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/Decode.java
rename to core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
index b539aa9..47b0ee3 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Decode.java
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
@@ -130,9 +130,13 @@ public class Decode {
}
public static String varString(InputStream stream) throws IOException {
- int length = (int) varInt(stream);
+ return varString(stream, null);
+ }
+
+ public static String varString(InputStream stream, AccessCounter counter) throws IOException {
+ int length = (int) varInt(stream, counter);
// FIXME: technically, it says the length in characters, but I think this one might be correct
// otherwise it will get complicated, as we'll need to read UTF-8 char by char...
- return new String(bytes(stream, length), "utf-8");
+ return new String(bytes(stream, length, counter), "utf-8");
}
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java
similarity index 89%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
rename to core/src/main/java/ch/dissem/bitmessage/utils/Encode.java
index 095fb78..2cdc262 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java
@@ -103,20 +103,30 @@ 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.
*/
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/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java b/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java
new file mode 100644
index 0000000..b1ace02
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java
@@ -0,0 +1,10 @@
+package ch.dissem.bitmessage.utils;
+
+/**
+ * Created by chrig on 07.12.2015.
+ */
+public class Numbers {
+ public static long max(long a, long b) {
+ return a > b ? a : b;
+ }
+}
diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Points.java b/core/src/main/java/ch/dissem/bitmessage/utils/Points.java
new file mode 100644
index 0000000..937fe20
--- /dev/null
+++ b/core/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/core/src/main/java/ch/dissem/bitmessage/utils/Property.java
similarity index 56%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/Property.java
rename to core/src/main/java/ch/dissem/bitmessage/utils/Property.java
index 1030513..b823eb5 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Property.java
@@ -16,8 +16,16 @@
package ch.dissem.bitmessage.utils;
+import java.util.Arrays;
+import java.util.Objects;
+
/**
- * 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;
@@ -30,6 +38,36 @@ public class Property {
this.properties = properties;
}
+ public String getName() {
+ return name;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Returns the property if available or null
otherwise.
+ * Subproperties can be requested by submitting the sequence of properties.
+ */
+ public Property getProperty(String... name) {
+ if (name == null || name.length == 0) return null;
+
+ for (Property p : properties) {
+ if (Objects.equals(name[0], p.name)) {
+ if (name.length == 1)
+ return p;
+ else
+ return p.getProperty(Arrays.copyOfRange(name, 1, name.length));
+ }
+ }
+ return null;
+ }
+
+ public Property[] getProperties() {
+ return properties;
+ }
+
@Override
public String toString() {
return toString("");
diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java
new file mode 100644
index 0000000..0c7134b
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.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.utils;
+
+import ch.dissem.bitmessage.ports.Cryptography;
+
+/**
+ * Created by chris on 20.07.15.
+ */
+public class Singleton {
+ private static Cryptography cryptography;
+
+ public static void initialize(Cryptography cryptography) {
+ synchronized (Singleton.class) {
+ if (Singleton.cryptography == null) {
+ Singleton.cryptography = cryptography;
+ }
+ }
+ }
+
+ public static Cryptography security() {
+ return cryptography;
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java b/core/src/main/java/ch/dissem/bitmessage/utils/Strings.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java
rename to core/src/main/java/ch/dissem/bitmessage/utils/Strings.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java b/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java
similarity index 100%
rename from domain/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java
rename to core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java
diff --git a/core/src/main/resources/nodes.txt b/core/src/main/resources/nodes.txt
new file mode 100644
index 0000000..9466a85
--- /dev/null
+++ b/core/src/main/resources/nodes.txt
@@ -0,0 +1,8 @@
+[stream 1]
+
+dissem.ch:8444
+bootstrap8080.bitmessage.org:8080
+bootstrap8444.bitmessage.org:8444
+
+[stream 2]
+# none yet
\ No newline at end of file
diff --git a/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java b/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java
similarity index 96%
rename from domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java
rename to core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java
index ebb3707..21f9506 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java
+++ b/core/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/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
similarity index 91%
rename from domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
rename to core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
index f0f8ddb..9a24842 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
+++ b/core/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/core/src/test/java/ch/dissem/bitmessage/SignatureTest.java
similarity index 96%
rename from domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java
rename to core/src/test/java/ch/dissem/bitmessage/SignatureTest.java
index dc2eb85..71b7d2a 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java
+++ b/core/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/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java
similarity index 95%
rename from domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java
rename to core/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/core/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/entity/SerializationTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
similarity index 61%
rename from domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
rename to core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
index 7e56f95..1bcb8e7 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
@@ -17,21 +17,23 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.*;
-import ch.dissem.bitmessage.exception.DecryptionFailedException;
+import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
+import ch.dissem.bitmessage.entity.valueobject.Label;
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.lang.reflect.Field;
+import java.util.ArrayList;
+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 ch.dissem.bitmessage.utils.Singleton.security;
+import static org.junit.Assert.*;
-public class SerializationTest {
+public class SerializationTest extends TestBase {
@Test
public void ensureGetPubkeyIsDeserializedAndSerializedCorrectly() throws IOException {
doTest("V2GetPubkey.payload", 2, GetPubkey.class);
@@ -75,7 +77,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())
@@ -87,16 +89,57 @@ public class SerializationTest {
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);
}
+ @Test
+ public void ensureNetworkMessageIsSerializedAndDeserializedCorrectly() throws Exception {
+ ArrayList ivs = new ArrayList<>(50000);
+ for (int i = 0; i < 50000; i++) {
+ ivs.add(new InventoryVector(security().randomBytes(32)));
+ }
+
+ Inv inv = new Inv.Builder().inventory(ivs).build();
+ NetworkMessage before = new NetworkMessage(inv);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ before.write(out);
+
+ NetworkMessage after = Factory.getNetworkMessage(3, new ByteArrayInputStream(out.toByteArray()));
+ Inv invAfter = (Inv) after.getPayload();
+ assertEquals(ivs, invAfter.getInventory());
+ }
+
private void doTest(String resourceName, int version, Class> expectedPayloadType) throws IOException {
byte[] data = TestUtils.getBytes(resourceName);
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());
}
+
+ @Test
+ public void ensureSystemSerializationWorks() throws Exception {
+ Plaintext plaintext = new Plaintext.Builder(MSG)
+ .from(TestUtils.loadContact())
+ .to(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
+ .labels(Collections.singletonList(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/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
new file mode 100644
index 0000000..1ed4aac
--- /dev/null
+++ b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.utils.Bytes;
+import ch.dissem.bitmessage.utils.CallbackWaiter;
+import ch.dissem.bitmessage.utils.TestBase;
+import org.junit.Test;
+
+import static ch.dissem.bitmessage.utils.Singleton.security;
+import static org.junit.Assert.assertTrue;
+
+public class ProofOfWorkEngineTest extends TestBase {
+ @Test(timeout = 90_000)
+ public void testSimplePOWEngine() throws InterruptedException {
+ testPOW(new SimplePOWEngine());
+ }
+
+ @Test(timeout = 90_000)
+ public void testThreadedPOWEngine() throws InterruptedException {
+ testPOW(new MultiThreadedPOWEngine());
+ }
+
+ private void testPOW(ProofOfWorkEngine engine) throws InterruptedException {
+ byte[] initialHash = security().sha512(new byte[]{1, 3, 6, 4});
+ byte[] target = {0, 0, 0, -1, -1, -1, -1, -1};
+
+ final CallbackWaiter waiter1 = new CallbackWaiter<>();
+ engine.calculateNonce(initialHash, target,
+ new ProofOfWorkEngine.Callback() {
+ @Override
+ public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
+ waiter1.setValue(nonce);
+ }
+ });
+ byte[] nonce = waiter1.waitForValue();
+ System.out.println("Calculating nonce took " + waiter1.getTime() + "ms");
+ assertTrue(Bytes.lt(security().doubleSha512(nonce, initialHash), target, 8));
+
+ // Let's add a second (shorter) run to find possible multi threading issues
+ byte[] initialHash2 = security().sha512(new byte[]{1, 3, 6, 5});
+ byte[] target2 = {0, 0, -1, -1, -1, -1, -1, -1};
+
+ final CallbackWaiter waiter2 = new CallbackWaiter<>();
+ engine.calculateNonce(initialHash2, target2,
+ new ProofOfWorkEngine.Callback() {
+ @Override
+ public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
+ waiter2.setValue(nonce);
+ }
+ });
+ byte[] nonce2 = waiter2.waitForValue();
+ System.out.println("Calculating nonce took " + waiter2.getTime() + "ms");
+ assertTrue(Bytes.lt(security().doubleSha512(nonce2, initialHash2), target2, 8));
+ assertTrue("Second nonce must be quicker to find", waiter1.getTime() > waiter2.getTime());
+ }
+
+}
diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
similarity index 81%
rename from domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
rename to core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
index 7c83724..1af8d37 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
+++ b/core/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;
@@ -25,16 +26,13 @@ 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();
@Test
public void ensureExpandsCorrectly() {
byte[] source = {1};
- byte[] expected = {0,1};
+ byte[] expected = {0, 1};
assertArrayEquals(expected, Bytes.expand(source, 2));
}
@@ -56,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/domain/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java
similarity index 100%
rename from domain/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java
rename to core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java
diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java
similarity index 96%
rename from domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java
rename to core/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/core/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/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java
similarity index 98%
rename from domain/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java
rename to core/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/core/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/StringsTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java
similarity index 100%
rename from domain/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java
rename to core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java
diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java b/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java
new file mode 100644
index 0000000..e757d91
--- /dev/null
+++ b/core/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.cryptography.bc.BouncyCryptography;
+
+/**
+ * Created by chris on 20.07.15.
+ */
+public class TestBase {
+ static {
+ Singleton.initialize(new BouncyCryptography());
+ }
+}
diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java
similarity index 100%
rename from domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java
rename to core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java
diff --git a/domain/src/test/resources/BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ.pubkey b/core/src/test/resources/BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ.pubkey
similarity index 100%
rename from domain/src/test/resources/BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ.pubkey
rename to core/src/test/resources/BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ.pubkey
diff --git a/domain/src/test/resources/BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey b/core/src/test/resources/BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey
similarity index 100%
rename from domain/src/test/resources/BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey
rename to core/src/test/resources/BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey
diff --git a/domain/src/test/resources/BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h.pubkey b/core/src/test/resources/BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h.pubkey
similarity index 100%
rename from domain/src/test/resources/BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h.pubkey
rename to core/src/test/resources/BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h.pubkey
diff --git a/domain/src/test/resources/V1Msg.payload b/core/src/test/resources/V1Msg.payload
similarity index 100%
rename from domain/src/test/resources/V1Msg.payload
rename to core/src/test/resources/V1Msg.payload
diff --git a/domain/src/test/resources/V1MsgStrangeData.payload b/core/src/test/resources/V1MsgStrangeData.payload
similarity index 100%
rename from domain/src/test/resources/V1MsgStrangeData.payload
rename to core/src/test/resources/V1MsgStrangeData.payload
diff --git a/domain/src/test/resources/V2GetPubkey.payload b/core/src/test/resources/V2GetPubkey.payload
similarity index 100%
rename from domain/src/test/resources/V2GetPubkey.payload
rename to core/src/test/resources/V2GetPubkey.payload
diff --git a/domain/src/test/resources/V2Pubkey.payload b/core/src/test/resources/V2Pubkey.payload
similarity index 100%
rename from domain/src/test/resources/V2Pubkey.payload
rename to core/src/test/resources/V2Pubkey.payload
diff --git a/domain/src/test/resources/V3GetPubkey.payload b/core/src/test/resources/V3GetPubkey.payload
similarity index 100%
rename from domain/src/test/resources/V3GetPubkey.payload
rename to core/src/test/resources/V3GetPubkey.payload
diff --git a/domain/src/test/resources/V3Pubkey.payload b/core/src/test/resources/V3Pubkey.payload
similarity index 100%
rename from domain/src/test/resources/V3Pubkey.payload
rename to core/src/test/resources/V3Pubkey.payload
diff --git a/domain/src/test/resources/V4Broadcast.payload b/core/src/test/resources/V4Broadcast.payload
similarity index 100%
rename from domain/src/test/resources/V4Broadcast.payload
rename to core/src/test/resources/V4Broadcast.payload
diff --git a/domain/src/test/resources/V4GetPubkey.payload b/core/src/test/resources/V4GetPubkey.payload
similarity index 100%
rename from domain/src/test/resources/V4GetPubkey.payload
rename to core/src/test/resources/V4GetPubkey.payload
diff --git a/domain/src/test/resources/V4Pubkey.payload b/core/src/test/resources/V4Pubkey.payload
similarity index 100%
rename from domain/src/test/resources/V4Pubkey.payload
rename to core/src/test/resources/V4Pubkey.payload
diff --git a/domain/src/test/resources/V5Broadcast.payload b/core/src/test/resources/V5Broadcast.payload
similarity index 100%
rename from domain/src/test/resources/V5Broadcast.payload
rename to core/src/test/resources/V5Broadcast.payload
diff --git a/cryptography-bc/build.gradle b/cryptography-bc/build.gradle
new file mode 100644
index 0000000..c09b0db
--- /dev/null
+++ b/cryptography-bc/build.gradle
@@ -0,0 +1,18 @@
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.project {
+ name 'Jabit Bouncy Cryptography'
+ artifactId = 'jabit-cryptography-bouncy'
+ description 'The Cryptography implementation using bouncy castle'
+ }
+ }
+ }
+}
+
+dependencies {
+ compile project(':core')
+ compile 'org.bouncycastle:bcprov-jdk15on:1.52'
+ testCompile 'junit:junit:4.11'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+}
diff --git a/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.java b/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.java
new file mode 100644
index 0000000..28be67a
--- /dev/null
+++ b/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.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.cryptography.bc;
+
+import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
+import ch.dissem.bitmessage.ports.AbstractCryptography;
+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 BouncyCryptography extends AbstractCryptography {
+ private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
+
+ static {
+ java.security.Security.addProvider(new BouncyCastleProvider());
+ }
+
+ public BouncyCryptography() {
+ 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/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java
similarity index 54%
rename from domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java
rename to cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java
index 9b97eb1..3a68968 100644
--- a/domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java
+++ b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java
@@ -1,38 +1,29 @@
-/*
- * 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.ports.ProofOfWorkEngine;
+import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
+import ch.dissem.bitmessage.utils.CallbackWaiter;
+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 class CryptographyTest {
public static final byte[] TEST_VALUE = "teststring".getBytes();
public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary(""
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4");
@@ -42,29 +33,39 @@ public class SecurityTest {
public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
+ "cd566972b5e50104011a92b59fa8e0b1234851ae");
+ private static BouncyCryptography security;
+
+ public CryptographyTest() {
+ security = new BouncyCryptography();
+ 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,25 +76,26 @@ 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
- public void testDoProofOfWork() throws IOException {
+ public void testDoProofOfWork() throws Exception {
ObjectMessage objectMessage = new ObjectMessage.Builder()
.nonce(new byte[8])
.expiresTime(UnixTime.now(+2 * DAY))
.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);
+ final CallbackWaiter waiter = new CallbackWaiter<>();
+ security.doProofOfWork(objectMessage, 1000, 1000,
+ new ProofOfWorkEngine.Callback() {
+ @Override
+ public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
+ waiter.setValue(nonce);
+ }
+ });
+ objectMessage.setNonce(waiter.waitForValue());
+ 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/cryptography-sc/build.gradle b/cryptography-sc/build.gradle
new file mode 100644
index 0000000..16771fc
--- /dev/null
+++ b/cryptography-sc/build.gradle
@@ -0,0 +1,17 @@
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.project {
+ name 'Jabit Spongy Cryptography'
+ artifactId = 'jabit-cryptography-spongy'
+ description 'The Cryptography implementation using spongy castle (needed for Android)'
+ }
+ }
+ }
+}
+
+dependencies {
+ compile project(':core')
+ compile 'com.madgag.spongycastle:prov:1.52.0.0'
+ testCompile 'junit:junit:4.11'
+}
diff --git a/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.java b/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.java
new file mode 100644
index 0000000..c9506fb
--- /dev/null
+++ b/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.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.cryptography.sc;
+
+import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
+import ch.dissem.bitmessage.ports.AbstractCryptography;
+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 SpongyCryptography extends AbstractCryptography {
+ private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
+
+ static {
+ java.security.Security.addProvider(new BouncyCastleProvider());
+ }
+
+ public SpongyCryptography() {
+ 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/demo/build.gradle b/demo/build.gradle
index 8d6414c..84d5907 100644
--- a/demo/build.gradle
+++ b/demo/build.gradle
@@ -14,16 +14,20 @@ uploadArchives {
}
}
+sourceCompatibility = 1.8
+
task fatCapsule(type: FatCapsule) {
applicationClass 'ch.dissem.bitmessage.demo.Main'
}
dependencies {
- compile project(':domain')
+ compile project(':core')
compile project(':networking')
compile project(':repositories')
+ compile project(':cryptography-bc')
compile project(':wif')
compile 'org.slf4j:slf4j-simple:1.7.12'
compile 'args4j:args4j:2.32'
+ compile 'com.h2database:h2:1.4.190'
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 0e03880..26d6652 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
@@ -20,12 +20,15 @@ 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.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.repository.*;
+import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
import java.util.List;
import java.util.Scanner;
@@ -38,27 +41,32 @@ public class Application {
private BitmessageContext ctx;
- public Application() {
+ public Application(String syncServer, int syncPort) {
JdbcConfig jdbcConfig = new JdbcConfig();
ctx = new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository(jdbcConfig))
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
- .networkHandler(new NetworkNode())
+ .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
+ .networkHandler(new DefaultNetworkHandler())
+ .cryptography(new BouncyCryptography())
.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);
- }
- }
- });
+ if (syncServer == null) {
+ ctx.startup();
+ }
scanner = new Scanner(System.in);
@@ -70,6 +78,9 @@ public class Application {
System.out.println("c) contacts");
System.out.println("s) subscriptions");
System.out.println("m) messages");
+ if (syncServer != null) {
+ System.out.println("y) sync");
+ }
System.out.println("?) info");
System.out.println("e) exit");
@@ -94,6 +105,9 @@ public class Application {
break;
case "e":
break;
+ case "y":
+ ctx.synchronize(InetAddress.getByName(syncServer), syncPort, 120, true);
+ break;
default:
System.out.println("Unknown command. Please try again.");
}
@@ -102,6 +116,7 @@ public class Application {
}
} while (!"e".equals(command));
LOG.info("Shutting down client");
+ ctx.cleanup();
ctx.shutdown();
}
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..b0e114b 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
@@ -17,8 +17,10 @@
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.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.repository.*;
+import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
import ch.dissem.bitmessage.wif.WifExporter;
import ch.dissem.bitmessage.wif.WifImporter;
import org.kohsuke.args4j.CmdLineException;
@@ -49,7 +51,9 @@ public class Main {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
- .networkHandler(new NetworkNode())
+ .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
+ .networkHandler(new DefaultNetworkHandler())
+ .cryptography(new BouncyCryptography())
.port(48444)
.build();
@@ -60,7 +64,7 @@ public class Main {
new WifImporter(ctx, options.importWIF).importAll();
}
} else {
- new Application();
+ new Application(options.syncServer, options.syncPort);
}
}
@@ -70,5 +74,11 @@ public class Main {
@Option(name = "-export", usage = "Export to WIF file.")
private File exportWIF;
+
+ @Option(name = "-syncServer", usage = "Use manual synchronization with the given server instead of starting a full node.")
+ private String syncServer;
+
+ @Option(name = "-syncPort", usage = "Port to use for synchronisation")
+ private int syncPort = 8444;
}
}
diff --git a/domain/build.gradle b/domain/build.gradle
deleted file mode 100644
index eb83499..0000000
--- a/domain/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-uploadArchives {
- repositories {
- mavenDeployer {
- pom.project {
- name 'Jabit Domain'
- artifactId = 'jabit-domain'
- description 'A Java implementation of the Bitmessage protocol. This is the core part. You\'ll either need the networking and repositories modules, too, or implement your own.'
- }
- }
- }
-}
-
-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
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
deleted file mode 100644
index 7878ada..0000000
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ /dev/null
@@ -1,317 +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;
-
-import ch.dissem.bitmessage.entity.BitmessageAddress;
-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.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.Security;
-import ch.dissem.bitmessage.utils.UnixTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Arrays;
-
-import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
-import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
-import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
-import static ch.dissem.bitmessage.utils.UnixTime.DAY;
-
-/**
- * Use this class if you want to create a Bitmessage client.
- * You'll need the Builder to create a BitmessageContext, and set the following properties:
- *
- * addressRepo
- * inventory
- * nodeRegistry
- * networkHandler
- * messageRepo
- * streams
- *
- * The default implementations in the different module builds can be used.
- * The port defaults to 8444 (the default Bitmessage port)
- */
-public class BitmessageContext {
- public static final int CURRENT_VERSION = 3;
- private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
-
- private final InternalContext ctx;
-
- private Listener listener;
-
- private BitmessageContext(Builder builder) {
- ctx = new InternalContext(builder);
- }
-
- public AddressRepository addresses() {
- return ctx.getAddressRepo();
- }
-
- public MessageRepository messages() {
- return ctx.getMessageRepository();
- }
-
- public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
- BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
- shorter,
- ctx.getStreams()[0],
- ctx.getNetworkNonceTrialsPerByte(),
- ctx.getNetworkExtraBytes(),
- features
- ));
- ctx.getAddressRepo().save(identity);
- // TODO: this should happen in a separate thread
- ctx.sendPubkey(identity, identity.getStream());
- return identity;
- }
-
- public void addDistributedMailingList(String address, String alias) {
- // 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();
-
- 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) {
- 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);
- }
- }
-
- private void requestPubkey(BitmessageAddress requestingIdentity, BitmessageAddress address) {
- ctx.send(
- requestingIdentity,
- address,
- new GetPubkey(address),
- +28 * DAY,
- ctx.getNetworkNonceTrialsPerByte(),
- ctx.getNetworkExtraBytes()
- );
- }
-
- 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();
- Security.doProofOfWork(object, ctx.getProofOfWorkEngine(),
- 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));
- }
-
- public void shutdown() {
- ctx.getNetworkHandler().stop();
- }
-
- public void addContact(BitmessageAddress contact) {
- ctx.getAddressRepo().save(contact);
- tryToFindMatchingPubkey(contact);
- if (contact.getPubkey() == null) {
- ctx.requestPubkey(contact);
- }
- }
-
- private void tryToFindMatchingPubkey(BitmessageAddress address) {
- for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) {
- try {
- Pubkey pubkey = (Pubkey) object.getPayload();
- if (address.getVersion() == 4) {
- V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
- if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) {
- v4Pubkey.decrypt(address.getPublicDecryptionKey());
- if (object.isSignatureValid(v4Pubkey)) {
- address.setPubkey(v4Pubkey);
- ctx.getAddressRepo().save(address);
- break;
- } else {
- LOG.debug("Found pubkey for " + address + " but signature is invalid");
- }
- }
- } else {
- if (Arrays.equals(pubkey.getRipe(), address.getRipe())) {
- address.setPubkey(pubkey);
- ctx.getAddressRepo().save(address);
- break;
- }
- }
- } catch (Exception e) {
- LOG.debug(e.getMessage(), e);
- }
- }
- }
-
- public void addSubscribtion(BitmessageAddress address) {
- address.setSubscribed(true);
- ctx.getAddressRepo().save(address);
- tryToFindBroadcastsForAddress(address);
- }
-
- private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
- for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) {
- try {
- Broadcast broadcast = (Broadcast) object.getPayload();
- broadcast.decrypt(address);
- listener.receive(broadcast.getPlaintext());
- } catch (DecryptionFailedException ignore) {
- } catch (Exception e) {
- LOG.debug(e.getMessage(), e);
- }
- }
- }
-
- public Property status() {
- return new Property("status", null,
- ctx.getNetworkHandler().getNetworkStatus()
- );
- }
-
- public interface Listener {
- void receive(Plaintext plaintext);
- }
-
- public static final class Builder {
- int port = 8444;
- Inventory inventory;
- NodeRegistry nodeRegistry;
- NetworkHandler networkHandler;
- AddressRepository addressRepo;
- MessageRepository messageRepo;
- ProofOfWorkEngine proofOfWorkEngine;
-
- public Builder() {
- }
-
- public Builder port(int port) {
- this.port = port;
- return this;
- }
-
- public Builder inventory(Inventory inventory) {
- this.inventory = inventory;
- return this;
- }
-
- public Builder nodeRegistry(NodeRegistry nodeRegistry) {
- this.nodeRegistry = nodeRegistry;
- return this;
- }
-
- public Builder networkHandler(NetworkHandler networkHandler) {
- this.networkHandler = networkHandler;
- return this;
- }
-
- public Builder addressRepo(AddressRepository addressRepo) {
- this.addressRepo = addressRepo;
- return this;
- }
-
- public Builder messageRepo(MessageRepository messageRepo) {
- this.messageRepo = messageRepo;
- return this;
- }
-
- public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
- this.proofOfWorkEngine = proofOfWorkEngine;
- return this;
- }
-
- public BitmessageContext build() {
- nonNull("inventory", inventory);
- nonNull("nodeRegistry", nodeRegistry);
- nonNull("networkHandler", networkHandler);
- nonNull("addressRepo", addressRepo);
- nonNull("messageRepo", messageRepo);
- if (proofOfWorkEngine == null) {
- proofOfWorkEngine = new MultiThreadedPOWEngine();
- }
- return new BitmessageContext(this);
- }
-
- private void nonNull(String name, Object o) {
- if (o == null) throw new IllegalStateException(name + " must not be null");
- }
- }
-
-}
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/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java b/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
deleted file mode 100644
index aed5722..0000000
--- a/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
+++ /dev/null
@@ -1,48 +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.ports;
-
-import ch.dissem.bitmessage.utils.Bytes;
-import ch.dissem.bitmessage.utils.Security;
-import org.junit.Test;
-
-import static org.junit.Assert.assertTrue;
-
-/**
- * Created by chris on 17.04.15.
- */
-public class ProofOfWorkEngineTest {
- @Test
- public void testSimplePOWEngine() {
- testPOW(new SimplePOWEngine());
- }
-
- @Test
- public void testThreadedPOWEngine() {
- testPOW(new MultiThreadedPOWEngine());
- }
-
- private void testPOW(ProofOfWorkEngine engine) {
- long time = System.currentTimeMillis();
- 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));
- }
-}
diff --git a/extensions/build.gradle b/extensions/build.gradle
new file mode 100644
index 0000000..d44f900
--- /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(':core')
+ testCompile 'junit:junit:4.11'
+ testCompile 'org.slf4j:slf4j-simple:1.7.12'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(path: ':core', configuration: 'testArtifacts')
+ testCompile project(':cryptography-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..49c6f1b
--- /dev/null
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
@@ -0,0 +1,143 @@
+/*
+ * 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 {
+ public static final String COMMAND = "ENCRYPTED";
+ private final Reader dataReader;
+ private CryptoBox container;
+ private BitmessageAddress sender;
+ private T data;
+
+ public CryptoCustomMessage(T data) throws IOException {
+ super(COMMAND);
+ this.data = data;
+ this.dataReader = null;
+ }
+
+ private CryptoCustomMessage(CryptoBox container, Reader dataReader) {
+ super(COMMAND);
+ this.container = container;
+ this.dataReader = dataReader;
+ }
+
+ public static CryptoCustomMessage read(CustomMessage data, Reader dataReader) throws IOException {
+ CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data.getData()), data.getData().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 {
+ Encode.varString(COMMAND, out);
+ 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..0024aaa
--- /dev/null
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.extensions.CryptoCustomMessage;
+import ch.dissem.bitmessage.utils.Encode;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+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;
+
+ 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;
+ 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);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ProofOfWorkRequest other = (ProofOfWorkRequest) o;
+
+ if (!sender.equals(other.sender)) return false;
+ if (!Arrays.equals(initialHash, other.initialHash)) return false;
+ if (request != other.request) return false;
+ return Arrays.equals(data, other.data);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = sender.hashCode();
+ result = 31 * result + Arrays.hashCode(initialHash);
+ result = 31 * result + request.hashCode();
+ result = 31 * result + Arrays.hashCode(data);
+ return result;
+ }
+
+ public static class Reader implements CryptoCustomMessage.Reader {
+ private final BitmessageAddress identity;
+
+ public Reader(BitmessageAddress identity) {
+ this.identity = identity;
+ }
+
+ @Override
+ public ProofOfWorkRequest read(BitmessageAddress sender, InputStream in) throws IOException {
+ return ProofOfWorkRequest.read(identity, in);
+ }
+ }
+
+ public enum Request {
+ CALCULATE,
+ 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..c1303e3
--- /dev/null
+++ b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.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;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.payload.GenericPayload;
+import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
+import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
+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 ensureEncryptThenDecryptYieldsSameObject() 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());
+
+ CustomMessage customMessage = CustomMessage.read(in, out.size());
+ CryptoCustomMessage messageAfter = CryptoCustomMessage.read(customMessage,
+ 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);
+ }
+
+ @Test
+ public void testWithActualRequest() throws Exception {
+ PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
+ final BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey);
+
+ ProofOfWorkRequest requestBefore = new ProofOfWorkRequest(sendingIdentity, security().randomBytes(64),
+ ProofOfWorkRequest.Request.CALCULATE);
+
+ CryptoCustomMessage messageBefore = new CryptoCustomMessage<>(requestBefore);
+ messageBefore.signAndEncrypt(sendingIdentity, security().createPublicKey(sendingIdentity.getPublicDecryptionKey()));
+
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ messageBefore.write(out);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+
+ CustomMessage customMessage = CustomMessage.read(in, out.size());
+ CryptoCustomMessage messageAfter = CryptoCustomMessage.read(customMessage,
+ new ProofOfWorkRequest.Reader(sendingIdentity));
+ ProofOfWorkRequest requestAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey());
+
+ assertEquals(requestBefore, requestAfter);
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..eb6ed30
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,10 @@
+# Don't change this file - override those properties in the
+# gradle.properties file in your home directory instead
+
+signing.keyId=
+signing.password=
+#signing.secretKeyRingFile=
+
+ossrhUsername=
+ossrhPassword=
+
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f2e98f0..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.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
diff --git a/networking/build.gradle b/networking/build.gradle
index 07d268e..984f585 100644
--- a/networking/build.gradle
+++ b/networking/build.gradle
@@ -11,7 +11,10 @@ uploadArchives {
}
dependencies {
- compile project(':domain')
- testCompile 'org.slf4j:slf4j-simple:1.7.12'
+ compile project(':core')
testCompile 'junit:junit:4.11'
+ testCompile 'org.slf4j:slf4j-simple:1.7.12'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(path: ':core', configuration: 'testArtifacts')
+ testCompile project(':cryptography-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 0eb0f09..54bfee4 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -25,8 +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.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,6 +32,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;
@@ -43,54 +42,87 @@ import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentMap;
import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT;
+import static ch.dissem.bitmessage.networking.Connection.Mode.SYNC;
import static ch.dissem.bitmessage.networking.Connection.State.*;
+import static ch.dissem.bitmessage.utils.Singleton.security;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
/**
* A connection to a specific node
*/
-public class Connection implements Runnable {
+class Connection {
public static final int READ_TIMEOUT = 2000;
- private final static Logger LOG = LoggerFactory.getLogger(Connection.class);
+ private static final Logger LOG = LoggerFactory.getLogger(Connection.class);
private static final int CONNECT_TIMEOUT = 5000;
+
+ private final long startTime;
private final ConcurrentMap ivCache;
- private InternalContext ctx;
- private Mode mode;
- private State state;
- private Socket socket;
+ 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 Set commonRequestedObjects;
+ private final Set requestedObjects;
+ private final long syncTimeout;
+ private final ReaderRunnable reader = new ReaderRunnable();
+ private final WriterRunnable writer = new WriterRunnable();
+ private final DefaultNetworkHandler networkHandler;
+
+ private volatile State state;
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;
+ private int readTimeoutCounter;
+ private boolean socketInitialized;
+ private long lastObjectTime;
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();
+ Set requestedObjectsMap) throws IOException {
+ this(context, mode, listener, socket, requestedObjectsMap,
+ Collections.newSetFromMap(new ConcurrentHashMap(10_000)),
+ 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;
+ Set requestedObjectsMap) {
+ this(context, mode, listener, new Socket(), requestedObjectsMap,
+ Collections.newSetFromMap(new ConcurrentHashMap(10_000)),
+ node, 0);
}
- private Connection(InternalContext context, Mode mode, MessageListener listener,
- ConcurrentMap requestedObjectsMap) {
+ private Connection(InternalContext context, Mode mode, MessageListener listener, Socket socket,
+ Set commonRequestedObjects, Set requestedObjects, NetworkAddress node, long syncTimeout) {
+ this.startTime = UnixTime.now();
this.ctx = context;
this.mode = mode;
this.state = CONNECTING;
this.listener = listener;
- this.requestedObjects = requestedObjectsMap;
+ this.socket = socket;
+ this.commonRequestedObjects = commonRequestedObjects;
+ this.requestedObjects = requestedObjects;
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<>();
+ this.networkHandler = (DefaultNetworkHandler) ctx.getNetworkHandler();
+ }
+
+ public static Connection sync(InternalContext ctx, InetAddress address, int port, MessageListener listener,
+ long timeoutInSeconds) throws IOException {
+ return new Connection(ctx, Mode.SYNC, listener, new Socket(address, port),
+ new HashSet(),
+ new HashSet(),
+ new NetworkAddress.Builder().ip(address).port(port).stream(1).build(),
+ timeoutInSeconds);
+ }
+
+ public long getStartTime() {
+ return startTime;
}
public Mode getMode() {
@@ -105,101 +137,42 @@ public class Connection implements Runnable {
return node;
}
- @Override
- public void run() {
- try (Socket socket = this.socket) {
- 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);
- this.in = socket.getInputStream();
- this.out = socket.getOutputStream();
- if (mode == CLIENT) {
- send(new Version.Builder().defaults().addrFrom(host).addrRecv(node).build());
- }
- while (state != DISCONNECTED) {
- try {
- NetworkMessage msg = Factory.getNetworkMessage(version, in);
- if (msg == null)
- continue;
- switch (state) {
- case ACTIVE:
- receiveMessage(msg.getPayload());
- sendQueue();
- break;
+ @SuppressWarnings("RedundantIfStatement")
+ private boolean syncFinished(NetworkMessage msg) {
+ if (mode != SYNC) {
+ return false;
+ }
+ if (Thread.interrupted()) {
+ return true;
+ }
+ if (state != ACTIVE) {
+ return false;
+ }
+ if (syncTimeout < UnixTime.now()) {
+ LOG.info("Synchronization timed out");
+ return true;
+ }
+ if (msg == null) {
+ if (requestedObjects.isEmpty() && sendingQueue.isEmpty())
+ return true;
- default:
- switch (msg.getPayload().getCommand()) {
- case VERSION:
- Version payload = (Version) msg.getPayload();
- if (payload.getNonce() == ctx.getClientNonce()) {
- LOG.info("Tried to connect to self, disconnecting.");
- disconnect();
- } else if (payload.getVersion() >= BitmessageContext.CURRENT_VERSION) {
- this.version = payload.getVersion();
- this.streams = payload.getStreams();
- send(new VerAck());
- switch (mode) {
- case SERVER:
- send(new Version.Builder().defaults().addrFrom(host).addrRecv(node).build());
- break;
- case CLIENT:
- activateConnection();
- break;
- }
- } else {
- LOG.info("Received unsupported version " + payload.getVersion() + ", disconnecting.");
- disconnect();
- }
- break;
- case VERACK:
- switch (mode) {
- case SERVER:
- activateConnection();
- break;
- case CLIENT:
- // NO OP
- break;
- }
- break;
- default:
- throw new NodeException("Command 'version' or 'verack' expected, but was '"
- + msg.getPayload().getCommand() + "'");
- }
- }
- if (socket.isClosed()) state = DISCONNECTED;
- } catch (SocketTimeoutException ignore) {
- if (state == ACTIVE) {
- sendQueue();
- }
- }
- }
- } catch (IOException | NodeException e) {
- disconnect();
- LOG.debug("Disconnected from node " + node + ": " + e.getMessage());
- } catch (RuntimeException e) {
- disconnect();
- throw e;
+ readTimeoutCounter++;
+ return readTimeoutCounter > 1;
+ } else {
+ readTimeoutCounter = 0;
+ return false;
}
}
private void activateConnection() {
LOG.info("Successfully established connection with node " + node);
state = ACTIVE;
- sendAddresses();
+ if (mode != SYNC) {
+ sendAddresses();
+ ctx.getNodeRegistry().offerAddresses(Collections.singletonList(node));
+ }
sendInventory();
node.setTime(UnixTime.now());
- ctx.getNodeRegistry().offerAddresses(Arrays.asList(node));
- }
-
- private void sendQueue() {
- if (sendingQueue.size() > 0) {
- LOG.debug("Sending " + sendingQueue.size() + " messages to node " + node);
- }
- for (MessagePayload msg = sendingQueue.poll(); msg != null; msg = sendingQueue.poll()) {
- send(msg);
- }
}
private void cleanupIvCache() {
@@ -227,41 +200,16 @@ public class Connection implements Runnable {
}
}
- private void updateRequestedObjects(List missing) {
- Long now = UnixTime.now();
- Long fiveMinutesAgo = now - 5 * MINUTE;
- Long tenMinutesAgo = now - 10 * MINUTE;
- List stillMissing = new LinkedList<>();
- for (Map.Entry entry : requestedObjects.entrySet()) {
- if (entry.getValue() < fiveMinutesAgo) {
- stillMissing.add(entry.getKey());
- // If it's still not available after 10 minutes, we won't look for it
- // any longer (except it's announced again)
- if (entry.getValue() < tenMinutesAgo) {
- requestedObjects.remove(entry.getKey());
- }
- }
- }
-
- for (InventoryVector iv : missing) {
- requestedObjects.put(iv, now);
- }
- if (!stillMissing.isEmpty()) {
- LOG.debug(stillMissing.size() + " items are still missing.");
- missing.addAll(stillMissing);
- }
- }
-
private void receiveMessage(MessagePayload messagePayload) {
switch (messagePayload.getCommand()) {
case INV:
Inv inv = (Inv) messagePayload;
+ int originalSize = inv.getInventory().size();
updateIvCache(inv.getInventory());
List missing = ctx.getInventory().getMissing(inv.getInventory(), streams);
- missing.removeAll(requestedObjects.keySet());
- LOG.debug("Received inventory with " + inv.getInventory().size() + " elements, of which are "
+ missing.removeAll(commonRequestedObjects);
+ LOG.debug("Received inventory with " + originalSize + " elements, of which are "
+ missing.size() + " missing.");
- updateRequestedObjects(missing);
send(new GetData.Builder().inventory(missing).build());
break;
case GETDATA:
@@ -274,21 +222,26 @@ public class Connection implements Runnable {
case OBJECT:
ObjectMessage objectMessage = (ObjectMessage) messagePayload;
try {
- LOG.debug("Received object " + objectMessage.getInventoryVector());
- Security.checkProofOfWork(objectMessage, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
+ requestedObjects.remove(objectMessage.getInventoryVector());
+ if (ctx.getInventory().contains(objectMessage)) {
+ LOG.trace("Received object " + objectMessage.getInventoryVector() + " - already in inventory");
+ break;
+ }
listener.receive(objectMessage);
+ security().checkProofOfWork(objectMessage, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
ctx.getInventory().storeObject(objectMessage);
// offer object to some random nodes so it gets distributed throughout the network:
- // FIXME: don't do this while we catch up after initialising our first connection
- // (that might be a bit tricky to do)
- ctx.getNetworkHandler().offer(objectMessage.getInventoryVector());
+ networkHandler.offer(objectMessage.getInventoryVector());
+ lastObjectTime = UnixTime.now();
} 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());
+ if (commonRequestedObjects.remove(objectMessage.getInventoryVector())) {
+ LOG.debug("Received object that wasn't requested.");
+ }
}
break;
case ADDR:
@@ -296,6 +249,7 @@ public class Connection implements Runnable {
LOG.debug("Received " + addr.getAddresses().size() + " addresses.");
ctx.getNodeRegistry().offerAddresses(addr.getAddresses());
break;
+ case CUSTOM:
case VERACK:
case VERSION:
throw new RuntimeException("Unexpectedly received '" + messagePayload.getCommand() + "' command");
@@ -318,11 +272,19 @@ public class Connection implements Runnable {
public void disconnect() {
state = DISCONNECTED;
+
+ // Make sure objects that are still missing are requested from other nodes
+ networkHandler.request(requestedObjects);
}
- private void send(MessagePayload payload) {
+ void send(MessagePayload payload) {
try {
- new NetworkMessage(payload).write(out);
+ if (payload instanceof GetData) {
+ requestedObjects.addAll(((GetData) payload).getInventory());
+ }
+ synchronized (this) {
+ new NetworkMessage(payload).write(out);
+ }
} catch (IOException e) {
LOG.error(e.getMessage(), e);
disconnect();
@@ -330,7 +292,6 @@ public class Connection implements Runnable {
}
public void offer(InventoryVector iv) {
- LOG.debug("Offering " + iv + " to node " + node.toString());
sendingQueue.offer(new Inv.Builder()
.addInventoryVector(iv)
.build());
@@ -354,14 +315,147 @@ public class Connection implements Runnable {
return Objects.hash(node);
}
- public void request(InventoryVector key) {
- sendingQueue.offer(new GetData.Builder()
- .addInventoryVector(key)
- .build()
- );
+ private synchronized void initSocket(Socket socket) throws IOException {
+ if (!socketInitialized) {
+ if (!socket.isConnected()) {
+ LOG.trace("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();
+ socketInitialized = true;
+ }
}
- public enum Mode {SERVER, CLIENT}
+ public ReaderRunnable getReader() {
+ return reader;
+ }
+
+ public WriterRunnable getWriter() {
+ return writer;
+ }
+
+ public enum Mode {SERVER, CLIENT, SYNC}
public enum State {CONNECTING, ACTIVE, DISCONNECTED}
+
+ public class ReaderRunnable implements Runnable {
+ @Override
+ public void run() {
+ lastObjectTime = 0;
+ try (Socket socket = Connection.this.socket) {
+ initSocket(socket);
+ if (mode == CLIENT || mode == SYNC) {
+ send(new Version.Builder().defaults().addrFrom(host).addrRecv(node).build());
+ }
+ while (state != DISCONNECTED) {
+ if (mode != SYNC) {
+ if (state == ACTIVE && requestedObjects.isEmpty() && sendingQueue.isEmpty()) {
+ Thread.sleep(1000);
+ } else {
+ Thread.sleep(100);
+ }
+ }
+ try {
+ NetworkMessage msg = Factory.getNetworkMessage(version, in);
+ if (msg == null)
+ continue;
+ switch (state) {
+ case ACTIVE:
+ receiveMessage(msg.getPayload());
+ break;
+
+ default:
+ switch (msg.getPayload().getCommand()) {
+ case VERSION:
+ Version payload = (Version) msg.getPayload();
+ if (payload.getNonce() == ctx.getClientNonce()) {
+ LOG.info("Tried to connect to self, disconnecting.");
+ disconnect();
+ } else if (payload.getVersion() >= BitmessageContext.CURRENT_VERSION) {
+ version = payload.getVersion();
+ streams = payload.getStreams();
+ send(new VerAck());
+ switch (mode) {
+ case SERVER:
+ send(new Version.Builder().defaults().addrFrom(host).addrRecv(node).build());
+ break;
+ case CLIENT:
+ case SYNC:
+ activateConnection();
+ break;
+ }
+ } else {
+ LOG.info("Received unsupported version " + payload.getVersion() + ", disconnecting.");
+ disconnect();
+ }
+ break;
+ case VERACK:
+ switch (mode) {
+ case SERVER:
+ activateConnection();
+ break;
+ case CLIENT:
+ case SYNC:
+ // NO OP
+ 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() + "'");
+ }
+ }
+ if (socket.isClosed() || syncFinished(msg) || checkOpenRequests()) disconnect();
+ } catch (SocketTimeoutException ignore) {
+ if (state == ACTIVE) {
+ if (syncFinished(null)) disconnect();
+ }
+ }
+ }
+ } catch (InterruptedException | IOException | NodeException e) {
+ LOG.trace("Reader disconnected from node " + node + ": " + e.getMessage());
+ } catch (RuntimeException e) {
+ LOG.trace("Reader disconnecting from node " + node + " due to error: " + e.getMessage(), e);
+ } finally {
+ disconnect();
+ try {
+ socket.close();
+ } catch (Exception e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ }
+ }
+ }
+
+ private boolean checkOpenRequests() {
+ return !requestedObjects.isEmpty() && lastObjectTime > 0 && (UnixTime.now() - lastObjectTime) > 2 * MINUTE;
+ }
+
+ public class WriterRunnable implements Runnable {
+ @Override
+ public void run() {
+ try (Socket socket = Connection.this.socket) {
+ initSocket(socket);
+ while (state != DISCONNECTED) {
+ if (!sendingQueue.isEmpty()) {
+ send(sendingQueue.poll());
+ } else {
+ Thread.sleep(1000);
+ }
+ }
+ } catch (IOException | InterruptedException e) {
+ 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
new file mode 100644
index 0000000..d3bec17
--- /dev/null
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -0,0 +1,353 @@
+/*
+ * 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.InternalContext;
+import ch.dissem.bitmessage.InternalContext.ContextHolder;
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.GetData;
+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;
+import ch.dissem.bitmessage.utils.UnixTime;
+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.*;
+import java.util.concurrent.*;
+
+import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT;
+import static ch.dissem.bitmessage.networking.Connection.Mode.SERVER;
+import static ch.dissem.bitmessage.networking.Connection.State.ACTIVE;
+import static ch.dissem.bitmessage.utils.DebugUtils.inc;
+import static java.util.Collections.newSetFromMap;
+
+/**
+ * Handles all the networky stuff.
+ */
+public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
+ private final static Logger LOG = LoggerFactory.getLogger(DefaultNetworkHandler.class);
+
+ public final static int NETWORK_MAGIC_NUMBER = 8;
+
+ private final Collection connections = new ConcurrentLinkedQueue<>();
+ private final ExecutorService pool;
+ private InternalContext ctx;
+ private ServerSocket serverSocket;
+ private volatile boolean running;
+
+ private Set requestedObjects = newSetFromMap(new ConcurrentHashMap(50_000));
+
+ public DefaultNetworkHandler() {
+ 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
+ public void setContext(InternalContext context) {
+ this.ctx = context;
+ }
+
+ @Override
+ public Future> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds) {
+ try {
+ Connection connection = Connection.sync(ctx, server, port, listener, timeoutInSeconds);
+ Future> reader = pool.submit(connection.getReader());
+ pool.execute(connection.getWriter());
+ return reader;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @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) {
+ throw new IllegalStateException("Listener must be set at start");
+ }
+ if (running) {
+ throw new IllegalStateException("Network already running - you need to stop first.");
+ }
+ try {
+ running = true;
+ connections.clear();
+ serverSocket = new ServerSocket(ctx.getPort());
+ pool.execute(new Runnable() {
+ @Override
+ public void run() {
+ while (!serverSocket.isClosed()) {
+ try {
+ Socket socket = serverSocket.accept();
+ socket.setSoTimeout(Connection.READ_TIMEOUT);
+ startConnection(new Connection(ctx, SERVER, socket, listener, requestedObjects));
+ } catch (IOException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ }
+ }
+ });
+ pool.execute(new Runnable() {
+ public Connection initialConnection;
+
+ @Override
+ public void run() {
+ try {
+ while (running) {
+ try {
+ int active = 0;
+ long now = UnixTime.now();
+ synchronized (connections) {
+ int diff = connections.size() - ctx.getConnectionLimit();
+ if (diff > 0) {
+ for (Connection c : connections) {
+ c.disconnect();
+ diff--;
+ if (diff == 0) break;
+ }
+ }
+ boolean forcedDisconnect = false;
+ for (Iterator iterator = connections.iterator(); iterator.hasNext(); ) {
+ Connection c = iterator.next();
+ // Just in case they were all created at the same time, don't disconnect
+ // all at once.
+ if (!forcedDisconnect && now - c.getStartTime() > ctx.getConnectionTTL()) {
+ c.disconnect();
+ forcedDisconnect = true;
+ }
+ switch (c.getState()) {
+ case DISCONNECTED:
+ iterator.remove();
+ break;
+ case ACTIVE:
+ active++;
+ break;
+ }
+ }
+ }
+ if (active < NETWORK_MAGIC_NUMBER) {
+ List addresses = ctx.getNodeRegistry().getKnownAddresses(
+ NETWORK_MAGIC_NUMBER - active, ctx.getStreams());
+ boolean first = active == 0 && initialConnection == null;
+ for (NetworkAddress address : addresses) {
+ Connection c = new Connection(ctx, CLIENT, address, listener, requestedObjects);
+ if (first) {
+ initialConnection = c;
+ first = false;
+ }
+ startConnection(c);
+ }
+ Thread.sleep(10000);
+ } else if (initialConnection != null) {
+ initialConnection.disconnect();
+ initialConnection = null;
+ Thread.sleep(10000);
+ } else {
+ Thread.sleep(30000);
+ }
+ } catch (InterruptedException e) {
+ running = false;
+ } catch (Exception e) {
+ LOG.error("Error in connection manager. Ignored.", e);
+ }
+ }
+ } finally {
+ LOG.debug("Connection manager shutting down.");
+ running = false;
+ }
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ return running;
+ }
+
+ @Override
+ public void stop() {
+ running = false;
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ synchronized (connections) {
+ for (Connection c : connections) {
+ c.disconnect();
+ }
+ }
+ requestedObjects.clear();
+ }
+
+ private void startConnection(Connection c) {
+ synchronized (connections) {
+ // prevent connecting twice to the same node
+ if (connections.contains(c)) {
+ return;
+ }
+ connections.add(c);
+ }
+ pool.execute(c.getReader());
+ pool.execute(c.getWriter());
+ }
+
+ @Override
+ public void offer(final InventoryVector iv) {
+ List target = new LinkedList<>();
+ synchronized (connections) {
+ for (Connection connection : connections) {
+ if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) {
+ target.add(connection);
+ }
+ }
+ }
+ List randomSubset = Collections.selectRandom(NETWORK_MAGIC_NUMBER, target);
+ for (Connection connection : randomSubset) {
+ connection.offer(iv);
+ }
+ }
+
+ @Override
+ public Property getNetworkStatus() {
+ TreeSet streams = new TreeSet<>();
+ TreeMap incomingConnections = new TreeMap<>();
+ TreeMap outgoingConnections = new TreeMap<>();
+
+ synchronized (connections) {
+ for (Connection connection : connections) {
+ if (connection.getState() == ACTIVE) {
+ long stream = connection.getNode().getStream();
+ streams.add(stream);
+ if (connection.getMode() == SERVER) {
+ inc(incomingConnections, stream);
+ } else {
+ inc(outgoingConnections, stream);
+ }
+ }
+ }
+ }
+ Property[] streamProperties = new Property[streams.size()];
+ int i = 0;
+ for (Long stream : streams) {
+ int incoming = incomingConnections.containsKey(stream) ? incomingConnections.get(stream) : 0;
+ int outgoing = outgoingConnections.containsKey(stream) ? outgoingConnections.get(stream) : 0;
+ streamProperties[i] = new Property("stream " + stream,
+ null, new Property("nodes", incoming + outgoing),
+ new Property("incoming", incoming),
+ new Property("outgoing", outgoing)
+ );
+ i++;
+ }
+ return new Property("network", null,
+ new Property("connectionManager", running ? "running" : "stopped"),
+ new Property("connections", null, streamProperties),
+ new Property("requestedObjects", requestedObjects.size())
+ );
+ }
+
+ void request(Set inventoryVectors) {
+ if (!running || inventoryVectors.isEmpty()) return;
+ synchronized (connections) {
+ Map> distribution = new HashMap<>();
+ for (Connection connection : connections) {
+ if (connection.getState() == ACTIVE) {
+ distribution.put(connection, new LinkedList());
+ }
+ }
+ Iterator iterator = inventoryVectors.iterator();
+ InventoryVector next;
+ if (iterator.hasNext()) {
+ next = iterator.next();
+ } else {
+ return;
+ }
+ boolean firstRound = true;
+ while (firstRound || iterator.hasNext()) {
+ if (!firstRound) {
+ next = iterator.next();
+ firstRound = true;
+ } else {
+ firstRound = false;
+ }
+ for (Connection connection : distribution.keySet()) {
+ if (connection.knowsOf(next)) {
+ List ivs = distribution.get(connection);
+ if (ivs.size() == 50_000) {
+ connection.send(new GetData.Builder().inventory(ivs).build());
+ ivs.clear();
+ }
+ ivs.add(next);
+ iterator.remove();
+
+ if (iterator.hasNext()) {
+ next = iterator.next();
+ firstRound = true;
+ } else {
+ firstRound = false;
+ break;
+ }
+ }
+ }
+ }
+ for (Connection connection : distribution.keySet()) {
+ List ivs = distribution.get(connection);
+ if (!ivs.isEmpty()) {
+ connection.send(new GetData.Builder().inventory(ivs).build());
+ }
+ }
+ }
+ }
+}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java b/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java
deleted file mode 100644
index f27f7ff..0000000
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java
+++ /dev/null
@@ -1,211 +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.InternalContext;
-import ch.dissem.bitmessage.InternalContext.ContextHolder;
-import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
-import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
-import ch.dissem.bitmessage.ports.NetworkHandler;
-import ch.dissem.bitmessage.utils.Collections;
-import ch.dissem.bitmessage.utils.Property;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT;
-import static ch.dissem.bitmessage.networking.Connection.Mode.SERVER;
-import static ch.dissem.bitmessage.networking.Connection.State.ACTIVE;
-import static ch.dissem.bitmessage.networking.Connection.State.DISCONNECTED;
-import static ch.dissem.bitmessage.utils.DebugUtils.inc;
-
-/**
- * Handles all the networky stuff.
- */
-public class NetworkNode implements NetworkHandler, ContextHolder {
- public final static int NETWORK_MAGIC_NUMBER = 8;
- private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class);
- private final ExecutorService pool;
- private final List connections = new LinkedList<>();
- private InternalContext ctx;
- private ServerSocket serverSocket;
- private Thread serverThread;
- private Thread connectionManager;
-
- private ConcurrentMap requestedObjects = new ConcurrentHashMap<>();
-
- public NetworkNode() {
- pool = Executors.newCachedThreadPool();
- }
-
- @Override
- public void setContext(InternalContext context) {
- this.ctx = context;
- }
-
- @Override
- public void start(final MessageListener listener) {
- if (listener == null) {
- throw new IllegalStateException("Listener must be set at start");
- }
- try {
- serverSocket = new ServerSocket(ctx.getPort());
- serverThread = new Thread(new Runnable() {
- @Override
- public void run() {
- while (!serverSocket.isClosed()) {
- try {
- Socket socket = serverSocket.accept();
- socket.setSoTimeout(Connection.READ_TIMEOUT);
- startConnection(new Connection(ctx, SERVER, socket, listener, requestedObjects));
- } catch (IOException e) {
- LOG.debug(e.getMessage(), e);
- }
- }
- }
- }, "server");
- serverThread.start();
- connectionManager = new Thread(new Runnable() {
- @Override
- public void run() {
- while (!Thread.interrupted()) {
- try {
- int active = 0;
- synchronized (connections) {
- for (Iterator iterator = connections.iterator(); iterator.hasNext(); ) {
- Connection c = iterator.next();
- if (c.getState() == DISCONNECTED) {
- // Remove the current element from the iterator and the list.
- iterator.remove();
- }
- if (c.getState() == ACTIVE) {
- active++;
- }
- }
- }
- if (active < NETWORK_MAGIC_NUMBER) {
- List addresses = ctx.getNodeRegistry().getKnownAddresses(
- NETWORK_MAGIC_NUMBER - active, ctx.getStreams());
- for (NetworkAddress address : addresses) {
- startConnection(new Connection(ctx, CLIENT, address, listener, requestedObjects));
- }
- }
- Thread.sleep(30000);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (Exception e) {
- LOG.error("Error in connection manager. Ignored.", e);
- }
- }
- LOG.debug("Connection manager shutting down.");
- }
- }, "connection-manager");
- connectionManager.start();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void stop() {
- connectionManager.interrupt();
- try {
- serverSocket.close();
- } catch (IOException e) {
- LOG.debug(e.getMessage(), e);
- }
- pool.shutdown();
- synchronized (connections) {
- for (Connection c : connections) {
- c.disconnect();
- }
- }
- }
-
- private void startConnection(Connection c) {
- synchronized (connections) {
- // prevent connecting twice to the same node
- if (connections.contains(c)) {
- return;
- }
- connections.add(c);
- }
- pool.execute(c);
- }
-
- @Override
- public void offer(final InventoryVector iv) {
- List target = new LinkedList<>();
- synchronized (connections) {
- for (Connection connection : connections) {
- if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) {
- target.add(connection);
- }
- }
- }
- LOG.debug(target.size() + " connections available to offer " + iv);
- List randomSubset = Collections.selectRandom(NETWORK_MAGIC_NUMBER, target);
- for (Connection connection : randomSubset) {
- connection.offer(iv);
- }
- }
-
- @Override
- public Property getNetworkStatus() {
- TreeSet streams = new TreeSet<>();
- TreeMap incomingConnections = new TreeMap<>();
- TreeMap outgoingConnections = new TreeMap<>();
-
- synchronized (connections) {
- for (Connection connection : connections) {
- if (connection.getState() == ACTIVE) {
- long stream = connection.getNode().getStream();
- streams.add(stream);
- if (connection.getMode() == SERVER) {
- inc(incomingConnections, stream);
- } else {
- inc(outgoingConnections, stream);
- }
- }
- }
- }
- Property[] streamProperties = new Property[streams.size()];
- int i = 0;
- for (Long stream : streams) {
- int incoming = incomingConnections.containsKey(stream) ? incomingConnections.get(stream) : 0;
- int outgoing = outgoingConnections.containsKey(stream) ? outgoingConnections.get(stream) : 0;
- streamProperties[i] = new Property("stream " + stream,
- null, new Property("nodes", incoming + outgoing),
- new Property("incoming", incoming),
- new Property("outgoing", outgoing)
- );
- i++;
- }
- return new Property("network", null,
- new Property("connectionManager", connectionManager.isAlive() ? "running" : "stopped"),
- new Property("connections", null, streamProperties)
- );
- }
-}
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..a45ec47
--- /dev/null
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.ports.ProofOfWorkRepository;
+import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
+import ch.dissem.bitmessage.utils.Property;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+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;
+
+/**
+ * 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 peer;
+ private static BitmessageContext node;
+ private static NetworkHandler networkHandler;
+
+ @BeforeClass
+ public static void setUp() {
+ peerInventory = new TestInventory();
+ peer = new BitmessageContext.Builder()
+ .addressRepo(Mockito.mock(AddressRepository.class))
+ .inventory(peerInventory)
+ .messageRepo(Mockito.mock(MessageRepository.class))
+ .powRepo(Mockito.mock(ProofOfWorkRepository.class))
+ .port(6001)
+ .nodeRegistry(new TestNodeRegistry())
+ .networkHandler(new DefaultNetworkHandler())
+ .cryptography(new BouncyCryptography())
+ .listener(Mockito.mock(BitmessageContext.Listener.class))
+ .build();
+ peer.startup();
+
+ nodeInventory = new TestInventory();
+ networkHandler = new DefaultNetworkHandler();
+ node = new BitmessageContext.Builder()
+ .addressRepo(Mockito.mock(AddressRepository.class))
+ .inventory(nodeInventory)
+ .messageRepo(Mockito.mock(MessageRepository.class))
+ .powRepo(Mockito.mock(ProofOfWorkRepository.class))
+ .port(6002)
+ .nodeRegistry(new TestNodeRegistry(localhost))
+ .networkHandler(networkHandler)
+ .cryptography(new BouncyCryptography())
+ .listener(Mockito.mock(BitmessageContext.Listener.class))
+ .build();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ shutdown(peer);
+ }
+
+ private static void shutdown(BitmessageContext node) {
+ node.shutdown();
+ do {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignore) {
+ }
+ } while (node.isRunning());
+ }
+
+ @Test(timeout = 5_000)
+ public void ensureNodesAreConnecting() {
+ try {
+ node.startup();
+ Property status;
+ do {
+ Thread.yield();
+ status = node.status().getProperty("network", "connections", "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",
+ "V4Pubkey.payload"
+ );
+
+ Future> future = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ mock(NetworkHandler.MessageListener.class),
+ 10);
+ future.get();
+ assertInventorySize(3, nodeInventory);
+ assertInventorySize(3, peerInventory);
+ }
+
+ @Test(timeout = 5_000)
+ public void ensureObjectsAreSynchronizedIfOnlyPeerHasObjects() throws Exception {
+ peerInventory.init(
+ "V4Pubkey.payload",
+ "V5Broadcast.payload"
+ );
+
+ nodeInventory.init();
+
+ Future> future = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ mock(NetworkHandler.MessageListener.class),
+ 10);
+ future.get();
+ assertInventorySize(2, nodeInventory);
+ assertInventorySize(2, peerInventory);
+ }
+
+ @Test(timeout = 5_000)
+ public void ensureObjectsAreSynchronizedIfOnlyNodeHasObjects() throws Exception {
+ peerInventory.init();
+
+ nodeInventory.init(
+ "V1Msg.payload"
+ );
+
+ Future> future = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ mock(NetworkHandler.MessageListener.class),
+ 10);
+ future.get();
+ assertInventorySize(1, nodeInventory);
+ assertInventorySize(1, peerInventory);
+ }
+
+ private void assertInventorySize(int expected, TestInventory inventory) throws InterruptedException {
+ long timeout = System.currentTimeMillis() + 1000;
+ while (expected != inventory.getInventory().size() && System.currentTimeMillis() < timeout) {
+ Thread.sleep(10);
+ }
+ assertEquals(expected, inventory.getInventory().size());
+ }
+}
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkNodeTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkNodeTest.java
deleted file mode 100644
index 6819311..0000000
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkNodeTest.java
+++ /dev/null
@@ -1,59 +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;
-
-/**
- * Created by chris on 20.03.15.
- */
-public class NetworkNodeTest {
- private NetworkAddress localhost = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(8444).build();
-
- @Ignore
- @Test(expected = InterruptedException.class)
- public void testSendMessage() throws Exception {
- final Thread baseThread = Thread.currentThread();
- NetworkNode net = new NetworkNode();
-// 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/TestInventory.java b/networking/src/test/java/ch/dissem/bitmessage/networking/TestInventory.java
new file mode 100644
index 0000000..cefe316
--- /dev/null
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/TestInventory.java
@@ -0,0 +1,81 @@
+/*
+ * 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 boolean contains(ObjectMessage object) {
+ return inventory.containsKey(object.getInventoryVector());
+ }
+
+ @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 0000000..ddf21cd
Binary files /dev/null and b/networking/src/test/resources/V1Msg.payload differ
diff --git a/networking/src/test/resources/V4Pubkey.payload b/networking/src/test/resources/V4Pubkey.payload
new file mode 100644
index 0000000..a5e4c5c
Binary files /dev/null and b/networking/src/test/resources/V4Pubkey.payload differ
diff --git a/networking/src/test/resources/V5Broadcast.payload b/networking/src/test/resources/V5Broadcast.payload
new file mode 100644
index 0000000..87c8c04
Binary files /dev/null and b/networking/src/test/resources/V5Broadcast.payload differ
diff --git a/repositories/build.gradle b/repositories/build.gradle
index 2f467b8..abb8651 100644
--- a/repositories/build.gradle
+++ b/repositories/build.gradle
@@ -2,7 +2,7 @@ uploadArchives {
repositories {
mavenDeployer {
pom.project {
- name 'Jabit Domain'
+ name 'Jabit Repositories'
artifactId = 'jabit-repositories'
description 'A Java implementation of the Bitmessage protocol. This contains JDBC implementations of the repositories.'
}
@@ -10,9 +10,13 @@ uploadArchives {
}
}
+sourceCompatibility = 1.8
+
dependencies {
- compile project(':domain')
- compile 'com.h2database:h2:1.4.187'
+ compile project(':core')
compile 'org.flywaydb:flyway-core:3.2.1'
- testCompile 'junit:junit:4.11'
+ testCompile 'junit:junit:4.12'
+ testCompile 'com.h2database:h2:1.4.190'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(':cryptography-bc')
}
\ No newline at end of file
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..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;
@@ -96,9 +97,10 @@ 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());
+
+ 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"));
@@ -179,10 +181,9 @@ 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);
+ ps.setBytes(parameterIndex, null);
}
}
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..d583a71 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
@@ -18,21 +18,20 @@ 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.PreparedStatement;
+import java.sql.SQLException;
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;
@@ -81,10 +80,9 @@ abstract class JdbcHelper {
if (data != null) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
data.write(os);
- ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
- ps.setBlob(parameterIndex, is);
+ 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 c7e8873..3336475 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
@@ -27,12 +27,17 @@ import org.slf4j.LoggerFactory;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static ch.dissem.bitmessage.utils.UnixTime.now;
public class JdbcInventory extends JdbcHelper implements Inventory {
private static final Logger LOG = LoggerFactory.getLogger(JdbcInventory.class);
+ private final Map> cache = new ConcurrentHashMap<>();
+
public JdbcInventory(JdbcConfig config) {
super(config);
}
@@ -40,35 +45,43 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
@Override
public List getInventory(long... streams) {
List result = new LinkedList<>();
- try (Connection connection = config.getConnection()) {
- Statement stmt = connection.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT hash FROM Inventory WHERE expires > " + now() +
- " AND stream IN (" + join(streams) + ")");
- while (rs.next()) {
- result.add(new InventoryVector(rs.getBytes("hash")));
- }
- } catch (SQLException e) {
- LOG.error(e.getMessage(), e);
+ for (long stream : streams) {
+ getCache(stream).entrySet().stream()
+ .filter(e -> e.getValue() > now())
+ .forEach(e -> result.add(e.getKey()));
}
return result;
}
- private List getFullInventory(long... streams) {
- List result = new LinkedList<>();
- try (Connection connection = config.getConnection()) {
- Statement stmt = connection.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT hash FROM Inventory WHERE stream IN (" + join(streams) + ")");
- while (rs.next()) {
- result.add(new InventoryVector(rs.getBytes("hash")));
+
+ private Map getCache(long stream) {
+ Map result = cache.get(stream);
+ if (result == null) {
+ synchronized (cache) {
+ if (cache.get(stream) == null) {
+ result = new ConcurrentHashMap<>();
+ cache.put(stream, result);
+
+ try (Connection connection = config.getConnection()) {
+ Statement stmt = connection.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT hash, expires FROM Inventory WHERE expires > "
+ + now(-5 * MINUTE) + " AND stream = " + stream);
+ while (rs.next()) {
+ result.put(new InventoryVector(rs.getBytes("hash")), rs.getLong("expires"));
+ }
+ } catch (SQLException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
}
- } catch (SQLException e) {
- LOG.error(e.getMessage(), e);
}
return result;
}
@Override
public List getMissing(List offer, long... streams) {
- offer.removeAll(getFullInventory(streams));
+ for (long stream : streams) {
+ offer.removeAll(getCache(stream).keySet());
+ }
return offer;
}
@@ -119,6 +132,9 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
@Override
public void storeObject(ObjectMessage object) {
+ if (getCache(object.getStream()).containsKey(object.getInventoryVector()))
+ return;
+
try (Connection connection = config.getConnection()) {
PreparedStatement ps = connection.prepareStatement("INSERT INTO Inventory (hash, stream, expires, data, type, version) VALUES (?, ?, ?, ?, ?, ?)");
InventoryVector iv = object.getInventoryVector();
@@ -130,6 +146,7 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
ps.setLong(5, object.getType());
ps.setLong(6, object.getVersion());
ps.executeUpdate();
+ getCache(object.getStream()).put(iv, object.getExpiresTime());
} catch (SQLException e) {
LOG.debug("Error storing object of type " + object.getPayload().getClass().getSimpleName(), e);
} catch (Exception e) {
@@ -137,13 +154,21 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
}
}
+ @Override
+ public boolean contains(ObjectMessage object) {
+ return getCache(object.getStream()).entrySet().stream()
+ .anyMatch(x -> x.getKey().equals(object.getInventoryVector()));
+ }
+
@Override
public void cleanup() {
try (Connection connection = config.getConnection()) {
- // We delete only objects that expired 5 minutes ago or earlier, so we don't request objects we just deleted
- connection.createStatement().executeUpdate("DELETE FROM Inventory WHERE expires < " + (now() - 300));
+ connection.createStatement().executeUpdate("DELETE FROM Inventory WHERE expires < " + now(-5 * MINUTE));
} catch (SQLException e) {
LOG.debug(e.getMessage(), e);
}
+ for (Map c : cache.values()) {
+ c.entrySet().removeIf(e -> e.getValue() < now(-5 * MINUTE));
+ }
}
}
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..69890c3 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
@@ -22,10 +22,12 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository;
+import ch.dissem.bitmessage.utils.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.io.InputStream;
import java.sql.*;
import java.util.ArrayList;
import java.util.Collection;
@@ -84,6 +86,43 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
return result;
}
+ @Override
+ public int countUnread(Label label) {
+ String where;
+ if (label != null) {
+ where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND ";
+ } else {
+ where = "";
+ }
+ where += "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" +
+ "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))";
+
+ try (Connection connection = config.getConnection()) {
+ Statement stmt = connection.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT count(*) FROM Message WHERE " + where);
+ if (rs.next()) {
+ return rs.getInt(1);
+ }
+ } catch (SQLException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ return 0;
+ }
+
+ @Override
+ public Plaintext getMessage(byte[] initialHash) {
+ List plaintexts = find("initial_hash=X'" + Strings.hex(initialHash) + "'");
+ switch (plaintexts.size()) {
+ case 0:
+ return null;
+ case 1:
+ return plaintexts.get(0);
+ default:
+ throw new RuntimeException("This shouldn't happen, found " + plaintexts.size() +
+ " messages, one or none was expected");
+ }
+ }
+
@Override
public List findMessages(Label label) {
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")");
@@ -99,6 +138,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()) {
@@ -106,14 +150,14 @@ 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");
+ InputStream data = rs.getBinaryStream("data");
Plaintext.Type type = Plaintext.Type.valueOf(rs.getString("type"));
- Plaintext.Builder builder = Plaintext.readWithoutSignature(type, data.getBinaryStream());
+ Plaintext.Builder builder = Plaintext.readWithoutSignature(type, data);
long id = rs.getLong("id");
builder.id(id);
builder.IV(new InventoryVector(iv));
- builder.from(ctx.getAddressRepo().getAddress(rs.getString("sender")));
- builder.to(ctx.getAddressRepo().getAddress(rs.getString("recipient")));
+ builder.from(ctx.getAddressRepository().getAddress(rs.getString("sender")));
+ builder.to(ctx.getAddressRepository().getAddress(rs.getString("recipient")));
builder.sent(rs.getLong("sent"));
builder.received(rs.getLong("received"));
builder.status(Plaintext.Status.valueOf(rs.getString("status")));
@@ -144,12 +188,12 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
public void save(Plaintext message) {
// save from address if necessary
if (message.getId() == null) {
- BitmessageAddress savedAddress = ctx.getAddressRepo().getAddress(message.getFrom().getAddress());
+ BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(message.getFrom().getAddress());
if (savedAddress == null || savedAddress.getPrivateKey() == null) {
if (savedAddress != null && savedAddress.getAlias() != null) {
message.getFrom().setAlias(savedAddress.getAlias());
}
- ctx.getAddressRepo().save(message.getFrom());
+ ctx.getAddressRepository().save(message.getFrom());
}
}
@@ -190,7 +234,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, initial_hash) 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());
@@ -199,6 +244,7 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
ps.setLong(6, message.getSent());
ps.setLong(7, message.getReceived());
ps.setString(8, message.getStatus() != null ? message.getStatus().name() : null);
+ ps.setBytes(9, message.getInitialHash());
ps.executeUpdate();
@@ -210,20 +256,33 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private void update(Connection connection, Plaintext message) throws SQLException, IOException {
PreparedStatement ps = connection.prepareStatement(
- "UPDATE Message SET iv=?, sent=?, received=?, status=? WHERE id=?");
+ "UPDATE Message SET iv=?, sent=?, received=?, status=?, initial_hash=? WHERE id=?");
ps.setBytes(1, message.getInventoryVector() != null ? message.getInventoryVector().getHash() : null);
ps.setLong(2, message.getSent());
ps.setLong(3, message.getReceived());
ps.setString(4, message.getStatus() != null ? message.getStatus().name() : null);
- ps.setLong(5, (Long) message.getId());
+ ps.setBytes(5, message.getInitialHash());
+ ps.setLong(6, (Long) message.getId());
ps.executeUpdate();
}
@Override
public void remove(Plaintext message) {
try (Connection connection = config.getConnection()) {
- Statement stmt = connection.createStatement();
- stmt.executeUpdate("DELETE FROM Message WHERE id = " + message.getId());
+ try {
+ connection.setAutoCommit(false);
+ Statement stmt = connection.createStatement();
+ stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id = " + message.getId());
+ stmt.executeUpdate("DELETE FROM Message WHERE id = " + message.getId());
+ connection.commit();
+ } catch (SQLException e) {
+ try {
+ connection.rollback();
+ } catch (SQLException e1) {
+ LOG.debug(e1.getMessage(), e);
+ }
+ LOG.error(e.getMessage(), e);
+ }
} catch (SQLException e) {
LOG.error(e.getMessage(), e);
}
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java
new file mode 100644
index 0000000..9268311
--- /dev/null
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java
@@ -0,0 +1,93 @@
+package ch.dissem.bitmessage.repository;
+
+import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.factory.Factory;
+import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
+import ch.dissem.bitmessage.utils.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.*;
+import java.util.LinkedList;
+import java.util.List;
+
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
+/**
+ * @author Christian Basler
+ */
+public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWorkRepository {
+ private static final Logger LOG = LoggerFactory.getLogger(JdbcProofOfWorkRepository.class);
+
+ public JdbcProofOfWorkRepository(JdbcConfig config) {
+ super(config);
+ }
+
+ @Override
+ public Item getItem(byte[] initialHash) {
+ try (Connection connection = config.getConnection()) {
+ PreparedStatement ps = connection.prepareStatement("SELECT data, version, nonce_trials_per_byte, extra_bytes FROM POW WHERE initial_hash=?");
+ ps.setBytes(1, initialHash);
+ ResultSet rs = ps.executeQuery();
+ if (rs.next()) {
+ Blob data = rs.getBlob("data");
+ return new Item(
+ Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()),
+ rs.getLong("nonce_trials_per_byte"),
+ rs.getLong("extra_bytes")
+ );
+ } else {
+ throw new RuntimeException("Object requested that we don't have. Initial hash: " + Strings.hex(initialHash));
+ }
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public List getItems() {
+ try (Connection connection = config.getConnection()) {
+ List result = new LinkedList<>();
+ Statement stmt = connection.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT initial_hash FROM POW");
+ while (rs.next()) {
+ result.add(rs.getBytes("initial_hash"));
+ }
+ return result;
+ } catch (SQLException e) {
+ LOG.error(e.getMessage(), e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
+ try (Connection connection = config.getConnection()) {
+ PreparedStatement ps = connection.prepareStatement("INSERT INTO POW (initial_hash, data, version, nonce_trials_per_byte, extra_bytes) VALUES (?, ?, ?, ?, ?)");
+ ps.setBytes(1, security().getInitialHash(object));
+ writeBlob(ps, 2, object);
+ ps.setLong(3, object.getVersion());
+ ps.setLong(4, nonceTrialsPerByte);
+ ps.setLong(5, extraBytes);
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ LOG.debug("Error storing object of type " + object.getPayload().getClass().getSimpleName(), e);
+ throw new RuntimeException(e);
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void removeObject(byte[] initialHash) {
+ try (Connection connection = config.getConnection()) {
+ PreparedStatement ps = connection.prepareStatement("DELETE FROM POW WHERE initial_hash=?");
+ ps.setBytes(1, initialHash);
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ }
+}
diff --git a/repositories/src/main/resources/db/migration/V2.0__Update_table_message.sql b/repositories/src/main/resources/db/migration/V2.0__Update_table_message.sql
new file mode 100644
index 0000000..0d81858
--- /dev/null
+++ b/repositories/src/main/resources/db/migration/V2.0__Update_table_message.sql
@@ -0,0 +1,2 @@
+ALTER TABLE Message ADD COLUMN initial_hash BINARY(64);
+ALTER TABLE Message ADD CONSTRAINT initial_hash_unique UNIQUE(initial_hash);
\ No newline at end of file
diff --git a/repositories/src/main/resources/db/migration/V2.1__Create_table_POW.sql b/repositories/src/main/resources/db/migration/V2.1__Create_table_POW.sql
new file mode 100644
index 0000000..b39c6c5
--- /dev/null
+++ b/repositories/src/main/resources/db/migration/V2.1__Create_table_POW.sql
@@ -0,0 +1,7 @@
+CREATE TABLE POW (
+ initial_hash BINARY(64) PRIMARY KEY,
+ data BLOB NOT NULL,
+ version BIGINT NOT NULL,
+ nonce_trials_per_byte BIGINT NOT NULL,
+ extra_bytes BIGINT NOT NULL
+);
diff --git a/repositories/src/main/resources/nodes.txt b/repositories/src/main/resources/nodes.txt
deleted file mode 100644
index b728ba5..0000000
--- a/repositories/src/main/resources/nodes.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-[stream 1]
-
-[2604:2000:1380:9f:82e:148b:2746:d0c7]:8080
-5.45.99.75:8444
-75.167.159.54:8444
-95.165.168.168:8444
-85.180.139.241:8444
-158.222.211.81:8080
-178.62.12.187:8448
-24.188.198.204:8111
-109.147.204.113:1195
-178.11.46.221:8444
-
-[stream 2]
-# none yet
\ No newline at end of file
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..6f31299 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;
@@ -110,6 +110,17 @@ public class JdbcInventoryTest {
assertNotNull(inventory.getObject(object.getInventoryVector()));
}
+ @Test
+ public void testContains() {
+ ObjectMessage object = getObjectMessage(5, 0, getGetPubkey());
+
+ assertFalse(inventory.contains(object));
+
+ inventory.storeObject(object);
+
+ assertTrue(inventory.contains(object));
+ }
+
@Test
public void testCleanup() throws Exception {
assertNotNull(inventory.getObject(inventoryVectorIgnore));
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..c7c3614 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()
+ .cryptography(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..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;
@@ -27,7 +28,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..be386cd
--- /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.cryptography.bc.BouncyCryptography;
+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 {
+ BouncyCryptography security = new BouncyCryptography();
+ 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/settings.gradle b/settings.gradle
index 6452ec0..4caa813 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,6 +1,6 @@
rootProject.name = 'Jabit'
-include 'domain'
+include 'core'
include 'networking'
@@ -9,3 +9,9 @@ include 'repositories'
include 'demo'
include 'wif'
+
+include 'cryptography-sc'
+
+include 'cryptography-bc'
+
+include 'extensions'
\ No newline at end of file
diff --git a/wif/build.gradle b/wif/build.gradle
index 63566a3..93a0248 100644
--- a/wif/build.gradle
+++ b/wif/build.gradle
@@ -11,8 +11,9 @@ uploadArchives {
}
dependencies {
- compile project(':domain')
+ compile project(':core')
compile 'org.ini4j:ini4j:0.5.4'
testCompile 'junit:junit:4.11'
- testCompile 'org.mockito:mockito-core:1.+'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(':cryptography-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..b930c0a 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.cryptography.bc.BouncyCryptography;
import org.junit.Before;
import org.junit.Test;
@@ -34,9 +35,11 @@ public class WifExporterTest {
@Before
public void setUp() throws Exception {
ctx = new BitmessageContext.Builder()
+ .cryptography(new BouncyCryptography())
.networkHandler(mock(NetworkHandler.class))
.inventory(mock(Inventory.class))
.messageRepo(mock(MessageRepository.class))
+ .powRepo(mock(ProofOfWorkRepository.class))
.nodeRegistry(mock(NodeRegistry.class))
.addressRepo(repo)
.build();
@@ -70,14 +73,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());
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..fe8e15c 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.cryptography.bc.BouncyCryptography;
import org.junit.Before;
import org.junit.Test;
@@ -37,9 +38,11 @@ public class WifImporterTest {
@Before
public void setUp() throws Exception {
ctx = new BitmessageContext.Builder()
+ .cryptography(new BouncyCryptography())
.networkHandler(mock(NetworkHandler.class))
.inventory(mock(Inventory.class))
.messageRepo(mock(MessageRepository.class))
+ .powRepo(mock(ProofOfWorkRepository.class))
.nodeRegistry(mock(NodeRegistry.class))
.addressRepo(repo)
.build();