From 2fae90c43373f3255226bfa5870a9e7d11b8e2bb Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Sun, 8 Nov 2015 19:29:26 +0100
Subject: [PATCH 01/87] Some code for sending acknowledgements - some of it
isn't tested - somehow the ack part seems to be empty, even though the flag
should be set
---
.../dissem/bitmessage/BitmessageContext.java | 4 +-
.../bitmessage/DefaultMessageListener.java | 23 ++++--
.../ch/dissem/bitmessage/InternalContext.java | 75 ++++++++++++-------
.../bitmessage/entity/BitmessageAddress.java | 8 ++
.../dissem/bitmessage/entity/Plaintext.java | 72 +++++++++++++++---
.../dissem/bitmessage/entity/payload/Ack.java | 33 ++++++++
.../bitmessage/entity/payload/Pubkey.java | 4 +
.../bitmessage/entity/valueobject/Label.java | 1 +
.../ch/dissem/bitmessage/factory/Factory.java | 7 ++
9 files changed, 181 insertions(+), 46 deletions(-)
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/entity/payload/Ack.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 9f4d9a3..8603063 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -156,6 +156,7 @@ public class BitmessageContext {
} else {
LOG.info("Sending message.");
msg.setStatus(DOING_PROOF_OF_WORK);
+ msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX));
ctx.getMessageRepository().save(msg);
ctx.send(
from,
@@ -165,9 +166,6 @@ public class BitmessageContext {
ctx.getNonceTrialsPerByte(to),
ctx.getExtraBytes(to)
);
- msg.setStatus(SENT);
- msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
- ctx.getMessageRepository().save(msg);
}
}
});
diff --git a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index e069704..c409327 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -118,15 +118,24 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
for (BitmessageAddress identity : ctx.getAddressRepo().getIdentities()) {
try {
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
- msg.getPlaintext().setTo(identity);
- if (!object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey())) {
+ Plaintext plaintext = msg.getPlaintext();
+ plaintext.setTo(identity);
+ if (!object.isSignatureValid(plaintext.getFrom().getPubkey())) {
LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
} else {
- msg.getPlaintext().setStatus(RECEIVED);
- msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD));
- msg.getPlaintext().setInventoryVector(object.getInventoryVector());
- ctx.getMessageRepository().save(msg.getPlaintext());
- listener.receive(msg.getPlaintext());
+ plaintext.setStatus(RECEIVED);
+ plaintext.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD));
+ plaintext.setInventoryVector(object.getInventoryVector());
+ ctx.getMessageRepository().save(plaintext);
+ listener.receive(plaintext);
+
+ if (identity.has(Pubkey.Feature.DOES_ACK)) {
+ ObjectMessage ack = plaintext.getAckMessage();
+ if (ack != null) {
+ ctx.getInventory().storeObject(ack);
+ ctx.getNetworkHandler().offer(ack.getInventoryVector());
+ }
+ }
}
break;
} catch (DecryptionFailedException ignore) {
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 7a89978..1614548 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -17,9 +17,8 @@
package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.*;
-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.entity.payload.*;
+import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Singleton;
import ch.dissem.bitmessage.utils.UnixTime;
@@ -29,6 +28,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.TreeSet;
+import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
@@ -162,39 +162,35 @@ public class InternalContext {
public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
final long timeToLive, final long nonceTrialsPerByte, final long extraBytes) {
try {
- if (to == null) to = from;
+ final BitmessageAddress recipient = (to != null ? to : from);
long expires = UnixTime.now(+timeToLive);
LOG.info("Expires at " + expires);
final ObjectMessage object = new ObjectMessage.Builder()
- .stream(to.getStream())
+ .stream(recipient.getStream())
.expiresTime(expires)
.payload(payload)
.build();
if (object.isSigned()) {
object.sign(from.getPrivateKey());
}
- if (payload instanceof Broadcast) {
- ((Broadcast) payload).encrypt();
- } else if (payload instanceof Encrypted) {
- object.encrypt(to.getPubkey());
+ if (payload instanceof Msg && recipient.has(Pubkey.Feature.DOES_ACK)) {
+ ObjectMessage ackMessage = ((Msg) payload).getPlaintext().getAckMessage();
+ messageCallback.proofOfWorkStarted(payload);
+ security.doProofOfWork(ackMessage, networkNonceTrialsPerByte, networkExtraBytes, new ProofOfWorkEngine.Callback() {
+ @Override
+ public void onNonceCalculated(byte[] nonce) {
+ object.encrypt(recipient.getPubkey());
+ security.doProofOfWork(object, nonceTrialsPerByte, extraBytes, new ProofOfWorkCallback(object, payload));
+ }
+ });
+ } else {
+ if (payload instanceof Broadcast) {
+ ((Broadcast) payload).encrypt();
+ } else if (payload instanceof Encrypted) {
+ object.encrypt(recipient.getPubkey());
+ }
+ security.doProofOfWork(object, nonceTrialsPerByte, extraBytes, new ProofOfWorkCallback(object, payload));
}
- messageCallback.proofOfWorkStarted(payload);
- security.doProofOfWork(object, nonceTrialsPerByte, extraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- object.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(payload);
- if (payload instanceof PlaintextHolder) {
- Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
- plaintext.setInventoryVector(object.getInventoryVector());
- messageRepository.save(plaintext);
- }
- inventory.storeObject(object);
- networkHandler.offer(object.getInventoryVector());
- messageCallback.messageOffered(payload, object.getInventoryVector());
- }
- });
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -266,4 +262,31 @@ public class InternalContext {
public interface ContextHolder {
void setContext(InternalContext context);
}
+
+ private class ProofOfWorkCallback implements ProofOfWorkEngine.Callback {
+ private final ObjectMessage object;
+ private final ObjectPayload payload;
+
+ private ProofOfWorkCallback(ObjectMessage object, ObjectPayload payload) {
+ this.object = object;
+ this.payload = payload;
+ }
+
+ @Override
+ public void onNonceCalculated(byte[] nonce) {
+ object.setNonce(nonce);
+ messageCallback.proofOfWorkCompleted(payload);
+ if (payload instanceof PlaintextHolder) {
+ Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
+ plaintext.setInventoryVector(object.getInventoryVector());
+ plaintext.setStatus(SENT);
+ plaintext.removeLabel(Label.Type.OUTBOX);
+ plaintext.addLabels(messageRepository.getLabels(Label.Type.SENT));
+ messageRepository.save(plaintext);
+ }
+ inventory.storeObject(object);
+ networkHandler.offer(object.getInventoryVector());
+ messageCallback.messageOffered(payload, object.getInventoryVector());
+ }
+ }
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
index 0441e6e..380e431 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
@@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.AccessCounter;
@@ -220,4 +221,11 @@ public class BitmessageAddress implements Serializable {
public void setSubscribed(boolean subscribed) {
this.subscribed = subscribed;
}
+
+ public boolean has(Feature feature) {
+ if (pubkey == null || feature == null) {
+ return false;
+ }
+ return feature.isActive(pubkey.getBehaviorBitfield());
+ }
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index eb0a60f..34ecd52 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -16,6 +16,7 @@
package ch.dissem.bitmessage.entity;
+import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.factory.Factory;
@@ -26,6 +27,8 @@ import ch.dissem.bitmessage.utils.UnixTime;
import java.io.*;
import java.util.*;
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
/**
* The unencrypted message to be sent by 'msg' or 'broadcast'.
*/
@@ -34,7 +37,8 @@ public class Plaintext implements Streamable {
private final BitmessageAddress from;
private final long encoding;
private final byte[] message;
- private final byte[] ack;
+ private final byte[] ackData;
+ private ObjectMessage ackMessage;
private Object id;
private InventoryVector inventoryVector;
private BitmessageAddress to;
@@ -53,7 +57,13 @@ public class Plaintext implements Streamable {
to = builder.to;
encoding = builder.encoding;
message = builder.message;
- ack = builder.ack;
+ ackData = builder.ackData;
+ if (builder.ackMessage != null) {
+ ackMessage = Factory.getObjectMessage(
+ 3,
+ new ByteArrayInputStream(builder.ackMessage),
+ builder.ackMessage.length);
+ }
signature = builder.signature;
status = builder.status;
sent = builder.sent;
@@ -159,8 +169,15 @@ public class Plaintext implements Streamable {
Encode.varInt(message.length, out);
out.write(message);
if (type == Type.MSG) {
- Encode.varInt(ack.length, out);
- out.write(ack);
+ if (to.has(Pubkey.Feature.DOES_ACK)) {
+ ByteArrayOutputStream ack = new ByteArrayOutputStream();
+ ackMessage.write(ack);
+ byte[] data = ack.toByteArray();
+ Encode.varInt(data.length, out);
+ out.write(data);
+ } else {
+ Encode.varInt(0, out);
+ }
}
if (includeSignature) {
if (signature == null) {
@@ -234,7 +251,7 @@ public class Plaintext implements Streamable {
return Objects.equals(encoding, plaintext.encoding) &&
Objects.equals(from, plaintext.from) &&
Arrays.equals(message, plaintext.message) &&
- Arrays.equals(ack, plaintext.ack) &&
+ Arrays.equals(ackData, plaintext.ackData) &&
Arrays.equals(to.getRipe(), plaintext.to.getRipe()) &&
Arrays.equals(signature, plaintext.signature) &&
Objects.equals(status, plaintext.status) &&
@@ -245,21 +262,46 @@ public class Plaintext implements Streamable {
@Override
public int hashCode() {
- return Objects.hash(from, encoding, message, ack, to, signature, status, sent, received, labels);
+ return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels);
}
public void addLabels(Label... labels) {
if (labels != null) {
- Collections.addAll(this.labels, labels);
+ for (Label label : labels) {
+ this.labels.add(label);
+ }
}
}
public void addLabels(Collection
+ *
+ * @deprecated use {@link TTL#pubkey(long)} instead.
*/
public Builder pubkeyTTL(long days) {
if (days < 0 || days > 28 * DAY) throw new IllegalArgumentException("TTL must be between 1 and 28 days");
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java
index 50164a3..fb45e50 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java
@@ -25,16 +25,18 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Semaphore;
+import java.util.concurrent.*;
import static ch.dissem.bitmessage.utils.Bytes.inc;
+import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool;
/**
* A POW engine using all available CPU cores.
*/
public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);
- private static final Semaphore semaphore = new Semaphore(1, true);
+ private final ExecutorService waiterPool = Executors.newSingleThreadExecutor(pool("POW-waiter").daemon().build());
+ private final ExecutorService workerPool = Executors.newCachedThreadPool(pool("POW-worker").daemon().build());
/**
* This method will block until all pending nonce calculations are done, but not wait for its own calculation
@@ -46,42 +48,59 @@ public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
*/
@Override
- public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
- try {
- semaphore.acquire();
- } catch (InterruptedException e) {
- throw new ApplicationException(e);
- }
- callback = new CallbackWrapper(callback);
- int cores = Runtime.getRuntime().availableProcessors();
- if (cores > 255) cores = 255;
- LOG.info("Doing POW using " + cores + " cores");
- List workers = new ArrayList<>(cores);
- for (int i = 0; i < cores; i++) {
- 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();
- }
+ public void calculateNonce(final byte[] initialHash, final byte[] target, final Callback callback) {
+ waiterPool.execute(new Runnable() {
+ @Override
+ public void run() {
+ long startTime = System.currentTimeMillis();
+
+ int cores = Runtime.getRuntime().availableProcessors();
+ if (cores > 255) cores = 255;
+ LOG.info("Doing POW using " + cores + " cores");
+ List workers = new ArrayList<>(cores);
+ for (int i = 0; i < cores; i++) {
+ Worker w = new Worker((byte) cores, i, initialHash, target);
+ workers.add(w);
+ }
+ List> futures = new ArrayList<>(cores);
+ 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.
+ futures.add(workerPool.submit(w));
+ }
+ try {
+ while (!Thread.interrupted()) {
+ for (Future future : futures) {
+ if (future.isDone()) {
+ callback.onNonceCalculated(initialHash, future.get());
+ LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds");
+ for (Future f : futures) {
+ f.cancel(true);
+ }
+ return;
+ }
+ }
+ Thread.sleep(100);
+ }
+ LOG.error("POW waiter thread interrupted - this should not happen!");
+ } catch (ExecutionException e) {
+ LOG.error(e.getMessage(), e);
+ } catch (InterruptedException e) {
+ LOG.error("POW waiter thread interrupted - this should not happen!", e);
+ }
+ }
+ });
}
- private static class Worker extends Thread {
- private final Callback callback;
+ private class Worker implements Callable {
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];
- public Worker(List workers, byte numberOfCores, int core, byte[] initialHash, byte[] target,
- Callback callback) {
- this.callback = callback;
+ Worker(byte numberOfCores, int core, byte[] initialHash, byte[] target) {
this.numberOfCores = numberOfCores;
- this.workers = workers;
this.initialHash = initialHash;
this.target = target;
this.nonce[7] = (byte) core;
@@ -94,49 +113,16 @@ public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
}
@Override
- public void run() {
+ public byte[] call() throws Exception {
do {
inc(nonce, numberOfCores);
mda.update(nonce);
mda.update(initialHash);
if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) {
- synchronized (callback) {
- if (!Thread.interrupted()) {
- for (Worker w : workers) {
- w.interrupt();
- }
- // Clear interrupted flag for callback
- Thread.interrupted();
- callback.onNonceCalculated(initialHash, nonce);
- }
- }
- return;
+ return nonce;
}
} 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);
- }
- }
+ return null;
}
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java b/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java
new file mode 100644
index 0000000..36cf5b9
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 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.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ThreadFactoryBuilder {
+ private final String namePrefix;
+ private int prio = Thread.NORM_PRIORITY;
+ private boolean daemon = false;
+
+ private ThreadFactoryBuilder(String pool) {
+ this.namePrefix = pool + "-thread-";
+ }
+
+
+ public static ThreadFactoryBuilder pool(String name) {
+ return new ThreadFactoryBuilder(name);
+ }
+
+ public ThreadFactoryBuilder lowPrio() {
+ prio = Thread.MIN_PRIORITY;
+ return this;
+ }
+
+ public ThreadFactoryBuilder daemon() {
+ daemon = true;
+ return this;
+ }
+
+ public ThreadFactory build() {
+ SecurityManager s = System.getSecurityManager();
+ final ThreadGroup group = (s != null) ? s.getThreadGroup() :
+ Thread.currentThread().getThreadGroup();
+
+ return new ThreadFactory() {
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(group, r,
+ namePrefix + threadNumber.getAndIncrement(),
+ 0);
+ t.setPriority(prio);
+ t.setDaemon(daemon);
+ return t;
+ }
+ };
+ }
+}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index 43333ac..289aa79 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -28,8 +28,7 @@ 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 org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import ch.dissem.bitmessage.utils.ThreadFactoryBuilder;
import java.io.IOException;
import java.net.InetAddress;
@@ -40,35 +39,27 @@ import java.util.concurrent.*;
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 ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool;
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;
final Collection connections = new ConcurrentLinkedQueue<>();
- private final ExecutorService pool;
+ private final ExecutorService pool = Executors.newCachedThreadPool(
+ pool("network")
+ .lowPrio()
+ .daemon()
+ .build());
private InternalContext ctx;
private ServerRunnable server;
private volatile boolean running;
final 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;
From ea2cd7bf5373c0a73de9ec06980fdc1ddeb60f91 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 29 Apr 2016 15:29:22 +0200
Subject: [PATCH 34/87] Improved labeler to cover all cases, and fixed when
labels are set while sending (outbox vs sent) Removed message callback with
both didn't work and is obsolete (use a labeler descendant)
---
.../bitmessage/BaseMessageCallback.java | 47 -----------------
.../dissem/bitmessage/BitmessageContext.java | 24 ++-------
.../bitmessage/DefaultMessageListener.java | 5 +-
.../ch/dissem/bitmessage/InternalContext.java | 44 +++-------------
.../ch/dissem/bitmessage/MessageCallback.java | 52 -------------------
.../dissem/bitmessage/ProofOfWorkService.java | 1 +
.../bitmessage/ports/DefaultLabeler.java | 40 +++++++++++---
.../ch/dissem/bitmessage/ports/Labeler.java | 14 ++++-
.../bitmessage/BitmessageContextTest.java | 1 -
9 files changed, 58 insertions(+), 170 deletions(-)
delete mode 100644 core/src/main/java/ch/dissem/bitmessage/BaseMessageCallback.java
delete mode 100644 core/src/main/java/ch/dissem/bitmessage/MessageCallback.java
diff --git a/core/src/main/java/ch/dissem/bitmessage/BaseMessageCallback.java b/core/src/main/java/ch/dissem/bitmessage/BaseMessageCallback.java
deleted file mode 100644
index bf46b74..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/BaseMessageCallback.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2016 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;
-
-/**
- * Default implementation that doesn't do anything.
- *
- * @author Christian Basler
- */
-public class BaseMessageCallback implements MessageCallback {
- @Override
- public void proofOfWorkStarted(ObjectPayload message) {
- // No op
- }
-
- @Override
- public void proofOfWorkCompleted(ObjectPayload message) {
- // No op
- }
-
- @Override
- public void messageOffered(ObjectPayload message, InventoryVector iv) {
- // No op
- }
-
- @Override
- public void messageAcknowledged(InventoryVector iv) {
- // No op
- }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index c2fb84f..682aa80 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -22,7 +22,6 @@ import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.payload.ObjectType;
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.ApplicationException;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
@@ -37,11 +36,12 @@ import java.net.InetAddress;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
-import java.util.concurrent.*;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
-import static ch.dissem.bitmessage.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.*;
@@ -155,8 +155,8 @@ public class BitmessageContext {
.from(from)
.to(to)
.message(subject, message)
- .labels(messages().getLabels(Label.Type.SENT))
.build();
+ labeler().markAsSending(msg);
send(msg);
}
@@ -171,15 +171,11 @@ public class BitmessageContext {
ctx.requestPubkey(to);
}
if (to.getPubkey() == null) {
- msg.setStatus(PUBKEY_REQUESTED);
- msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX));
ctx.getMessageRepository().save(msg);
}
}
if (to == null || to.getPubkey() != null) {
LOG.info("Sending message.");
- msg.setStatus(DOING_PROOF_OF_WORK);
- msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX));
ctx.getMessageRepository().save(msg);
ctx.send(
msg.getFrom(),
@@ -187,9 +183,6 @@ public class BitmessageContext {
wrapInObjectPayload(msg),
TTL.msg()
);
- msg.setStatus(SENT);
- msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
- ctx.getMessageRepository().save(msg);
}
}
@@ -310,7 +303,6 @@ public class BitmessageContext {
ProofOfWorkRepository proofOfWorkRepository;
ProofOfWorkEngine proofOfWorkEngine;
Cryptography cryptography;
- MessageCallback messageCallback;
CustomCommandHandler customCommandHandler;
Labeler labeler;
Listener listener;
@@ -358,11 +350,6 @@ public class BitmessageContext {
return this;
}
- public Builder messageCallback(MessageCallback callback) {
- this.messageCallback = callback;
- return this;
- }
-
public Builder customCommandHandler(CustomCommandHandler handler) {
this.customCommandHandler = handler;
return this;
@@ -429,9 +416,6 @@ public class BitmessageContext {
if (proofOfWorkEngine == null) {
proofOfWorkEngine = new MultiThreadedPOWEngine();
}
- if (messageCallback == null) {
- messageCallback = new BaseMessageCallback();
- }
if (labeler == null) {
labeler = new DefaultLabeler();
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index e8be606..ce7ab8d 100644
--- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -109,7 +109,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
List messages = ctx.getMessageRepository().findMessages(PUBKEY_REQUESTED, address);
LOG.info("Sending " + messages.size() + " messages for contact " + address);
for (Plaintext msg : messages) {
- msg.setStatus(DOING_PROOF_OF_WORK);
+ ctx.getLabeler().markAsSending(msg);
ctx.getMessageRepository().save(msg);
ctx.send(
msg.getFrom(),
@@ -117,8 +117,6 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
new Msg(msg),
+2 * DAY
);
- msg.setStatus(SENT);
- ctx.getMessageRepository().save(msg);
}
}
@@ -158,7 +156,6 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
protected void receive(InventoryVector iv, Plaintext msg) {
- msg.setStatus(RECEIVED);
msg.setInventoryVector(iv);
labeler.setLabels(msg);
ctx.getMessageRepository().save(msg);
diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 586adf4..15d6958 100644
--- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -18,7 +18,6 @@ package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.*;
import ch.dissem.bitmessage.entity.payload.*;
-import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Singleton;
@@ -31,8 +30,6 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.TreeSet;
-import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT;
-
/**
* 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
@@ -55,9 +52,9 @@ public class InternalContext {
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 Labeler labeler;
private final TreeSet streams = new TreeSet<>();
private final int port;
@@ -76,11 +73,11 @@ public class InternalContext {
this.proofOfWorkService = new ProofOfWorkService();
this.proofOfWorkEngine = builder.proofOfWorkEngine;
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.labeler = builder.labeler;
Singleton.initialize(cryptography);
@@ -96,8 +93,7 @@ public class InternalContext {
}
init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
- proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine,
- messageCallback, customCommandHandler, builder.labeler);
+ proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, builder.labeler);
for (BitmessageAddress identity : addressRepository.getIdentities()) {
streams.add(identity.getStream());
}
@@ -147,6 +143,10 @@ public class InternalContext {
return proofOfWorkService;
}
+ public Labeler getLabeler() {
+ return labeler;
+ }
+
public long[] getStreams() {
long[] result = new long[streams.size()];
int i = 0;
@@ -176,7 +176,6 @@ public class InternalContext {
}
if (payload instanceof Msg && recipient.has(Pubkey.Feature.DOES_ACK)) {
ObjectMessage ackMessage = ((Msg) payload).getPlaintext().getAckMessage();
- messageCallback.proofOfWorkStarted(payload);
cryptography.doProofOfWork(ackMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, new ProofOfWorkEngine.Callback() {
@Override
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
@@ -208,7 +207,6 @@ public class InternalContext {
.build();
response.sign(identity.getPrivateKey());
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) {
@@ -245,7 +243,6 @@ public class InternalContext {
.expiresTime(expires)
.payload(new GetPubkey(contact))
.build();
- messageCallback.proofOfWorkStarted(request.getPayload());
proofOfWorkService.doProofOfWork(request);
}
@@ -302,31 +299,4 @@ public class InternalContext {
public interface ContextHolder {
void setContext(InternalContext context);
}
-
- private class ProofOfWorkCallback implements ProofOfWorkEngine.Callback {
- private final ObjectMessage object;
- private final ObjectPayload payload;
-
- private ProofOfWorkCallback(ObjectMessage object, ObjectPayload payload) {
- this.object = object;
- this.payload = payload;
- }
-
- @Override
- public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
- object.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(payload);
- if (payload instanceof PlaintextHolder) {
- Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
- plaintext.setInventoryVector(object.getInventoryVector());
- plaintext.setStatus(SENT);
- plaintext.removeLabel(Label.Type.OUTBOX);
- plaintext.addLabels(messageRepository.getLabels(Label.Type.SENT));
- messageRepository.save(plaintext);
- }
- inventory.storeObject(object);
- networkHandler.offer(object.getInventoryVector());
- messageCallback.messageOffered(payload, object.getInventoryVector());
- }
- }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/MessageCallback.java b/core/src/main/java/ch/dissem/bitmessage/MessageCallback.java
deleted file mode 100644
index d09ff97..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/MessageCallback.java
+++ /dev/null
@@ -1,52 +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.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
index 93e461f..98fc9a1 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -66,6 +66,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
Plaintext plaintext = messageRepo.getMessage(initialHash);
if (plaintext != null) {
plaintext.setInventoryVector(object.getInventoryVector());
+ ctx.getLabeler().markAsSent(plaintext);
messageRepo.save(plaintext);
}
ctx.getInventory().storeObject(object);
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java
index 1598ce1..4f21b3e 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java
@@ -20,13 +20,14 @@ import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
-import java.util.Iterator;
+import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
public class DefaultLabeler implements Labeler, InternalContext.ContextHolder {
private InternalContext ctx;
@Override
public void setLabels(Plaintext msg) {
+ msg.setStatus(RECEIVED);
if (msg.getType() == Plaintext.Type.BROADCAST) {
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD));
} else {
@@ -35,14 +36,37 @@ public class DefaultLabeler implements Labeler, InternalContext.ContextHolder {
}
@Override
- public void markAsRead(Plaintext msg) {
- Iterator iterator = msg.getLabels().iterator();
- while (iterator.hasNext()) {
- Label label = iterator.next();
- if (label.getType() == Label.Type.UNREAD) {
- iterator.remove();
- }
+ public void markAsDraft(Plaintext msg) {
+ msg.setStatus(DRAFT);
+ msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.DRAFT));
+ }
+
+ @Override
+ public void markAsSending(Plaintext msg) {
+ if (msg.getTo() != null || msg.getTo().getPubkey() == null) {
+ msg.setStatus(PUBKEY_REQUESTED);
+ } else {
+ msg.setStatus(DOING_PROOF_OF_WORK);
}
+ msg.removeLabel(Label.Type.DRAFT);
+ msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX));
+ }
+
+ @Override
+ public void markAsSent(Plaintext msg) {
+ msg.setStatus(SENT);
+ msg.removeLabel(Label.Type.OUTBOX);
+ msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
+ }
+
+ @Override
+ public void markAsAcknowledged(Plaintext msg) {
+ msg.setStatus(SENT_ACKNOWLEDGED);
+ }
+
+ @Override
+ public void markAsRead(Plaintext msg) {
+ msg.removeLabel(Label.Type.UNREAD);
}
@Override
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java b/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java
index 95febaf..5e52845 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java
@@ -19,7 +19,11 @@ package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.entity.Plaintext;
/**
- * Defines and sets labels
+ * Defines and sets labels. Note that it should also update the status field of a message.
+ *
+ * As the labeler gets called whenever the state of a message changes, it can also be used
+ * as a listener.
+ *
*/
public interface Labeler {
/**
@@ -29,6 +33,14 @@ public interface Labeler {
*/
void setLabels(Plaintext msg);
+ void markAsDraft(Plaintext msg);
+
+ void markAsSending(Plaintext msg);
+
+ void markAsSent(Plaintext msg);
+
+ void markAsAcknowledged(Plaintext msg);
+
void markAsRead(Plaintext msg);
void markAsUnread(Plaintext msg);
diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
index 7ca0d87..ed095a8 100644
--- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
@@ -56,7 +56,6 @@ public class BitmessageContextTest {
.cryptography(new BouncyCryptography())
.inventory(mock(Inventory.class))
.listener(listener)
- .messageCallback(mock(MessageCallback.class))
.messageRepo(mock(MessageRepository.class))
.networkHandler(mock(NetworkHandler.class))
.nodeRegistry(mock(NodeRegistry.class))
From c7594795f00ae23d9be89bf981e7c4429e35b7f6 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Mon, 2 May 2016 11:11:29 +0200
Subject: [PATCH 35/87] Fixed tests & bugs, removed Ack payload type (a
GenericPayload is now used) - SystemTest don't work yet, sending messages
seems broken - ProofOfWorkService needs some work, the current solution is a
hack (and might be the reason above tests are broken)
---
.../dissem/bitmessage/BitmessageContext.java | 1 +
.../ch/dissem/bitmessage/InternalContext.java | 5 +-
.../bitmessage/entity/ObjectMessage.java | 29 +++++++++++-
.../dissem/bitmessage/entity/Plaintext.java | 4 +-
.../dissem/bitmessage/entity/payload/Ack.java | 33 -------------
.../bitmessage/entity/payload/Broadcast.java | 15 ++++++
.../entity/payload/GenericPayload.java | 2 +-
.../dissem/bitmessage/entity/payload/Msg.java | 16 +++++++
.../entity/payload/ObjectPayload.java | 1 +
.../ch/dissem/bitmessage/factory/Factory.java | 11 +++--
.../bitmessage/BitmessageContextTest.java | 46 +++++++++++++++++--
.../DefaultMessageListenerTest.java | 1 +
.../ch/dissem/bitmessage/EncryptionTest.java | 2 +-
.../bitmessage/ProofOfWorkServiceTest.java | 1 +
.../bitmessage/entity/SerializationTest.java | 2 +-
.../ch/dissem/bitmessage/utils/TestUtils.java | 2 +-
.../bitmessage/security/CryptographyTest.java | 8 ++--
.../extensions/CryptoCustomMessageTest.java | 2 +-
18 files changed, 124 insertions(+), 57 deletions(-)
delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/Ack.java
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 682aa80..b19c303 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -164,6 +164,7 @@ public class BitmessageContext {
if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) {
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
}
+ msg.setStatus(Plaintext.Status.DRAFT);
BitmessageAddress to = msg.getTo();
if (to != null) {
if (to.getPubkey() == null) {
diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 15d6958..58333d5 100644
--- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -175,10 +175,13 @@ public class InternalContext {
object.sign(from.getPrivateKey());
}
if (payload instanceof Msg && recipient.has(Pubkey.Feature.DOES_ACK)) {
- ObjectMessage ackMessage = ((Msg) payload).getPlaintext().getAckMessage();
+ final ObjectMessage ackMessage = ((Msg) payload).getPlaintext().getAckMessage();
cryptography.doProofOfWork(ackMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, new ProofOfWorkEngine.Callback() {
@Override
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
+ // FIXME: the message gets lost if calculation is cancelled
+ // (e.g. by terminating the application)
+ ackMessage.setNonce(nonce);
object.encrypt(recipient.getPubkey());
proofOfWorkService.doProofOfWork(recipient, object);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
index 68aa5ac..a0878c2 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
@@ -29,6 +29,8 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Objects;
import static ch.dissem.bitmessage.utils.Singleton.security;
@@ -55,7 +57,7 @@ public class ObjectMessage implements MessagePayload {
expiresTime = builder.expiresTime;
objectType = builder.objectType;
version = builder.payload.getVersion();
- stream = builder.streamNumber;
+ stream = builder.streamNumber > 0 ? builder.streamNumber : builder.payload.getStream();
payload = builder.payload;
}
@@ -230,4 +232,29 @@ public class ObjectMessage implements MessagePayload {
return new ObjectMessage(this);
}
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ObjectMessage that = (ObjectMessage) o;
+
+ return expiresTime == that.expiresTime &&
+ objectType == that.objectType &&
+ version == that.version &&
+ stream == that.stream &&
+ Objects.equals(payload, that.payload);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(nonce);
+ result = 31 * result + (int) (expiresTime ^ (expiresTime >>> 32));
+ result = 31 * result + (int) (objectType ^ (objectType >>> 32));
+ result = 31 * result + (int) (version ^ (version >>> 32));
+ result = 31 * result + (int) (stream ^ (stream >>> 32));
+ result = 31 * result + (payload != null ? payload.hashCode() : 0);
+ return result;
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index b34206a..c0a33ef 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -173,7 +173,7 @@ public class Plaintext implements Streamable {
Encode.varInt(message.length, out);
out.write(message);
if (type == Type.MSG) {
- if (to.has(Pubkey.Feature.DOES_ACK)) {
+ if (to.has(Pubkey.Feature.DOES_ACK) && getAckMessage() != null) {
ByteArrayOutputStream ack = new ByteArrayOutputStream();
getAckMessage().write(ack);
byte[] data = ack.toByteArray();
@@ -255,7 +255,7 @@ public class Plaintext implements Streamable {
return Objects.equals(encoding, plaintext.encoding) &&
Objects.equals(from, plaintext.from) &&
Arrays.equals(message, plaintext.message) &&
- Arrays.equals(ackData, plaintext.ackData) &&
+ Objects.equals(getAckMessage(), plaintext.getAckMessage()) &&
Arrays.equals(to.getRipe(), plaintext.to.getRipe()) &&
Arrays.equals(signature, plaintext.signature) &&
Objects.equals(status, plaintext.status) &&
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Ack.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Ack.java
deleted file mode 100644
index a8308b9..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Ack.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package ch.dissem.bitmessage.entity.payload;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * Created by chrigu on 06.11.15.
- */
-public class Ack extends ObjectPayload {
- private final long stream;
- private final byte[] data;
-
- public Ack(long version, long stream, byte[] data) {
- super(version);
- this.stream = stream;
- this.data = data;
- }
-
- @Override
- public ObjectType getType() {
- return ObjectType.MSG;
- }
-
- @Override
- public long getStream() {
- return stream;
- }
-
- @Override
- public void write(OutputStream out) throws IOException {
- out.write(data);
- }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
index 7e19e8f..1533a2d 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
@@ -23,6 +23,7 @@ import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import java.io.IOException;
+import java.util.Objects;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
import static ch.dissem.bitmessage.utils.Singleton.security;
@@ -96,4 +97,18 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted, Plai
public boolean isDecrypted() {
return plaintext != null;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Broadcast broadcast = (Broadcast) o;
+ return stream == broadcast.stream &&
+ (Objects.equals(encrypted, broadcast.encrypted) || Objects.equals(plaintext, broadcast.plaintext));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(stream);
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
index 66cc296..4ae4696 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
@@ -39,7 +39,7 @@ public class GenericPayload extends ObjectPayload {
this.data = data;
}
- public static GenericPayload read(long version, InputStream is, long stream, int length) throws IOException {
+ public static GenericPayload read(long version, long stream, InputStream is, int length) throws IOException {
return new GenericPayload(version, stream, Decode.bytes(is, length));
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
index 8974ce3..edc4643 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
@@ -24,6 +24,7 @@ import ch.dissem.bitmessage.exception.DecryptionFailedException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Objects;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
@@ -108,4 +109,19 @@ public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it.");
encrypted.write(out);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ Msg msg = (Msg) o;
+ return stream == msg.stream &&
+ (Objects.equals(encrypted, msg.encrypted) || Objects.equals(plaintext, msg.plaintext));
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) stream;
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
index 33da28d..a7dbaff 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
@@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Streamable;
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.io.IOException;
import java.io.OutputStream;
diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
index 48cafff..cfa3229 100644
--- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
@@ -31,6 +31,7 @@ import java.io.InputStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
+import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
@@ -155,7 +156,7 @@ public class Factory {
}
// fallback: just store the message - we don't really care what it is
LOG.trace("Unexpected object type: " + objectType);
- return GenericPayload.read(version, stream, streamNumber, length);
+ return GenericPayload.read(version, streamNumber, stream, length);
}
private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
@@ -177,7 +178,7 @@ public class Factory {
private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true);
- return pubkey != null ? pubkey : GenericPayload.read(version, stream, streamNumber, length);
+ return pubkey != null ? pubkey : GenericPayload.read(version, streamNumber, stream, length);
}
private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException {
@@ -192,7 +193,7 @@ public class Factory {
return V5Broadcast.read(stream, streamNumber, length);
default:
LOG.debug("Encountered unknown broadcast version " + version);
- return GenericPayload.read(version, stream, streamNumber, length);
+ return GenericPayload.read(version, streamNumber, stream, length);
}
}
@@ -208,7 +209,7 @@ public class Factory {
public static ObjectMessage createAck(Plaintext plaintext) {
if (plaintext == null || plaintext.getAckData() == null)
return null;
- Ack ack = new Ack(3, plaintext.getFrom().getStream(), plaintext.getAckData());
- return new ObjectMessage.Builder().payload(ack).build();
+ GenericPayload ack = new GenericPayload(3, plaintext.getFrom().getStream(), plaintext.getAckData());
+ return new ObjectMessage.Builder().objectType(MSG).payload(ack).build();
}
}
diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
index ed095a8..91dd94e 100644
--- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
@@ -22,19 +22,22 @@ import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.ports.*;
-import ch.dissem.bitmessage.utils.MessageMatchers;
-import ch.dissem.bitmessage.utils.Singleton;
-import ch.dissem.bitmessage.utils.TestUtils;
+import ch.dissem.bitmessage.utils.*;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
+import java.util.Collections;
+import java.util.stream.Collectors;
import static ch.dissem.bitmessage.entity.payload.ObjectType.*;
import static ch.dissem.bitmessage.utils.MessageMatchers.object;
+import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.*;
@@ -59,9 +62,41 @@ public class BitmessageContextTest {
.messageRepo(mock(MessageRepository.class))
.networkHandler(mock(NetworkHandler.class))
.nodeRegistry(mock(NodeRegistry.class))
- .powRepo(mock(ProofOfWorkRepository.class))
- .proofOfWorkEngine(mock(ProofOfWorkEngine.class))
+ .powRepo(spy(new ProofOfWorkRepository() {
+ Map items = new HashMap<>();
+
+ @Override
+ public Item getItem(byte[] initialHash) {
+ return items.get(new InventoryVector(initialHash));
+ }
+
+ @Override
+ public List getItems() {
+ List result = new LinkedList<>();
+ for (InventoryVector iv : items.keySet()) {
+ result.add(iv.getHash());
+ }
+ return result;
+ }
+
+ @Override
+ public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
+ items.put(new InventoryVector(security().getInitialHash(object)), new Item(object, nonceTrialsPerByte, extraBytes));
+ }
+
+ @Override
+ public void removeObject(byte[] initialHash) {
+ items.remove(initialHash);
+ }
+ }))
+ .proofOfWorkEngine(spy(new ProofOfWorkEngine() {
+ @Override
+ public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
+ callback.onNonceCalculated(initialHash, new byte[8]);
+ }
+ }))
.build();
+ TTL.msg(2 * MINUTE);
}
@Test
@@ -161,6 +196,7 @@ public class BitmessageContextTest {
public void ensureMessageIsSent() throws Exception {
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
"Subject", "Message");
+ assertEquals(1, ctx.internals().getProofOfWorkRepository().getItems().size());
verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce())
.putObject(object(MSG), eq(1000L), eq(1000L));
verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Plaintext.Type.MSG));
diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java
index e225924..eacaab2 100644
--- a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java
@@ -66,6 +66,7 @@ public class DefaultMessageListenerTest extends TestBase {
when(ctx.getMessageRepository()).thenReturn(messageRepo);
when(ctx.getInventory()).thenReturn(inventory);
when(ctx.getNetworkHandler()).thenReturn(networkHandler);
+ when(ctx.getLabeler()).thenReturn(mock(Labeler.class));
listener = new DefaultMessageListener(ctx, mock(Labeler.class), mock(BitmessageContext.Listener.class));
}
diff --git a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
index 9a24842..be57e4a 100644
--- a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
@@ -42,7 +42,7 @@ public class EncryptionTest extends TestBase {
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey());
- GenericPayload after = GenericPayload.read(0, cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 1, 100);
+ GenericPayload after = GenericPayload.read(0, 1, cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 100);
assertEquals(before, after);
}
diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java
index e66beb1..fa58cfe 100644
--- a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java
@@ -65,6 +65,7 @@ public class ProofOfWorkServiceTest {
when(ctx.getInventory()).thenReturn(inventory);
when(ctx.getNetworkHandler()).thenReturn(networkHandler);
when(ctx.getMessageRepository()).thenReturn(messageRepo);
+ when(ctx.getLabeler()).thenReturn(mock(Labeler.class));
proofOfWorkService = new ProofOfWorkService();
proofOfWorkService.setContext(ctx);
diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
index 5d8777a..aa08475 100644
--- a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
@@ -82,7 +82,7 @@ public class SerializationTest extends TestBase {
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.to(TestUtils.loadContact())
.message("Subject", "Message")
- .ack("ack".getBytes())
+ .ackData("ack".getBytes())
.signature(new byte[0])
.build();
ByteArrayOutputStream out = new ByteArrayOutputStream();
diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java
index 770039d..21f85ff 100644
--- a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java
+++ b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java
@@ -72,7 +72,7 @@ public class TestUtils {
public static BitmessageAddress loadContact() throws IOException, DecryptionFailedException {
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
- ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
+ ObjectMessage object = TestUtils.loadObjectMessage(3, "V4Pubkey.payload");
object.decrypt(address.getPublicDecryptionKey());
address.setPubkey((V4Pubkey) object.getPayload());
return address;
diff --git a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java
index 3e5695c..b0937d3 100644
--- a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java
+++ b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java
@@ -21,9 +21,7 @@ import java.io.IOException;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -82,7 +80,7 @@ public class CryptographyTest {
.nonce(new byte[8])
.expiresTime(UnixTime.now(+28 * DAY))
.objectType(0)
- .payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
+ .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0))
.build();
crypto.checkProofOfWork(objectMessage, 1000, 1000);
}
@@ -93,7 +91,7 @@ public class CryptographyTest {
.nonce(new byte[8])
.expiresTime(UnixTime.now(+2 * MINUTE))
.objectType(0)
- .payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
+ .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0))
.build();
final CallbackWaiter waiter = new CallbackWaiter<>();
crypto.doProofOfWork(objectMessage, 1000, 1000,
diff --git a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
index c1303e3..d451323 100644
--- a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
+++ b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
@@ -52,7 +52,7 @@ public class CryptoCustomMessageTest extends TestBase {
new CryptoCustomMessage.Reader() {
@Override
public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException {
- return GenericPayload.read(0, in, 1, 100);
+ return GenericPayload.read(0, 1, in, 100);
}
});
GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey());
From 678a48ac3fc729ff6c472b95e0d0b729f6052db0 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Thu, 5 May 2016 10:50:22 +0200
Subject: [PATCH 36/87] Fixed system test and ProofOfWorkService
---
.../dissem/bitmessage/BitmessageContext.java | 30 ++----
.../bitmessage/DefaultMessageListener.java | 7 +-
.../ch/dissem/bitmessage/InternalContext.java | 33 +++----
.../dissem/bitmessage/ProofOfWorkService.java | 60 ++++++++----
.../bitmessage/entity/BitmessageAddress.java | 16 ++--
.../bitmessage/entity/NetworkMessage.java | 4 +-
.../bitmessage/entity/ObjectMessage.java | 8 +-
.../dissem/bitmessage/entity/Plaintext.java | 4 +-
.../bitmessage/entity/payload/Broadcast.java | 4 +-
.../bitmessage/entity/payload/CryptoBox.java | 24 ++---
.../bitmessage/entity/payload/Pubkey.java | 6 +-
.../entity/valueobject/PrivateKey.java | 22 ++---
.../ch/dissem/bitmessage/factory/Factory.java | 6 +-
.../bitmessage/factory/V3MessageFactory.java | 4 +-
.../bitmessage/ports/MessageRepository.java | 2 +
.../ports/ProofOfWorkRepository.java | 14 +++
.../ch/dissem/bitmessage/utils/Singleton.java | 2 +-
.../bitmessage/BitmessageContextTest.java | 18 ++--
.../DefaultMessageListenerTest.java | 2 +-
.../ch/dissem/bitmessage/EncryptionTest.java | 4 +-
.../entity/BitmessageAddressTest.java | 8 +-
.../bitmessage/entity/SerializationTest.java | 4 +-
.../ports/ProofOfWorkEngineTest.java | 10 +-
.../extensions/CryptoCustomMessage.java | 6 +-
.../extensions/CryptoCustomMessageTest.java | 10 +-
.../bitmessage/networking/Connection.java | 4 +-
repositories/build.gradle | 1 +
.../repository/JdbcMessageRepository.java | 18 ++++
.../repository/JdbcProofOfWorkRepository.java | 66 ++++++++++----
.../db/migration/V3.1__Update_table_POW.sql | 2 +
.../repository/JdbcAddressRepositoryTest.java | 6 +-
.../repository/JdbcMessageRepositoryTest.java | 8 +-
.../JdbcProofOfWorkRepositoryTest.java | 91 ++++++++++++++++++-
.../ch/dissem/bitmessage/wif/WifExporter.java | 4 +-
.../ch/dissem/bitmessage/wif/WifImporter.java | 4 +-
35 files changed, 339 insertions(+), 173 deletions(-)
create mode 100644 repositories/src/main/resources/db/migration/V3.1__Update_table_POW.sql
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index b19c303..d46c36c 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -18,12 +18,9 @@ package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.*;
import ch.dissem.bitmessage.entity.payload.Broadcast;
-import ch.dissem.bitmessage.entity.payload.Msg;
-import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
-import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.*;
@@ -178,23 +175,16 @@ public class BitmessageContext {
if (to == null || to.getPubkey() != null) {
LOG.info("Sending message.");
ctx.getMessageRepository().save(msg);
- ctx.send(
- msg.getFrom(),
- to,
- wrapInObjectPayload(msg),
- TTL.msg()
- );
- }
- }
-
- private ObjectPayload wrapInObjectPayload(Plaintext msg) {
- switch (msg.getType()) {
- case MSG:
- return new Msg(msg);
- case BROADCAST:
- return Factory.getBroadcast(msg);
- default:
- throw new ApplicationException("Unknown message type " + msg.getType());
+ if (msg.getType() == MSG) {
+ ctx.send(msg, TTL.msg());
+ } else {
+ ctx.send(
+ msg.getFrom(),
+ to,
+ Factory.getBroadcast(msg),
+ TTL.msg()
+ );
+ }
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index ce7ab8d..14c405c 100644
--- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -111,12 +111,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
for (Plaintext msg : messages) {
ctx.getLabeler().markAsSending(msg);
ctx.getMessageRepository().save(msg);
- ctx.send(
- msg.getFrom(),
- msg.getTo(),
- new Msg(msg),
- +2 * DAY
- );
+ ctx.send(msg, +2 * DAY);
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 58333d5..884dd16 100644
--- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -160,6 +160,15 @@ public class InternalContext {
return port;
}
+ public void send(final Plaintext plaintext, final long timeToLive) {
+ if (plaintext.getAckMessage() != null) {
+ long expires = UnixTime.now(+timeToLive);
+ proofOfWorkService.doProofOfWorkWithAck(plaintext, expires);
+ } else {
+ send(plaintext.getFrom(), plaintext.getTo(), new Msg(plaintext), timeToLive);
+ }
+ }
+
public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
final long timeToLive) {
try {
@@ -174,26 +183,12 @@ public class InternalContext {
if (object.isSigned()) {
object.sign(from.getPrivateKey());
}
- if (payload instanceof Msg && recipient.has(Pubkey.Feature.DOES_ACK)) {
- final ObjectMessage ackMessage = ((Msg) payload).getPlaintext().getAckMessage();
- cryptography.doProofOfWork(ackMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
- // FIXME: the message gets lost if calculation is cancelled
- // (e.g. by terminating the application)
- ackMessage.setNonce(nonce);
- object.encrypt(recipient.getPubkey());
- proofOfWorkService.doProofOfWork(recipient, object);
- }
- });
- } else {
- if (payload instanceof Broadcast) {
- ((Broadcast) payload).encrypt();
- } else if (payload instanceof Encrypted) {
- object.encrypt(recipient.getPubkey());
- }
- proofOfWorkService.doProofOfWork(to, object);
+ if (payload instanceof Broadcast) {
+ ((Broadcast) payload).encrypt();
+ } else if (payload instanceof Encrypted) {
+ object.encrypt(recipient.getPubkey());
}
+ proofOfWorkService.doProofOfWork(to, object);
} catch (IOException e) {
throw new ApplicationException(e);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
index 98fc9a1..6df0cc0 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -1,14 +1,15 @@
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.entity.*;
+import ch.dissem.bitmessage.entity.payload.Broadcast;
+import ch.dissem.bitmessage.entity.payload.Msg;
+import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.ports.Cryptography;
import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
+import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,7 +17,7 @@ import java.util.List;
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* @author Christian Basler
@@ -35,7 +36,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
LOG.info("Doing POW for " + items.size() + " tasks.");
for (byte[] initialHash : items) {
- ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
+ Item item = powRepo.getItem(initialHash);
cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this);
}
}
@@ -59,25 +60,50 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this);
}
+ public void doProofOfWorkWithAck(Plaintext plaintext, long expirationTime) {
+ final ObjectMessage ack = plaintext.getAckMessage();
+ Item item = new Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES,
+ expirationTime, plaintext);
+ powRepo.putObject(item);
+ cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this);
+ }
+
@Override
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
- ObjectMessage object = powRepo.getItem(initialHash).object;
- object.setNonce(nonce);
- Plaintext plaintext = messageRepo.getMessage(initialHash);
- if (plaintext != null) {
- plaintext.setInventoryVector(object.getInventoryVector());
- ctx.getLabeler().markAsSent(plaintext);
- messageRepo.save(plaintext);
+ Item item = powRepo.getItem(initialHash);
+ if (item.message == null) {
+ ObjectMessage object = powRepo.getItem(initialHash).object;
+ object.setNonce(nonce);
+ Plaintext plaintext = messageRepo.getMessage(initialHash);
+ if (plaintext != null) {
+ plaintext.setInventoryVector(object.getInventoryVector());
+ ctx.getLabeler().markAsSent(plaintext);
+ messageRepo.save(plaintext);
+ }
+ ctx.getInventory().storeObject(object);
+ powRepo.removeObject(initialHash);
+ ctx.getNetworkHandler().offer(object.getInventoryVector());
+ } else {
+ item.message.getAckMessage().setNonce(nonce);
+ final ObjectMessage object = new ObjectMessage.Builder()
+ .stream(item.message.getStream())
+ .expiresTime(item.expirationTime)
+ .payload(new Msg(item.message))
+ .build();
+ if (object.isSigned()) {
+ object.sign(item.message.getFrom().getPrivateKey());
+ }
+ if (object.getPayload() instanceof Encrypted) {
+ object.encrypt(item.message.getTo().getPubkey());
+ }
+ doProofOfWork(item.message.getTo(), object);
}
- ctx.getInventory().storeObject(object);
- powRepo.removeObject(initialHash);
- ctx.getNetworkHandler().offer(object.getInventoryVector());
}
@Override
public void setContext(InternalContext ctx) {
this.ctx = ctx;
- this.cryptography = security();
+ this.cryptography = cryptography();
this.powRepo = ctx.getProofOfWorkRepository();
this.messageRepo = ctx.getMessageRepository();
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
index 84b349d..ce199c4 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
@@ -37,7 +37,7 @@ 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;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
@@ -74,19 +74,19 @@ public class BitmessageAddress implements Serializable {
Encode.varInt(version, os);
Encode.varInt(stream, os);
if (version < 4) {
- byte[] checksum = security().sha512(os.toByteArray(), ripe);
+ byte[] checksum = cryptography().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 = cryptography().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 = cryptography().doubleSha512(os.toByteArray());
os.write(checksum, 0, 4);
this.address = "BM-" + Base58.encode(os.toByteArray());
} catch (IOException e) {
@@ -147,18 +147,18 @@ public class BitmessageAddress implements Serializable {
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
// test checksum
- byte[] checksum = security().doubleSha512(bytes, bytes.length - 4);
+ byte[] checksum = cryptography().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 = cryptography().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 = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64);
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
}
@@ -173,7 +173,7 @@ public class BitmessageAddress implements Serializable {
Encode.varInt(version, out);
Encode.varInt(stream, out);
out.write(ripe);
- return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64);
+ return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64);
} catch (IOException e) {
throw new ApplicationException(e);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
index 347af6c..860c6ed 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
@@ -27,7 +27,7 @@ import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* A network message is exchanged between two nodes.
@@ -51,7 +51,7 @@ public class NetworkMessage implements Streamable {
* First 4 bytes of sha512(payload)
*/
private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
- byte[] d = security().sha512(bytes);
+ byte[] d = cryptography().sha512(bytes);
return new byte[]{d[0], d[1], d[2], d[3]};
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
index a0878c2..6f74257 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
@@ -32,7 +32,7 @@ import java.io.OutputStream;
import java.util.Arrays;
import java.util.Objects;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* The 'object' command sends an object that is shared throughout the network.
@@ -96,7 +96,7 @@ public class ObjectMessage implements MessagePayload {
public InventoryVector getInventoryVector() {
return new InventoryVector(
- Bytes.truncate(security().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
+ Bytes.truncate(cryptography().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
);
}
@@ -121,7 +121,7 @@ public class ObjectMessage implements MessagePayload {
public void sign(PrivateKey key) {
if (payload.isSigned()) {
- payload.setSignature(security().getSignature(getBytesToSign(), key));
+ payload.setSignature(cryptography().getSignature(getBytesToSign(), key));
}
}
@@ -155,7 +155,7 @@ public class ObjectMessage implements MessagePayload {
public boolean isSignatureValid(Pubkey pubkey) throws IOException {
if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
- return security().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
+ return cryptography().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
}
@Override
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index c0a33ef..a2b66f7 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -28,7 +28,7 @@ import ch.dissem.bitmessage.utils.UnixTime;
import java.io.*;
import java.util.*;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* The unencrypted message to be sent by 'msg' or 'broadcast'.
@@ -511,7 +511,7 @@ public class Plaintext implements Streamable {
to = new BitmessageAddress(0, 0, destinationRipe);
}
if (type == Type.MSG && ackMessage == null && ackData == null) {
- ackData = security().randomBytes(32);
+ ackData = cryptography().randomBytes(32);
}
return new Plaintext(this);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
index 1533a2d..a583330 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
@@ -26,7 +26,7 @@ import java.io.IOException;
import java.util.Objects;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
@@ -81,7 +81,7 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted, Plai
}
public void encrypt() throws IOException {
- encrypt(security().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
+ encrypt(cryptography().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
}
@Override
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
index 6609070..89d7bd7 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
@@ -27,7 +27,7 @@ import java.io.*;
import java.util.Arrays;
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
public class CryptoBox implements Streamable {
@@ -50,22 +50,22 @@ public class CryptoBox implements Streamable {
// 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 = cryptography().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 = cryptography().randomBytes(PRIVATE_KEY_SIZE);
+ R = cryptography().createPublicKey(r);
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
- byte[] P = security().multiply(K, r);
+ byte[] P = cryptography().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 = cryptography().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 = security().crypt(true, data, key_e, initializationVector);
+ encrypted = cryptography().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);
@@ -75,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 = cryptography().createPoint(builder.xComponent, builder.yComponent);
encrypted = builder.encrypted;
mac = builder.mac;
}
@@ -101,9 +101,9 @@ public class CryptoBox implements Streamable {
public InputStream decrypt(byte[] k) throws DecryptionFailedException {
// 1. The private key used to decrypt is called k.
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
- byte[] P = security().multiply(R, k);
+ byte[] P = cryptography().multiply(R, k);
// 3. Use the X component of public key P and calculate the SHA512 hash H.
- byte[] H = security().sha512(Arrays.copyOfRange(P, 1, 33));
+ byte[] H = cryptography().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);
@@ -116,14 +116,14 @@ 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(security().crypt(false, encrypted, key_e, initializationVector));
+ return new ByteArrayInputStream(cryptography().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 cryptography().mac(key_m, macData.toByteArray());
} catch (IOException e) {
throw new ApplicationException(e);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
index 789c64c..c476bf9 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
@@ -20,7 +20,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
@@ -35,7 +35,7 @@ public abstract class Pubkey extends ObjectPayload {
}
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
- return security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey));
+ return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey));
}
public abstract byte[] getSigningKey();
@@ -45,7 +45,7 @@ public abstract class Pubkey extends ObjectPayload {
public abstract int getBehaviorBitfield();
public byte[] getRipe() {
- return security().ripemd160(security().sha512(getSigningKey(), getEncryptionKey()));
+ return cryptography().ripemd160(cryptography().sha512(getSigningKey(), getEncryptionKey()));
}
public long getNonceTrialsPerByte() {
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
index b9c3afb..716afc6 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
@@ -30,7 +30,7 @@ import java.io.*;
import java.util.ArrayList;
import java.util.List;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
@@ -53,15 +53,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);
- pubEK = security().createPublicKey(privEK);
+ privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE);
+ privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE);
+ pubSK = cryptography().createPublicKey(privSK);
+ pubEK = cryptography().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 = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
nonceTrialsPerByte, extraBytes, features);
}
@@ -118,11 +118,11 @@ public class PrivateKey implements Streamable {
long encryptionKeyNonce = nextNonce + 1;
byte[] ripe;
do {
- privEK = Bytes.truncate(security().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32);
- privSK = Bytes.truncate(security().sha512(seed, Encode.varInt(signingKeyNonce)), 32);
- pubSK = security().createPublicKey(privSK);
- pubEK = security().createPublicKey(privEK);
- ripe = security().ripemd160(security().sha512(pubSK, pubEK));
+ privEK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32);
+ privSK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(signingKeyNonce)), 32);
+ pubSK = cryptography().createPublicKey(privSK);
+ pubEK = cryptography().createPublicKey(privEK);
+ ripe = cryptography().ripemd160(cryptography().sha512(pubSK, pubEK));
signingKeyNonce += 2;
encryptionKeyNonce += 2;
diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
index cfa3229..49db9d6 100644
--- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
@@ -32,7 +32,7 @@ import java.net.SocketException;
import java.net.SocketTimeoutException;
import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
@@ -117,8 +117,8 @@ public class Factory {
BitmessageAddress temp = new BitmessageAddress(address);
PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey,
createPubkey(temp.getVersion(), temp.getStream(),
- security().createPublicKey(privateSigningKey),
- security().createPublicKey(privateEncryptionKey),
+ cryptography().createPublicKey(privateSigningKey),
+ cryptography().createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, behaviourBitfield));
BitmessageAddress result = new BitmessageAddress(privateKey);
if (!result.getAddress().equals(address)) {
diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
index d13e73e..af9839f 100644
--- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
@@ -32,7 +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;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Creates protocol v3 network messages from {@link InputStream InputStreams}
@@ -183,7 +183,7 @@ class V3MessageFactory {
}
private static boolean testChecksum(byte[] checksum, byte[] payload) {
- byte[] payloadChecksum = security().sha512(payload);
+ byte[] payloadChecksum = cryptography().sha512(payload);
for (int i = 0; i < checksum.length; i++) {
if (checksum[i] != payloadChecksum[i]) {
return false;
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
index 9e949a7..c4cff5c 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
@@ -30,6 +30,8 @@ public interface MessageRepository {
int countUnread(Label label);
+ Plaintext getMessage(Object id);
+
Plaintext getMessage(byte[] initialHash);
List findMessages(Label label);
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
index 739c172..61dc2ab 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
@@ -1,6 +1,8 @@
package ch.dissem.bitmessage.ports;
+import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.entity.Plaintext;
import java.util.List;
@@ -16,6 +18,8 @@ public interface ProofOfWorkRepository {
void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
+ void putObject(Item item);
+
void removeObject(byte[] initialHash);
class Item {
@@ -23,10 +27,20 @@ public interface ProofOfWorkRepository {
public final long nonceTrialsPerByte;
public final long extraBytes;
+ // Needed for ACK POW calculation
+ public final Long expirationTime;
+ public final Plaintext message;
+
public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
+ this(object, nonceTrialsPerByte, extraBytes, 0, null);
+ }
+
+ public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes, long expirationTime, Plaintext message) {
this.object = object;
this.nonceTrialsPerByte = nonceTrialsPerByte;
this.extraBytes = extraBytes;
+ this.expirationTime = expirationTime;
+ this.message = message;
}
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java
index a751c65..2eeaa97 100644
--- a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java
@@ -30,7 +30,7 @@ public class Singleton {
}
}
- public static Cryptography security() {
+ public static Cryptography cryptography() {
return cryptography;
}
}
diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
index 91dd94e..6e49853 100644
--- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
@@ -24,19 +24,20 @@ import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.ports.*;
-import ch.dissem.bitmessage.utils.*;
+import ch.dissem.bitmessage.utils.MessageMatchers;
+import ch.dissem.bitmessage.utils.Singleton;
+import ch.dissem.bitmessage.utils.TTL;
+import ch.dissem.bitmessage.utils.TestUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
-import java.util.Collections;
-import java.util.stream.Collectors;
import static ch.dissem.bitmessage.entity.payload.ObjectType.*;
import static ch.dissem.bitmessage.utils.MessageMatchers.object;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
@@ -79,9 +80,14 @@ public class BitmessageContextTest {
return result;
}
+ @Override
+ public void putObject(Item item) {
+ items.put(new InventoryVector(cryptography().getInitialHash(item.object)), item);
+ }
+
@Override
public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
- items.put(new InventoryVector(security().getInitialHash(object)), new Item(object, nonceTrialsPerByte, extraBytes));
+ items.put(new InventoryVector(cryptography().getInitialHash(object)), new Item(object, nonceTrialsPerByte, extraBytes));
}
@Override
@@ -196,7 +202,7 @@ public class BitmessageContextTest {
public void ensureMessageIsSent() throws Exception {
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
"Subject", "Message");
- assertEquals(1, ctx.internals().getProofOfWorkRepository().getItems().size());
+ assertEquals(2, ctx.internals().getProofOfWorkRepository().getItems().size());
verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce())
.putObject(object(MSG), eq(1000L), eq(1000L));
verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Plaintext.Type.MSG));
diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java
index eacaab2..170cb93 100644
--- a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java
@@ -99,7 +99,7 @@ public class DefaultMessageListenerTest extends TestBase {
.payload(identity.getPubkey())
.build();
objectMessage.sign(identity.getPrivateKey());
- objectMessage.encrypt(Singleton.security().createPublicKey(identity.getPublicDecryptionKey()));
+ objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.getPublicDecryptionKey()));
listener.receive(objectMessage);
verify(addressRepo).save(any(BitmessageAddress.class));
diff --git a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
index be57e4a..0a6ee25 100644
--- a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
@@ -30,14 +30,14 @@ import org.junit.Test;
import java.io.IOException;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
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, cryptography().randomBytes(100));
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey());
diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java
index 5d10cdf..fced84f 100644
--- a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java
@@ -28,7 +28,7 @@ import java.util.Arrays;
import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.INCLUDE_DESTINATION;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static org.junit.Assert.*;
public class BitmessageAddressTest extends TestBase {
@@ -126,7 +126,7 @@ public class BitmessageAddressTest extends TestBase {
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)));
+ cryptography().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
assertEquals(address_string, address.getAddress());
}
@@ -136,7 +136,7 @@ public class BitmessageAddressTest extends TestBase {
byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU");
byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz");
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
- security().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
+ cryptography().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress());
}
@@ -151,7 +151,7 @@ public class BitmessageAddressTest extends TestBase {
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 = cryptography().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/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
index aa08475..26d8a35 100644
--- a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
@@ -30,7 +30,7 @@ import java.util.ArrayList;
import java.util.Collections;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static org.junit.Assert.*;
public class SerializationTest extends TestBase {
@@ -102,7 +102,7 @@ public class SerializationTest extends TestBase {
public void ensureNetworkMessageIsSerializedAndDeserializedCorrectly() throws Exception {
ArrayList ivs = new ArrayList<>(50000);
for (int i = 0; i < 50000; i++) {
- ivs.add(new InventoryVector(security().randomBytes(32)));
+ ivs.add(new InventoryVector(cryptography().randomBytes(32)));
}
Inv inv = new Inv.Builder().inventory(ivs).build();
diff --git a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
index 1ed4aac..c2efb1f 100644
--- a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java
@@ -21,7 +21,7 @@ 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 ch.dissem.bitmessage.utils.Singleton.cryptography;
import static org.junit.Assert.assertTrue;
public class ProofOfWorkEngineTest extends TestBase {
@@ -36,7 +36,7 @@ public class ProofOfWorkEngineTest extends TestBase {
}
private void testPOW(ProofOfWorkEngine engine) throws InterruptedException {
- byte[] initialHash = security().sha512(new byte[]{1, 3, 6, 4});
+ byte[] initialHash = cryptography().sha512(new byte[]{1, 3, 6, 4});
byte[] target = {0, 0, 0, -1, -1, -1, -1, -1};
final CallbackWaiter waiter1 = new CallbackWaiter<>();
@@ -49,10 +49,10 @@ public class ProofOfWorkEngineTest extends TestBase {
});
byte[] nonce = waiter1.waitForValue();
System.out.println("Calculating nonce took " + waiter1.getTime() + "ms");
- assertTrue(Bytes.lt(security().doubleSha512(nonce, initialHash), target, 8));
+ assertTrue(Bytes.lt(cryptography().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[] initialHash2 = cryptography().sha512(new byte[]{1, 3, 6, 5});
byte[] target2 = {0, 0, -1, -1, -1, -1, -1, -1};
final CallbackWaiter waiter2 = new CallbackWaiter<>();
@@ -65,7 +65,7 @@ public class ProofOfWorkEngineTest extends TestBase {
});
byte[] nonce2 = waiter2.waitForValue();
System.out.println("Calculating nonce took " + waiter2.getTime() + "ms");
- assertTrue(Bytes.lt(security().doubleSha512(nonce2, initialHash2), target2, 8));
+ assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8));
assertTrue("Second nonce must be quicker to find", waiter1.getTime() > waiter2.getTime());
}
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
index 0b5b142..7955e5b 100644
--- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
@@ -28,7 +28,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.*;
import static ch.dissem.bitmessage.utils.Decode.*;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* A {@link CustomMessage} implementation that contains signed and encrypted data.
@@ -80,7 +80,7 @@ public class CryptoCustomMessage extends CustomMessage {
}
data.write(out);
- Encode.varBytes(security().getSignature(out.toByteArray(), identity.getPrivateKey()), out);
+ Encode.varBytes(cryptography().getSignature(out.toByteArray(), identity.getPrivateKey()), out);
container = new CryptoBox(out.toByteArray(), publicKey);
}
@@ -138,7 +138,7 @@ public class CryptoCustomMessage extends CustomMessage {
}
public void checkSignature(Pubkey pubkey) throws IOException, IllegalStateException {
- if (!security().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) {
+ if (!cryptography().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) {
throw new IllegalStateException("Signature check failed");
}
}
diff --git a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
index d451323..bf0fa6d 100644
--- a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
+++ b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
@@ -30,7 +30,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static org.junit.Assert.assertEquals;
public class CryptoCustomMessageTest extends TestBase {
@@ -39,9 +39,9 @@ public class CryptoCustomMessageTest extends TestBase {
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey);
- GenericPayload payloadBefore = new GenericPayload(0, 1, security().randomBytes(100));
+ GenericPayload payloadBefore = new GenericPayload(0, 1, cryptography().randomBytes(100));
CryptoCustomMessage messageBefore = new CryptoCustomMessage<>(payloadBefore);
- messageBefore.signAndEncrypt(sendingIdentity, security().createPublicKey(sendingIdentity.getPublicDecryptionKey()));
+ messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.getPublicDecryptionKey()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
messageBefore.write(out);
@@ -65,11 +65,11 @@ public class CryptoCustomMessageTest extends TestBase {
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
final BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey);
- ProofOfWorkRequest requestBefore = new ProofOfWorkRequest(sendingIdentity, security().randomBytes(64),
+ ProofOfWorkRequest requestBefore = new ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64),
ProofOfWorkRequest.Request.CALCULATE);
CryptoCustomMessage messageBefore = new CryptoCustomMessage<>(requestBefore);
- messageBefore.signAndEncrypt(sendingIdentity, security().createPublicKey(sendingIdentity.getPublicDecryptionKey()));
+ messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.getPublicDecryptionKey()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
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 54c8acf..4717675 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -46,7 +46,7 @@ import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE
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.Singleton.cryptography;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
/**
@@ -249,7 +249,7 @@ class Connection {
}
try {
listener.receive(objectMessage);
- security().checkProofOfWork(objectMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES);
+ cryptography().checkProofOfWork(objectMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES);
ctx.getInventory().storeObject(objectMessage);
// offer object to some random nodes so it gets distributed throughout the network:
networkHandler.offer(objectMessage.getInventoryVector());
diff --git a/repositories/build.gradle b/repositories/build.gradle
index abb8651..2ab092c 100644
--- a/repositories/build.gradle
+++ b/repositories/build.gradle
@@ -18,5 +18,6 @@ dependencies {
testCompile 'junit:junit:4.12'
testCompile 'com.h2database:h2:1.4.190'
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/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
index 8f94d03..ae1b496 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
@@ -117,6 +117,24 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
return 0;
}
+ @Override
+ public Plaintext getMessage(Object id) {
+ if (id instanceof Long) {
+ List plaintexts = find("id=" + id);
+ switch (plaintexts.size()) {
+ case 0:
+ return null;
+ case 1:
+ return plaintexts.get(0);
+ default:
+ throw new ApplicationException("This shouldn't happen, found " + plaintexts.size() +
+ " messages, one or none was expected");
+ }
+ } else {
+ throw new IllegalArgumentException("Long expected for ID");
+ }
+ }
+
@Override
public Plaintext getMessage(byte[] initialHash) {
List plaintexts = find("initial_hash=X'" + Strings.hex(initialHash) + "'");
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java
index 0fbb2dc..ac6e69f 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java
@@ -1,6 +1,8 @@
package ch.dissem.bitmessage.repository;
+import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
@@ -8,18 +10,21 @@ import ch.dissem.bitmessage.utils.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* @author Christian Basler
*/
-public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWorkRepository {
+public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWorkRepository, InternalContext.ContextHolder {
private static final Logger LOG = LoggerFactory.getLogger(JdbcProofOfWorkRepository.class);
+ private InternalContext ctx;
public JdbcProofOfWorkRepository(JdbcConfig config) {
super(config);
@@ -30,17 +35,27 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork
try (
Connection connection = config.getConnection();
PreparedStatement ps = connection.prepareStatement("SELECT data, version, nonce_trials_per_byte, " +
- "extra_bytes FROM POW WHERE initial_hash=?")
+ "extra_bytes, expiration_time, message_id FROM POW WHERE initial_hash=?")
) {
ps.setBytes(1, initialHash);
try (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")
- );
+ if (rs.getObject("message_id") == null) {
+ return new Item(
+ Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()),
+ rs.getLong("nonce_trials_per_byte"),
+ rs.getLong("extra_bytes")
+ );
+ } else {
+ return new Item(
+ Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()),
+ rs.getLong("nonce_trials_per_byte"),
+ rs.getLong("extra_bytes"),
+ rs.getLong("expiration_time"),
+ ctx.getMessageRepository().getMessage(rs.getLong("message_id"))
+ );
+ }
} else {
throw new IllegalArgumentException("Object requested that we don't have. Initial hash: " + Strings.hex(initialHash));
}
@@ -70,24 +85,38 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork
}
@Override
- public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
+ public void putObject(Item item) {
try (
Connection connection = config.getConnection();
PreparedStatement ps = connection.prepareStatement("INSERT INTO POW (initial_hash, data, version, " +
- "nonce_trials_per_byte, extra_bytes) VALUES (?, ?, ?, ?, ?)")
+ "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " +
+ "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.setBytes(1, cryptography().getInitialHash(item.object));
+ writeBlob(ps, 2, item.object);
+ ps.setLong(3, item.object.getVersion());
+ ps.setLong(4, item.nonceTrialsPerByte);
+ ps.setLong(5, item.extraBytes);
+
+ if (item.message == null) {
+ ps.setObject(6, null);
+ ps.setObject(7, null);
+ } else {
+ ps.setLong(6, item.expirationTime);
+ ps.setLong(7, (Long) item.message.getId());
+ }
ps.executeUpdate();
} catch (IOException | SQLException e) {
- LOG.debug("Error storing object of type " + object.getPayload().getClass().getSimpleName(), e);
+ LOG.debug("Error storing object of type " + item.object.getPayload().getClass().getSimpleName(), e);
throw new ApplicationException(e);
}
}
+ @Override
+ public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
+ putObject(new Item(object, nonceTrialsPerByte, extraBytes));
+ }
+
@Override
public void removeObject(byte[] initialHash) {
try (
@@ -100,4 +129,9 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork
LOG.debug(e.getMessage(), e);
}
}
+
+ @Override
+ public void setContext(InternalContext context) {
+ this.ctx = context;
+ }
}
diff --git a/repositories/src/main/resources/db/migration/V3.1__Update_table_POW.sql b/repositories/src/main/resources/db/migration/V3.1__Update_table_POW.sql
new file mode 100644
index 0000000..d67a1b5
--- /dev/null
+++ b/repositories/src/main/resources/db/migration/V3.1__Update_table_POW.sql
@@ -0,0 +1,2 @@
+ALTER TABLE POW ADD COLUMN expiration_time BIGINT;
+ALTER TABLE POW ADD COLUMN message_id BIGINT;
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 1975d43..7f3098b 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java
@@ -68,9 +68,11 @@ public class JdbcAddressRepositoryTest extends TestBase {
public void testFindIdentity() throws Exception {
BitmessageAddress identity = new BitmessageAddress(IDENTITY_A);
assertEquals(4, identity.getVersion());
- assertEquals(identity, repo.findIdentity(identity.getTag()));
assertNull(repo.findContact(identity.getTag()));
- assertTrue(identity.has(Pubkey.Feature.DOES_ACK));
+
+ BitmessageAddress storedIdentity = repo.findIdentity(identity.getTag());
+ assertEquals(identity, storedIdentity);
+ assertTrue(storedIdentity.has(Pubkey.Feature.DOES_ACK));
}
@Test
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 816eafa..81051de 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
@@ -32,7 +32,7 @@ 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 ch.dissem.bitmessage.utils.Singleton.cryptography;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
@@ -54,7 +54,7 @@ public class JdbcMessageRepositoryTest extends TestBase {
AddressRepository addressRepo = new JdbcAddressRepository(config);
repo = new JdbcMessageRepository(config);
new InternalContext(new BitmessageContext.Builder()
- .cryptography(security())
+ .cryptography(cryptography())
.addressRepo(addressRepo)
.messageRepo(repo)
);
@@ -146,7 +146,7 @@ public class JdbcMessageRepositoryTest extends TestBase {
@Test
public void testSave() throws Exception {
Plaintext message = new Plaintext.Builder(MSG)
- .IV(new InventoryVector(security().randomBytes(32)))
+ .IV(new InventoryVector(cryptography().randomBytes(32)))
.from(identity)
.to(contactA)
.message("Subject", "Message")
@@ -169,7 +169,7 @@ public class JdbcMessageRepositoryTest extends TestBase {
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(cryptography().randomBytes(32)));
repo.save(message);
messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java
index c8fbaf0..4396eb5 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java
@@ -16,13 +16,24 @@
package ch.dissem.bitmessage.repository;
+import ch.dissem.bitmessage.BitmessageContext;
+import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.payload.GetPubkey;
-import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
+import ch.dissem.bitmessage.ports.AddressRepository;
+import ch.dissem.bitmessage.ports.MessageRepository;
+import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item;
+import ch.dissem.bitmessage.utils.TestUtils;
+import ch.dissem.bitmessage.utils.UnixTime;
import org.junit.Before;
import org.junit.Test;
+import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
+import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -33,17 +44,51 @@ import static org.junit.Assert.assertTrue;
public class JdbcProofOfWorkRepositoryTest extends TestBase {
private TestJdbcConfig config;
private JdbcProofOfWorkRepository repo;
+ private AddressRepository addressRepo;
+ private MessageRepository messageRepo;
+
+ private byte[] initialHash1;
+ private byte[] initialHash2;
@Before
public void setUp() throws Exception {
config = new TestJdbcConfig();
config.reset();
+ addressRepo = new JdbcAddressRepository(config);
+ messageRepo = new JdbcMessageRepository(config);
repo = new JdbcProofOfWorkRepository(config);
+ InternalContext ctx = new InternalContext(new BitmessageContext.Builder()
+ .addressRepo(addressRepo)
+ .messageRepo(messageRepo)
+ .powRepo(repo)
+ .cryptography(cryptography())
+ );
repo.putObject(new ObjectMessage.Builder()
.payload(new GetPubkey(new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"))).build(),
1000, 1000);
+ initialHash1 = repo.getItems().get(0);
+
+ BitmessageAddress sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8");
+ BitmessageAddress recipient = TestUtils.loadContact();
+ addressRepo.save(sender);
+ addressRepo.save(recipient);
+ Plaintext plaintext = new Plaintext.Builder(MSG)
+ .ackData(cryptography().randomBytes(32))
+ .from(sender)
+ .to(recipient)
+ .message("Subject", "Message")
+ .status(Plaintext.Status.DOING_PROOF_OF_WORK)
+ .build();
+ messageRepo.save(plaintext);
+ initialHash2 = cryptography().getInitialHash(plaintext.getAckMessage());
+ repo.putObject(new Item(
+ plaintext.getAckMessage(),
+ 1000, 1000,
+ UnixTime.now(+10 * MINUTE),
+ plaintext
+ ));
}
@Test
@@ -55,16 +100,52 @@ public class JdbcProofOfWorkRepositoryTest extends TestBase {
assertThat(repo.getItems().size(), is(sizeBefore + 1));
}
+ @Test
+ public void ensureAckObjectsAreStored() throws Exception {
+ int sizeBefore = repo.getItems().size();
+ BitmessageAddress sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8");
+ BitmessageAddress recipient = TestUtils.loadContact();
+ addressRepo.save(sender);
+ addressRepo.save(recipient);
+ Plaintext plaintext = new Plaintext.Builder(MSG)
+ .ackData(cryptography().randomBytes(32))
+ .from(sender)
+ .to(recipient)
+ .message("Subject", "Message")
+ .status(Plaintext.Status.DOING_PROOF_OF_WORK)
+ .build();
+ messageRepo.save(plaintext);
+ repo.putObject(new Item(
+ plaintext.getAckMessage(),
+ 1000, 1000,
+ UnixTime.now(+10 * MINUTE),
+ plaintext
+ ));
+ assertThat(repo.getItems().size(), is(sizeBefore + 1));
+ }
+
@Test
public void ensureItemCanBeRetrieved() {
- byte[] initialHash = repo.getItems().get(0);
- ProofOfWorkRepository.Item item = repo.getItem(initialHash);
+ Item item = repo.getItem(initialHash1);
assertThat(item, notNullValue());
assertThat(item.object.getPayload(), instanceOf(GetPubkey.class));
assertThat(item.nonceTrialsPerByte, is(1000L));
assertThat(item.extraBytes, is(1000L));
}
+ @Test
+ public void ensureAckItemCanBeRetrieved() {
+ Item item = repo.getItem(initialHash2);
+ assertThat(item, notNullValue());
+ assertThat(item.object.getPayload(), instanceOf(GenericPayload.class));
+ assertThat(item.nonceTrialsPerByte, is(1000L));
+ assertThat(item.extraBytes, is(1000L));
+ assertThat(item.expirationTime, not(0));
+ assertThat(item.message, notNullValue());
+ assertThat(item.message.getFrom().getPrivateKey(), notNullValue());
+ assertThat(item.message.getTo().getPubkey(), notNullValue());
+ }
+
@Test(expected = RuntimeException.class)
public void ensureRetrievingNonexistingItemThrowsException() {
repo.getItem(new byte[0]);
@@ -72,8 +153,8 @@ public class JdbcProofOfWorkRepositoryTest extends TestBase {
@Test
public void ensureItemCanBeDeleted() {
- byte[] initialHash = repo.getItems().get(0);
- repo.removeObject(initialHash);
+ repo.removeObject(initialHash1);
+ repo.removeObject(initialHash2);
assertTrue(repo.getItems().isEmpty());
}
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 ad6098e..3f4b370 100644
--- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java
+++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java
@@ -27,7 +27,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;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* @author Christian Basler
@@ -77,7 +77,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 = cryptography().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 1bd6bcd..f5a8ddc 100644
--- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java
+++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java
@@ -31,7 +31,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
-import static ch.dissem.bitmessage.utils.Singleton.security;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* @author Christian Basler
@@ -87,7 +87,7 @@ public class WifImporter {
throw new IOException("Unknown format: " + WIF_SECRET_LENGTH +
" bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
- byte[] hash = security().doubleSha256(bytes, 33);
+ byte[] hash = cryptography().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);
}
From 4f0b2cb8f86cfa85495de1099a1051900ba554af Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 6 May 2016 14:13:39 +0200
Subject: [PATCH 37/87] Fixed system test (this time for real)
---
.../dissem/bitmessage/BitmessageContext.java | 3 +-
.../bitmessage/DefaultMessageListener.java | 3 +-
.../dissem/bitmessage/ProofOfWorkService.java | 2 -
.../bitmessage/ports/DefaultLabeler.java | 2 +-
.../ports/ProofOfWorkRepository.java | 1 -
.../java/ch/dissem/bitmessage/SystemTest.java | 96 ++++++++++++++++++-
6 files changed, 97 insertions(+), 10 deletions(-)
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index d46c36c..23e8eff 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -153,7 +153,6 @@ public class BitmessageContext {
.to(to)
.message(subject, message)
.build();
- labeler().markAsSending(msg);
send(msg);
}
@@ -161,7 +160,7 @@ public class BitmessageContext {
if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) {
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
}
- msg.setStatus(Plaintext.Status.DRAFT);
+ labeler().markAsSending(msg);
BitmessageAddress to = msg.getTo();
if (to != null) {
if (to.getPubkey() == null) {
diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index 14c405c..eb27dd9 100644
--- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -24,6 +24,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.ports.Labeler;
import ch.dissem.bitmessage.ports.NetworkHandler;
+import ch.dissem.bitmessage.utils.TTL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -111,7 +112,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
for (Plaintext msg : messages) {
ctx.getLabeler().markAsSending(msg);
ctx.getMessageRepository().save(msg);
- ctx.send(msg, +2 * DAY);
+ ctx.send(msg, TTL.msg());
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
index 6df0cc0..e886c22 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -1,9 +1,7 @@
package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.*;
-import ch.dissem.bitmessage.entity.payload.Broadcast;
import ch.dissem.bitmessage.entity.payload.Msg;
-import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.ports.Cryptography;
import ch.dissem.bitmessage.ports.MessageRepository;
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java
index 4f21b3e..e4c2105 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java
@@ -43,7 +43,7 @@ public class DefaultLabeler implements Labeler, InternalContext.ContextHolder {
@Override
public void markAsSending(Plaintext msg) {
- if (msg.getTo() != null || msg.getTo().getPubkey() == null) {
+ if (msg.getTo() != null && msg.getTo().getPubkey() == null) {
msg.setStatus(PUBKEY_REQUESTED);
} else {
msg.setStatus(DOING_PROOF_OF_WORK);
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
index 61dc2ab..b27a05d 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
@@ -1,6 +1,5 @@
package ch.dissem.bitmessage.ports;
-import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java
index 06b6628..6751819 100644
--- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java
+++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java
@@ -4,13 +4,19 @@ import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
+import ch.dissem.bitmessage.ports.DefaultLabeler;
import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.utils.TTL;
-import org.junit.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
+import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
@@ -47,9 +53,10 @@ public class SystemTest {
.networkHandler(new DefaultNetworkHandler())
.cryptography(new BouncyCryptography())
.listener(aliceListener)
+ .labeler(new DebugLabeler("Alice"))
.build();
alice.startup();
- aliceIdentity = alice.createIdentity(false);
+ aliceIdentity = alice.createIdentity(false, DOES_ACK);
JdbcConfig bobDB = new JdbcConfig("jdbc:h2:mem:bob;DB_CLOSE_DELAY=-1", "sa", "");
bob = new BitmessageContext.Builder()
@@ -62,9 +69,13 @@ public class SystemTest {
.networkHandler(new DefaultNetworkHandler())
.cryptography(new BouncyCryptography())
.listener(bobListener)
+ .labeler(new DebugLabeler("Bob"))
.build();
bob.startup();
- bobIdentity = bob.createIdentity(false);
+ bobIdentity = bob.createIdentity(false, DOES_ACK);
+
+ ((DebugLabeler) alice.labeler()).init(aliceIdentity, bobIdentity);
+ ((DebugLabeler) bob.labeler()).init(aliceIdentity, bobIdentity);
}
@After
@@ -95,4 +106,83 @@ public class SystemTest {
assertThat(plaintext.getType(), equalTo(Plaintext.Type.BROADCAST));
assertThat(plaintext.getText(), equalTo(originalMessage));
}
+
+ private static class DebugLabeler extends DefaultLabeler {
+ private final Logger LOG = LoggerFactory.getLogger("Labeler");
+ final String name;
+ String alice;
+ String bob;
+
+ private DebugLabeler(String name) {
+ this.name = name;
+ }
+
+ private void init(BitmessageAddress alice, BitmessageAddress bob) {
+ this.alice = alice.getAddress();
+ this.bob = bob.getAddress();
+ }
+
+ @Override
+ public void setLabels(Plaintext msg) {
+ LOG.info(name + ": From " + name(msg.getFrom()) + ": Received");
+ super.setLabels(msg);
+ }
+
+ @Override
+ public void markAsDraft(Plaintext msg) {
+ LOG.info(name + ": From " + name(msg.getFrom()) + ": Draft");
+ super.markAsDraft(msg);
+ }
+
+ @Override
+ public void markAsSending(Plaintext msg) {
+ LOG.info(name + ": From " + name(msg.getFrom()) + ": Sending");
+ super.markAsSending(msg);
+ }
+
+ @Override
+ public void markAsSent(Plaintext msg) {
+ LOG.info(name + ": From " + name(msg.getFrom()) + ": Sent");
+ super.markAsSent(msg);
+ }
+
+ @Override
+ public void markAsAcknowledged(Plaintext msg) {
+ LOG.info(name + ": From " + name(msg.getFrom()) + ": Acknowledged");
+ super.markAsAcknowledged(msg);
+ }
+
+ @Override
+ public void markAsRead(Plaintext msg) {
+ LOG.info(name + ": From " + name(msg.getFrom()) + ": Read");
+ super.markAsRead(msg);
+ }
+
+ @Override
+ public void markAsUnread(Plaintext msg) {
+ LOG.info(name + ": From " + name(msg.getFrom()) + ": Unread");
+ super.markAsUnread(msg);
+ }
+
+ @Override
+ public void delete(Plaintext msg) {
+ LOG.info(name + ": From " + name(msg.getFrom()) + ": Cleared");
+ super.delete(msg);
+ }
+
+ @Override
+ public void archive(Plaintext msg) {
+ LOG.info(name + ": From " + name(msg.getFrom()) + ": Archived");
+ super.archive(msg);
+ }
+
+ private String name(BitmessageAddress address) {
+ if (alice.equals(address.getAddress()))
+ return "Alice";
+ else if (bob.equals(address.getAddress()))
+ return "Bob";
+ else
+ return "Unknown (" + address.getAddress() + ")";
+ }
+ }
}
From de8f04e22acbe5a1ffe77d099c9040e89f15ebdc Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 6 May 2016 17:29:39 +0200
Subject: [PATCH 38/87] Added warning to Labeler for developers who want to
implement it.
---
core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java b/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java
index 5e52845..e79bee2 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java
@@ -20,6 +20,8 @@ import ch.dissem.bitmessage.entity.Plaintext;
/**
* Defines and sets labels. Note that it should also update the status field of a message.
+ * Generally it's highly advised to override the {@link DefaultLabeler} whenever possible,
+ * instead of directly implementing the interface.
*
* As the labeler gets called whenever the state of a message changes, it can also be used
* as a listener.
@@ -35,6 +37,10 @@ public interface Labeler {
void markAsDraft(Plaintext msg);
+ /**
+ * It is paramount that this methods marks the {@link Plaintext} object with status
+ * {@link Plaintext.Status#PUBKEY_REQUESTED} (see {@link DefaultLabeler})
+ */
void markAsSending(Plaintext msg);
void markAsSent(Plaintext msg);
From 05d9ea93d266216933f751b0c8b0f83a076c2599 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 6 May 2016 19:39:39 +0200
Subject: [PATCH 39/87] Acknowledgments are now returned, received, and the
message (Plaintext object) updated -> no logic to resend messages yet
---
.../dissem/bitmessage/BitmessageContext.java | 12 ++-----
.../bitmessage/DefaultMessageListener.java | 17 ++++++++--
.../dissem/bitmessage/ProofOfWorkService.java | 23 +++++++++----
.../dissem/bitmessage/entity/Plaintext.java | 3 +-
.../entity/payload/GenericPayload.java | 4 +++
.../dissem/bitmessage/entity/payload/Msg.java | 1 +
.../ch/dissem/bitmessage/factory/Factory.java | 4 ++-
.../bitmessage/ports/MessageRepository.java | 2 ++
.../java/ch/dissem/bitmessage/SystemTest.java | 9 +++++-
.../bitmessage/repository/JdbcHelper.java | 10 ++++++
.../repository/JdbcMessageRepository.java | 32 +++++++++----------
.../migration/V3.2__Update_table_message.sql | 1 +
12 files changed, 79 insertions(+), 39 deletions(-)
create mode 100644 repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 23e8eff..2307958 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -31,8 +31,6 @@ import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -71,16 +69,10 @@ public class BitmessageContext {
private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder);
labeler = builder.labeler;
+ ctx.getProofOfWorkService().doMissingProofOfWork();
+
networkListener = new DefaultMessageListener(ctx, labeler, builder.listener);
-
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
-
- new Timer().schedule(new TimerTask() {
- @Override
- public void run() {
- ctx.getProofOfWorkService().doMissingProofOfWork();
- }
- }, 30_000); // After 30 seconds
}
public AddressRepository addresses() {
diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index eb27dd9..05961e4 100644
--- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -48,9 +48,12 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
@Override
+ @SuppressWarnings("ConstantConditions")
public void receive(ObjectMessage object) throws IOException {
ObjectPayload payload = object.getPayload();
- if (payload.getType() == null) return;
+ if (payload.getType() == null && payload instanceof GenericPayload) {
+ receive((GenericPayload) payload);
+ }
switch (payload.getType()) {
case GET_PUBKEY: {
@@ -125,7 +128,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
if (!object.isSignatureValid(plaintext.getFrom().getPubkey())) {
LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
} else {
- receive(object.getInventoryVector(), msg.getPlaintext());
+ receive(object.getInventoryVector(), plaintext);
}
break;
} catch (DecryptionFailedException ignore) {
@@ -133,6 +136,16 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
}
+ protected void receive(GenericPayload ack) {
+ if (ack.getData().length == Msg.ACK_LENGTH) {
+ Plaintext msg = ctx.getMessageRepository().getMessageForAck(ack.getData());
+ if (msg != null) {
+ ctx.getLabeler().markAsAcknowledged(msg);
+ ctx.getMessageRepository().save(msg);
+ }
+ }
+ }
+
protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException {
byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null;
for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) {
diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
index e886c22..d242b79 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -12,6 +12,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
@@ -29,14 +31,21 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
private MessageRepository messageRepo;
public void doMissingProofOfWork() {
- List items = powRepo.getItems();
+ final List items = powRepo.getItems();
if (items.isEmpty()) return;
- LOG.info("Doing POW for " + items.size() + " tasks.");
- for (byte[] initialHash : items) {
- Item item = powRepo.getItem(initialHash);
- cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this);
- }
+ // Wait for 30 seconds, to let the application start up before putting heavy load on the CPU
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ LOG.info("Doing POW for " + items.size() + " tasks.");
+ for (byte[] initialHash : items) {
+ Item item = powRepo.getItem(initialHash);
+ cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes,
+ ProofOfWorkService.this);
+ }
+ }
+ }, 30_000);
}
public void doProofOfWork(ObjectMessage object) {
@@ -79,7 +88,6 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
messageRepo.save(plaintext);
}
ctx.getInventory().storeObject(object);
- powRepo.removeObject(initialHash);
ctx.getNetworkHandler().offer(object.getInventoryVector());
} else {
item.message.getAckMessage().setNonce(nonce);
@@ -96,6 +104,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
}
doProofOfWork(item.message.getTo(), object);
}
+ powRepo.removeObject(initialHash);
}
@Override
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index a2b66f7..b90bf14 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -16,6 +16,7 @@
package ch.dissem.bitmessage.entity;
+import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
@@ -511,7 +512,7 @@ public class Plaintext implements Streamable {
to = new BitmessageAddress(0, 0, destinationRipe);
}
if (type == Type.MSG && ackMessage == null && ackData == null) {
- ackData = cryptography().randomBytes(32);
+ ackData = cryptography().randomBytes(Msg.ACK_LENGTH);
}
return new Plaintext(this);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
index 4ae4696..176d938 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
@@ -53,6 +53,10 @@ public class GenericPayload extends ObjectPayload {
return stream;
}
+ public byte[] getData() {
+ return data;
+ }
+
@Override
public void write(OutputStream stream) throws IOException {
stream.write(data);
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
index edc4643..64a010c 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
@@ -33,6 +33,7 @@ import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
*/
public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
private static final long serialVersionUID = 4327495048296365733L;
+ public static final int ACK_LENGTH = 32;
private long stream;
private CryptoBox encrypted;
diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
index 49db9d6..53e1676 100644
--- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
@@ -23,6 +23,8 @@ 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.TTL;
+import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -210,6 +212,6 @@ public class Factory {
if (plaintext == null || plaintext.getAckData() == null)
return null;
GenericPayload ack = new GenericPayload(3, plaintext.getFrom().getStream(), plaintext.getAckData());
- return new ObjectMessage.Builder().objectType(MSG).payload(ack).build();
+ return new ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now(+TTL.msg())).build();
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
index c4cff5c..0c46499 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
@@ -34,6 +34,8 @@ public interface MessageRepository {
Plaintext getMessage(byte[] initialHash);
+ Plaintext getMessageForAck(byte[] ackData);
+
List findMessages(Label label);
List findMessages(Status status);
diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java
index 6751819..03cbf5e 100644
--- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java
+++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java
@@ -5,11 +5,13 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.DefaultLabeler;
+import ch.dissem.bitmessage.ports.Labeler;
import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.utils.TTL;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,6 +22,7 @@ import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
/**
* @author Christian Basler
@@ -29,6 +32,7 @@ public class SystemTest {
private BitmessageContext alice;
private TestListener aliceListener = new TestListener();
+ private Labeler aliceLabeler = Mockito.spy(new DebugLabeler("Alice"));
private BitmessageAddress aliceIdentity;
private BitmessageContext bob;
@@ -53,7 +57,7 @@ public class SystemTest {
.networkHandler(new DefaultNetworkHandler())
.cryptography(new BouncyCryptography())
.listener(aliceListener)
- .labeler(new DebugLabeler("Alice"))
+ .labeler(aliceLabeler)
.build();
alice.startup();
aliceIdentity = alice.createIdentity(false, DOES_ACK);
@@ -93,6 +97,9 @@ public class SystemTest {
assertThat(plaintext.getType(), equalTo(Plaintext.Type.MSG));
assertThat(plaintext.getText(), equalTo(originalMessage));
+
+ Mockito.verify(aliceLabeler, Mockito.timeout(TimeUnit.MINUTES.toMillis(15)).atLeastOnce())
+ .markAsAcknowledged(any());
}
@Test
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 1df12f6..beb4c80 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
@@ -18,6 +18,7 @@ package ch.dissem.bitmessage.repository;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.payload.ObjectType;
+import ch.dissem.bitmessage.exception.ApplicationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,6 +26,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
+import java.util.Collection;
import static ch.dissem.bitmessage.utils.Strings.hex;
@@ -85,4 +87,12 @@ public abstract class JdbcHelper {
ps.setBytes(parameterIndex, os.toByteArray());
}
}
+
+ protected T single(Collection collection) {
+ if (collection.size() > 1) {
+ throw new ApplicationException("This shouldn't happen, found " + collection.size() +
+ " messages, one or none was expected");
+ }
+ return collection.stream().findAny().orElse(null);
+ }
}
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 ae1b496..ffc639f 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
@@ -137,16 +137,12 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
@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 ApplicationException("This shouldn't happen, found " + plaintexts.size() +
- " messages, one or none was expected");
- }
+ return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'"));
+ }
+
+ @Override
+ public Plaintext getMessageForAck(byte[] ackData) {
+ return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'"));
}
@Override
@@ -174,7 +170,7 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
try (
Connection connection = config.getConnection();
Statement stmt = connection.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT id, iv, type, sender, recipient, data, sent, received, status " +
+ ResultSet rs = stmt.executeQuery("SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, status " +
"FROM Message WHERE " + where)
) {
while (rs.next()) {
@@ -187,6 +183,7 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
builder.IV(new InventoryVector(iv));
builder.from(ctx.getAddressRepository().getAddress(rs.getString("sender")));
builder.to(ctx.getAddressRepository().getAddress(rs.getString("recipient")));
+ builder.ackData(rs.getBytes("ack_data"));
builder.sent(rs.getLong("sent"));
builder.received(rs.getLong("received"));
builder.status(Plaintext.Status.valueOf(rs.getString("status")));
@@ -268,8 +265,8 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private void insert(Connection connection, Plaintext message) throws SQLException, IOException {
try (PreparedStatement ps = connection.prepareStatement(
- "INSERT INTO Message (iv, type, sender, recipient, data, sent, received, status, initial_hash) " +
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ "INSERT INTO Message (iv, type, sender, recipient, data, ack_data, sent, received, status, initial_hash) " +
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS)
) {
ps.setBytes(1, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
@@ -277,10 +274,11 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
ps.setString(3, message.getFrom().getAddress());
ps.setString(4, message.getTo() == null ? null : message.getTo().getAddress());
writeBlob(ps, 5, message);
- ps.setLong(6, message.getSent());
- ps.setLong(7, message.getReceived());
- ps.setString(8, message.getStatus() == null ? null : message.getStatus().name());
- ps.setBytes(9, message.getInitialHash());
+ ps.setBytes(6, message.getAckData());
+ ps.setLong(7, message.getSent());
+ ps.setLong(8, message.getReceived());
+ ps.setString(9, message.getStatus() == null ? null : message.getStatus().name());
+ ps.setBytes(10, message.getInitialHash());
ps.executeUpdate();
// get generated id
diff --git a/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql b/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql
new file mode 100644
index 0000000..8b43b0a
--- /dev/null
+++ b/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql
@@ -0,0 +1 @@
+ALTER TABLE Message ADD COLUMN ack_data BINARY(32);
From a67ac27921fa89545109d61e42e42a27af7ea62b Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Tue, 10 May 2016 07:26:25 +0200
Subject: [PATCH 40/87] Fixed yet another test
---
.../src/main/java/ch/dissem/bitmessage/BitmessageContext.java | 2 +-
.../main/java/ch/dissem/bitmessage/ProofOfWorkService.java | 4 ++--
.../java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 2307958..aef6a67 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -69,7 +69,7 @@ public class BitmessageContext {
private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder);
labeler = builder.labeler;
- ctx.getProofOfWorkService().doMissingProofOfWork();
+ ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable
networkListener = new DefaultMessageListener(ctx, labeler, builder.listener);
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
index d242b79..04016b5 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -30,7 +30,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
private ProofOfWorkRepository powRepo;
private MessageRepository messageRepo;
- public void doMissingProofOfWork() {
+ public void doMissingProofOfWork(long delayInMilliseconds) {
final List items = powRepo.getItems();
if (items.isEmpty()) return;
@@ -45,7 +45,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
ProofOfWorkService.this);
}
}
- }, 30_000);
+ }, delayInMilliseconds);
}
public void doProofOfWork(ObjectMessage object) {
diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java
index fa58cfe..c4529e3 100644
--- a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java
@@ -77,9 +77,9 @@ public class ProofOfWorkServiceTest {
when(proofOfWorkRepo.getItem(any(byte[].class))).thenReturn(new ProofOfWorkRepository.Item(null, 1001, 1002));
doNothing().when(cryptography).doProofOfWork(any(ObjectMessage.class), anyLong(), anyLong(), any(ProofOfWorkEngine.Callback.class));
- proofOfWorkService.doMissingProofOfWork();
+ proofOfWorkService.doMissingProofOfWork(10);
- verify(cryptography).doProofOfWork((ObjectMessage) isNull(), eq(1001L), eq(1002L),
+ verify(cryptography, timeout(1000)).doProofOfWork((ObjectMessage) isNull(), eq(1001L), eq(1002L),
any(ProofOfWorkEngine.Callback.class));
}
From e44dd967d0230f3509b8c8ee0e65e210bc6b2246 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 13 May 2016 12:24:16 +0200
Subject: [PATCH 41/87] Test for NodeRegistry
---
core/build.gradle | 1 +
.../bitmessage/ports/MemoryNodeRegistry.java | 14 +--
.../bitmessage/ports/NodeRegistryTest.java | 95 +++++++++++++++++++
3 files changed, 101 insertions(+), 9 deletions(-)
create mode 100644 core/src/test/java/ch/dissem/bitmessage/ports/NodeRegistryTest.java
diff --git a/core/build.gradle b/core/build.gradle
index 8754cb3..dd100e6 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -26,6 +26,7 @@ artifacts {
dependencies {
compile 'org.slf4j:slf4j-api:1.7.12'
testCompile 'junit:junit:4.11'
+ testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile project(':cryptography-bc')
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java b/core/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
index c68a474..ff2ad95 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/MemoryNodeRegistry.java
@@ -46,15 +46,11 @@ public class MemoryNodeRegistry implements NodeRegistry {
while (scanner.hasNext()) {
try {
String line = scanner.nextLine().trim();
- if (line.startsWith("#") || line.isEmpty()) {
- // Ignore
- continue;
- }
if (line.startsWith("[stream")) {
stream = Long.parseLong(line.substring(8, line.lastIndexOf(']')));
streamSet = new HashSet<>();
stableNodes.put(stream, streamSet);
- } else if (streamSet != null) {
+ } else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) {
int portIndex = line.lastIndexOf(':');
InetAddress[] inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex));
int port = Integer.valueOf(line.substring(portIndex + 1));
@@ -89,12 +85,12 @@ public class MemoryNodeRegistry implements NodeRegistry {
known.remove(node);
}
}
- } else {
- Set nodes = stableNodes.get(stream);
- if (nodes == null || nodes.isEmpty()) {
+ }
+ if (result.isEmpty()) {
+ if (stableNodes.isEmpty()) {
loadStableNodes();
- nodes = stableNodes.get(stream);
}
+ Set nodes = stableNodes.get(stream);
if (nodes != null && !nodes.isEmpty()) {
// To reduce load on stable nodes, only return one
result.add(selectRandom(nodes));
diff --git a/core/src/test/java/ch/dissem/bitmessage/ports/NodeRegistryTest.java b/core/src/test/java/ch/dissem/bitmessage/ports/NodeRegistryTest.java
new file mode 100644
index 0000000..0e54446
--- /dev/null
+++ b/core/src/test/java/ch/dissem/bitmessage/ports/NodeRegistryTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2016 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.valueobject.NetworkAddress;
+import ch.dissem.bitmessage.utils.UnixTime;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static ch.dissem.bitmessage.utils.UnixTime.HOUR;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+public class NodeRegistryTest {
+ private NodeRegistry registry = new MemoryNodeRegistry();
+
+ @Test
+ public void ensureGetKnownNodesWithoutStreamsYieldsEmpty() {
+ assertThat(registry.getKnownAddresses(10), empty());
+ }
+
+ @Test
+ public void ensureGetKnownNodesForStream1YieldsResult() {
+ assertThat(registry.getKnownAddresses(10, 1), hasSize(1));
+ }
+
+ @Test
+ public void ensureNodeIsStored() {
+ registry.offerAddresses(Arrays.asList(
+ new NetworkAddress.Builder()
+ .ipv4(127, 0, 0, 1)
+ .port(42)
+ .stream(1)
+ .time(UnixTime.now())
+ .build(),
+ new NetworkAddress.Builder()
+ .ipv4(127, 0, 0, 2)
+ .port(42)
+ .stream(1)
+ .time(UnixTime.now())
+ .build(),
+ new NetworkAddress.Builder()
+ .ipv4(127, 0, 0, 2)
+ .port(42)
+ .stream(2)
+ .time(UnixTime.now())
+ .build()
+ ));
+ assertThat(registry.getKnownAddresses(10, 1).size(), is(2));
+ assertThat(registry.getKnownAddresses(10, 2).size(), is(1));
+ assertThat(registry.getKnownAddresses(10, 1, 2).size(), is(3));
+ }
+
+ @Test
+ public void ensureOldNodesAreRemoved() {
+ registry.offerAddresses(Arrays.asList(
+ new NetworkAddress.Builder()
+ .ipv4(127, 0, 0, 1)
+ .port(42)
+ .stream(1)
+ .time(UnixTime.now())
+ .build(),
+ new NetworkAddress.Builder()
+ .ipv4(127, 0, 0, 2)
+ .port(42)
+ .stream(1)
+ .time(UnixTime.now(-4 * HOUR))
+ .build(),
+ new NetworkAddress.Builder()
+ .ipv4(127, 0, 0, 2)
+ .port(42)
+ .stream(2)
+ .time(UnixTime.now())
+ .build()
+ ));
+ assertThat(registry.getKnownAddresses(10, 1).size(), is(1));
+ assertThat(registry.getKnownAddresses(10, 2).size(), is(1));
+ assertThat(registry.getKnownAddresses(10, 1, 2).size(), is(2));
+ }
+}
From 43f42dd400a2af4ca6b15f8065c8bdc57b7c3ca9 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 20 May 2016 07:32:41 +0200
Subject: [PATCH 42/87] This breaks a lot of things, but it seems necessary.
Implemented the resending mechanism and fixed many problems on the way, but
tests and triggers are still to do.
---
.../dissem/bitmessage/BitmessageContext.java | 4 +-
.../bitmessage/DefaultMessageListener.java | 9 ++-
.../ch/dissem/bitmessage/InternalContext.java | 16 +++-
.../dissem/bitmessage/ProofOfWorkService.java | 2 +
.../dissem/bitmessage/entity/Plaintext.java | 74 ++++++++++++++++---
.../ch/dissem/bitmessage/entity/Version.java | 4 +-
.../ch/dissem/bitmessage/factory/Factory.java | 2 +-
.../bitmessage/ports/MessageRepository.java | 2 +
.../bitmessage/entity/SerializationTest.java | 28 ++++++-
.../bitmessage/ports/NodeRegistryTest.java | 4 +
.../bitmessage/networking/Connection.java | 19 +++--
.../networking/ConnectionOrganizer.java | 7 +-
.../networking/DefaultNetworkHandler.java | 5 +-
.../bitmessage/networking/ServerRunnable.java | 7 +-
.../repository/JdbcMessageRepository.java | 50 ++++++++++---
.../migration/V3.2__Update_table_message.sql | 3 +
.../repository/JdbcMessageRepositoryTest.java | 18 ++++-
17 files changed, 202 insertions(+), 52 deletions(-)
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index aef6a67..ad21e72 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -167,13 +167,13 @@ public class BitmessageContext {
LOG.info("Sending message.");
ctx.getMessageRepository().save(msg);
if (msg.getType() == MSG) {
- ctx.send(msg, TTL.msg());
+ ctx.send(msg);
} else {
ctx.send(
msg.getFrom(),
to,
Factory.getBroadcast(msg),
- TTL.msg()
+ msg.getTTL()
);
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index 05961e4..b5d70d1 100644
--- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -51,8 +51,11 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
@SuppressWarnings("ConstantConditions")
public void receive(ObjectMessage object) throws IOException {
ObjectPayload payload = object.getPayload();
- if (payload.getType() == null && payload instanceof GenericPayload) {
- receive((GenericPayload) payload);
+ if (payload.getType() == null) {
+ if (payload instanceof GenericPayload) {
+ receive((GenericPayload) payload);
+ }
+ return;
}
switch (payload.getType()) {
@@ -115,7 +118,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
for (Plaintext msg : messages) {
ctx.getLabeler().markAsSending(msg);
ctx.getMessageRepository().save(msg);
- ctx.send(msg, TTL.msg());
+ ctx.send(msg);
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 884dd16..057bfef 100644
--- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Arrays;
+import java.util.List;
import java.util.TreeSet;
/**
@@ -160,12 +161,13 @@ public class InternalContext {
return port;
}
- public void send(final Plaintext plaintext, final long timeToLive) {
+ public void send(final Plaintext plaintext) {
if (plaintext.getAckMessage() != null) {
- long expires = UnixTime.now(+timeToLive);
+ long expires = UnixTime.now(+plaintext.getTTL());
+ LOG.info("Expires at " + expires);
proofOfWorkService.doProofOfWorkWithAck(plaintext, expires);
} else {
- send(plaintext.getFrom(), plaintext.getTo(), new Msg(plaintext), timeToLive);
+ send(plaintext.getFrom(), plaintext.getTo(), new Msg(plaintext), plaintext.getTTL());
}
}
@@ -278,6 +280,14 @@ public class InternalContext {
}
}
+ public void resendUnacknowledged() {
+ List messages = messageRepository.findMessagesToResend();
+ for (Plaintext message : messages) {
+ send(message);
+ messageRepository.save(message);
+ }
+ }
+
public long getClientNonce() {
return clientNonce;
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
index 04016b5..e9a27db 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -69,6 +69,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
public void doProofOfWorkWithAck(Plaintext plaintext, long expirationTime) {
final ObjectMessage ack = plaintext.getAckMessage();
+ messageRepo.save(plaintext);
Item item = new Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES,
expirationTime, plaintext);
powRepo.putObject(item);
@@ -84,6 +85,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
Plaintext plaintext = messageRepo.getMessage(initialHash);
if (plaintext != null) {
plaintext.setInventoryVector(object.getInventoryVector());
+ plaintext.updateNextTry();
ctx.getLabeler().markAsSent(plaintext);
messageRepo.save(plaintext);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index b90bf14..90c3c50 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -17,13 +17,14 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Msg;
-import ch.dissem.bitmessage.entity.payload.Pubkey;
+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.exception.ApplicationException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
+import ch.dissem.bitmessage.utils.TTL;
import ch.dissem.bitmessage.utils.UnixTime;
import java.io.*;
@@ -54,6 +55,10 @@ public class Plaintext implements Streamable {
private Set labels;
private byte[] initialHash;
+ private long ttl;
+ private int retries;
+ private Long nextTry;
+
private Plaintext(Builder builder) {
id = builder.id;
inventoryVector = builder.inventoryVector;
@@ -74,6 +79,9 @@ public class Plaintext implements Streamable {
sent = builder.sent;
received = builder.received;
labels = builder.labels;
+ ttl = builder.ttl;
+ retries = builder.retries;
+ nextTry = builder.nextTry;
}
public static Plaintext read(Type type, InputStream in) throws IOException {
@@ -96,7 +104,7 @@ public class Plaintext implements Streamable {
.destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null)
.encoding(Decode.varInt(in))
.message(Decode.varBytes(in))
- .ack(type == Type.MSG ? Decode.varBytes(in) : null);
+ .ackMessage(type == Type.MSG ? Decode.varBytes(in) : null);
}
public InventoryVector getInventoryVector() {
@@ -174,12 +182,10 @@ public class Plaintext implements Streamable {
Encode.varInt(message.length, out);
out.write(message);
if (type == Type.MSG) {
- if (to.has(Pubkey.Feature.DOES_ACK) && getAckMessage() != null) {
+ if (to.has(Feature.DOES_ACK) && getAckMessage() != null) {
ByteArrayOutputStream ack = new ByteArrayOutputStream();
getAckMessage().write(ack);
- byte[] data = ack.toByteArray();
- Encode.varInt(data.length, out);
- out.write(data);
+ Encode.varBytes(ack.toByteArray(), out);
} else {
Encode.varInt(0, out);
}
@@ -224,6 +230,30 @@ public class Plaintext implements Streamable {
this.status = status;
}
+ public long getTTL() {
+ return ttl;
+ }
+
+ public int getRetries() {
+ return retries;
+ }
+
+ public Long getNextTry() {
+ return nextTry;
+ }
+
+ public void updateNextTry() {
+ if (nextTry == null) {
+ if (sent != null && to.has(Feature.DOES_ACK)) {
+ nextTry = sent + ttl;
+ retries++;
+ }
+ } else {
+ nextTry = nextTry + (1 << retries) * ttl;
+ retries++;
+ }
+ }
+
public String getSubject() {
Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8");
String firstLine = s.nextLine();
@@ -272,9 +302,7 @@ public class Plaintext implements Streamable {
public void addLabels(Label... labels) {
if (labels != null) {
- for (Label label : labels) {
- this.labels.add(label);
- }
+ Collections.addAll(this.labels, labels);
}
}
@@ -366,6 +394,9 @@ public class Plaintext implements Streamable {
private long received;
private Status status;
private Set labels = new HashSet<>();
+ private long ttl;
+ private int retries;
+ private Long nextTry;
public Builder(Type type) {
this.type = type;
@@ -459,14 +490,15 @@ public class Plaintext implements Streamable {
return this;
}
- public Builder ack(byte[] ack) {
- if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ack only allowed for msg");
+ public Builder ackMessage(byte[] ack) {
+ if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ackMessage only allowed for msg");
this.ackMessage = ack;
return this;
}
public Builder ackData(byte[] ackData) {
- if (type != Type.MSG && ackData != null) throw new IllegalArgumentException("ack only allowed for msg");
+ if (type != Type.MSG && ackData != null)
+ throw new IllegalArgumentException("ackMessage only allowed for msg");
this.ackData = ackData;
return this;
}
@@ -496,6 +528,21 @@ public class Plaintext implements Streamable {
return this;
}
+ public Builder ttl(long ttl) {
+ this.ttl = ttl;
+ return this;
+ }
+
+ public Builder retries(int retries) {
+ this.retries = retries;
+ return this;
+ }
+
+ public Builder nextTry(Long nextTry) {
+ this.nextTry = nextTry;
+ return this;
+ }
+
public Plaintext build() {
if (from == null) {
from = new BitmessageAddress(Factory.createPubkey(
@@ -514,6 +561,9 @@ public class Plaintext implements Streamable {
if (type == Type.MSG && ackMessage == null && ackData == null) {
ackData = cryptography().randomBytes(Msg.ACK_LENGTH);
}
+ if (ttl <= 0) {
+ ttl = TTL.msg();
+ }
return new Plaintext(this);
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java b/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
index 3722528..8539be1 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
@@ -145,13 +145,13 @@ public class Version implements MessagePayload {
private String userAgent;
private long[] streamNumbers;
- public Builder defaults() {
+ public Builder defaults(long clientNonce) {
version = BitmessageContext.CURRENT_VERSION;
services = 1;
timestamp = UnixTime.now();
- nonce = new Random().nextInt();
userAgent = "/Jabit:0.0.1/";
streamNumbers = new long[]{1};
+ nonce = clientNonce;
return this;
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
index 53e1676..0597f6a 100644
--- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
@@ -212,6 +212,6 @@ public class Factory {
if (plaintext == null || plaintext.getAckData() == null)
return null;
GenericPayload ack = new GenericPayload(3, plaintext.getFrom().getStream(), plaintext.getAckData());
- return new ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now(+TTL.msg())).build();
+ return new ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now(plaintext.getTTL())).build();
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
index 0c46499..aca16b9 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java
@@ -44,6 +44,8 @@ public interface MessageRepository {
List findMessages(BitmessageAddress sender);
+ List findMessagesToResend();
+
void save(Plaintext message);
void remove(Plaintext message);
diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
index 26d8a35..5405f23 100644
--- a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java
@@ -82,7 +82,7 @@ public class SerializationTest extends TestBase {
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.to(TestUtils.loadContact())
.message("Subject", "Message")
- .ackData("ack".getBytes())
+ .ackData("ackMessage".getBytes())
.signature(new byte[0])
.build();
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -98,6 +98,32 @@ public class SerializationTest extends TestBase {
assertEquals(p1, p2);
}
+ @Test
+ public void ensurePlaintextWithAckMessageIsSerializedAndDeserializedCorrectly() throws Exception {
+ Plaintext p1 = new Plaintext.Builder(MSG)
+ .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
+ .to(TestUtils.loadContact())
+ .message("Subject", "Message")
+ .ackData("ackMessage".getBytes())
+ .signature(new byte[0])
+ .build();
+ ObjectMessage ackMessage1 = p1.getAckMessage();
+ assertNotNull(ackMessage1);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ 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);
+ assertEquals(ackMessage1, p2.getAckMessage());
+ }
+
@Test
public void ensureNetworkMessageIsSerializedAndDeserializedCorrectly() throws Exception {
ArrayList ivs = new ArrayList<>(50000);
diff --git a/core/src/test/java/ch/dissem/bitmessage/ports/NodeRegistryTest.java b/core/src/test/java/ch/dissem/bitmessage/ports/NodeRegistryTest.java
index 0e54446..993e3a9 100644
--- a/core/src/test/java/ch/dissem/bitmessage/ports/NodeRegistryTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/ports/NodeRegistryTest.java
@@ -34,6 +34,10 @@ public class NodeRegistryTest {
assertThat(registry.getKnownAddresses(10), empty());
}
+ /**
+ * Please note that this test fails if there is no internet connection,
+ * as the initial nodes' IP addresses are determined by DNS lookup.
+ */
@Test
public void ensureGetKnownNodesForStream1YieldsResult() {
assertThat(registry.getKnownAddresses(10, 1), hasSize(1));
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 4717675..9ed19bc 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -72,6 +72,7 @@ class Connection {
private final ReaderRunnable reader = new ReaderRunnable();
private final WriterRunnable writer = new WriterRunnable();
private final DefaultNetworkHandler networkHandler;
+ private final long clientNonce;
private volatile State state;
private InputStream in;
@@ -83,22 +84,23 @@ class Connection {
private long lastObjectTime;
public Connection(InternalContext context, Mode mode, Socket socket, MessageListener listener,
- Set requestedObjectsMap) throws IOException {
+ Set requestedObjectsMap, long clientNonce) 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);
+ 0, clientNonce);
}
public Connection(InternalContext context, Mode mode, NetworkAddress node, MessageListener listener,
- Set requestedObjectsMap) {
+ Set requestedObjectsMap, long clientNonce) {
this(context, mode, listener, new Socket(), requestedObjectsMap,
Collections.newSetFromMap(new ConcurrentHashMap(10_000)),
- node, 0);
+ node, 0, clientNonce);
}
private Connection(InternalContext context, Mode mode, MessageListener listener, Socket socket,
- Set commonRequestedObjects, Set requestedObjects, NetworkAddress node, long syncTimeout) {
+ Set commonRequestedObjects, Set requestedObjects,
+ NetworkAddress node, long syncTimeout, long clientNonce) {
this.startTime = UnixTime.now();
this.ctx = context;
this.mode = mode;
@@ -112,6 +114,7 @@ class Connection {
this.syncTimeout = (syncTimeout > 0 ? UnixTime.now(+syncTimeout) : 0);
this.ivCache = new ConcurrentHashMap<>();
this.networkHandler = (DefaultNetworkHandler) ctx.getNetworkHandler();
+ this.clientNonce = clientNonce;
}
public static Connection sync(InternalContext ctx, InetAddress address, int port, MessageListener listener,
@@ -120,7 +123,7 @@ class Connection {
new HashSet(),
new HashSet(),
new NetworkAddress.Builder().ip(address).port(port).stream(1).build(),
- timeoutInSeconds);
+ timeoutInSeconds, cryptography().randomNonce());
}
public long getStartTime() {
@@ -362,7 +365,7 @@ class Connection {
try (Socket socket = Connection.this.socket) {
initSocket(socket);
if (mode == CLIENT || mode == SYNC) {
- send(new Version.Builder().defaults().addrFrom(host).addrRecv(node).build());
+ send(new Version.Builder().defaults(clientNonce).addrFrom(host).addrRecv(node).build());
}
while (state != DISCONNECTED) {
if (mode != SYNC) {
@@ -446,7 +449,7 @@ class Connection {
send(new VerAck());
switch (mode) {
case SERVER:
- send(new Version.Builder().defaults().addrFrom(host).addrRecv(node).build());
+ send(new Version.Builder().defaults(clientNonce).addrFrom(host).addrRecv(node).build());
break;
case CLIENT:
case SYNC:
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java
index 1163d1c..c25a31c 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java
@@ -38,15 +38,17 @@ public class ConnectionOrganizer implements Runnable {
private final InternalContext ctx;
private final DefaultNetworkHandler networkHandler;
private final NetworkHandler.MessageListener listener;
+ private final long clientNonce;
private Connection initialConnection;
public ConnectionOrganizer(InternalContext ctx,
DefaultNetworkHandler networkHandler,
- NetworkHandler.MessageListener listener) {
+ NetworkHandler.MessageListener listener, long clientNonce) {
this.ctx = ctx;
this.networkHandler = networkHandler;
this.listener = listener;
+ this.clientNonce = clientNonce;
}
@Override
@@ -91,7 +93,8 @@ public class ConnectionOrganizer implements Runnable {
NETWORK_MAGIC_NUMBER - active, ctx.getStreams());
boolean first = active == 0 && initialConnection == null;
for (NetworkAddress address : addresses) {
- Connection c = new Connection(ctx, CLIENT, address, listener, networkHandler.requestedObjects);
+ Connection c = new Connection(ctx, CLIENT, address, listener,
+ networkHandler.requestedObjects, clientNonce);
if (first) {
initialConnection = c;
first = false;
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index 289aa79..635aed2 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -28,7 +28,6 @@ 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.ThreadFactoryBuilder;
import java.io.IOException;
import java.net.InetAddress;
@@ -109,9 +108,9 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
try {
running = true;
connections.clear();
- server = new ServerRunnable(ctx, this, listener);
+ server = new ServerRunnable(ctx, this, listener, ctx.getClientNonce());
pool.execute(server);
- pool.execute(new ConnectionOrganizer(ctx, this, listener));
+ pool.execute(new ConnectionOrganizer(ctx, this, listener, ctx.getClientNonce()));
} catch (IOException e) {
throw new ApplicationException(e);
}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java
index bf6d6f6..5a866b9 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java
@@ -37,12 +37,15 @@ public class ServerRunnable implements Runnable, Closeable {
private final ServerSocket serverSocket;
private final DefaultNetworkHandler networkHandler;
private final NetworkHandler.MessageListener listener;
+ private final long clientNonce;
- public ServerRunnable(InternalContext ctx, DefaultNetworkHandler networkHandler, NetworkHandler.MessageListener listener) throws IOException {
+ public ServerRunnable(InternalContext ctx, DefaultNetworkHandler networkHandler,
+ NetworkHandler.MessageListener listener, long clientNonce) throws IOException {
this.ctx = ctx;
this.networkHandler = networkHandler;
this.listener = listener;
this.serverSocket = new ServerSocket(ctx.getPort());
+ this.clientNonce = clientNonce;
}
@Override
@@ -52,7 +55,7 @@ public class ServerRunnable implements Runnable, Closeable {
Socket socket = serverSocket.accept();
socket.setSoTimeout(Connection.READ_TIMEOUT);
networkHandler.startConnection(new Connection(ctx, SERVER, socket, listener,
- networkHandler.requestedObjects));
+ networkHandler.requestedObjects, clientNonce));
} catch (IOException e) {
LOG.debug(e.getMessage(), e);
}
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
index ffc639f..7ac42f9 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
@@ -24,6 +24,8 @@ import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.Strings;
+import ch.dissem.bitmessage.utils.TTL;
+import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -165,13 +167,20 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
return find("sender='" + sender.getAddress() + "'");
}
+ @Override
+ public List findMessagesToResend() {
+ return find("status='" + Plaintext.Status.SENT.name() + "'" +
+ " AND next_try < " + UnixTime.now());
+ }
+
private List find(String where) {
List result = new LinkedList<>();
try (
Connection connection = config.getConnection();
Statement stmt = connection.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, status " +
- "FROM Message WHERE " + where)
+ ResultSet rs = stmt.executeQuery(
+ "SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, initial_hash, status, ttl, retries, next_try " +
+ "FROM Message WHERE " + where)
) {
while (rs.next()) {
byte[] iv = rs.getBytes("iv");
@@ -187,8 +196,13 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
builder.sent(rs.getLong("sent"));
builder.received(rs.getLong("received"));
builder.status(Plaintext.Status.valueOf(rs.getString("status")));
+ builder.ttl(rs.getLong("ttl"));
+ builder.retries(rs.getInt("retries"));
+ builder.nextTry(rs.getLong("next_try"));
builder.labels(findLabels(connection, id));
- result.add(builder.build());
+ Plaintext message = builder.build();
+ message.setInitialHash(rs.getBytes("initial_hash"));
+ result.add(message);
}
} catch (IOException | SQLException e) {
LOG.error(e.getMessage(), e);
@@ -265,8 +279,9 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private void insert(Connection connection, Plaintext message) throws SQLException, IOException {
try (PreparedStatement ps = connection.prepareStatement(
- "INSERT INTO Message (iv, type, sender, recipient, data, ack_data, sent, received, status, initial_hash) " +
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ "INSERT INTO Message (iv, type, sender, recipient, data, ack_data, sent, received, " +
+ "status, initial_hash, ttl, retries, next_try) " +
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS)
) {
ps.setBytes(1, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
@@ -279,6 +294,9 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
ps.setLong(8, message.getReceived());
ps.setString(9, message.getStatus() == null ? null : message.getStatus().name());
ps.setBytes(10, message.getInitialHash());
+ ps.setLong(11, message.getTTL());
+ ps.setInt(12, message.getRetries());
+ ps.setObject(13, message.getNextTry());
ps.executeUpdate();
// get generated id
@@ -291,13 +309,23 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private void update(Connection connection, Plaintext message) throws SQLException, IOException {
try (PreparedStatement ps = connection.prepareStatement(
- "UPDATE Message SET iv=?, sent=?, received=?, status=?, initial_hash=? WHERE id=?")) {
+ "UPDATE Message SET iv=?, type=?, sender=?, recipient=?, data=?, ack_data=?, sent=?, received=?, " +
+ "status=?, initial_hash=?, ttl=?, retries=?, next_try=? " +
+ "WHERE id=?")) {
ps.setBytes(1, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
- ps.setLong(2, message.getSent());
- ps.setLong(3, message.getReceived());
- ps.setString(4, message.getStatus() == null ? null : message.getStatus().name());
- ps.setBytes(5, message.getInitialHash());
- ps.setLong(6, (Long) message.getId());
+ ps.setString(2, message.getType().name());
+ ps.setString(3, message.getFrom().getAddress());
+ ps.setString(4, message.getTo() == null ? null : message.getTo().getAddress());
+ writeBlob(ps, 5, message);
+ ps.setBytes(6, message.getAckData());
+ ps.setLong(7, message.getSent());
+ ps.setLong(8, message.getReceived());
+ ps.setString(9, message.getStatus() == null ? null : message.getStatus().name());
+ ps.setBytes(10, message.getInitialHash());
+ ps.setLong(11, message.getTTL());
+ ps.setInt(12, message.getRetries());
+ ps.setObject(13, message.getNextTry());
+ ps.setLong(14, (Long) message.getId());
ps.executeUpdate();
}
}
diff --git a/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql b/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql
index 8b43b0a..38847cf 100644
--- a/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql
+++ b/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql
@@ -1 +1,4 @@
ALTER TABLE Message ADD COLUMN ack_data BINARY(32);
+ALTER TABLE Message ADD COLUMN ttl BIGINT NOT NULL;
+ALTER TABLE Message ADD COLUMN retries INT NOT NULL DEFAULT 0;
+ALTER TABLE Message ADD COLUMN next_try BIGINT;
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 81051de..df8a42e 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
@@ -19,6 +19,7 @@ package ch.dissem.bitmessage.repository;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
@@ -32,6 +33,7 @@ import java.util.Arrays;
import java.util.List;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
+import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
@@ -59,14 +61,14 @@ public class JdbcMessageRepositoryTest extends TestBase {
.messageRepo(repo)
);
- BitmessageAddress tmp = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000));
+ BitmessageAddress tmp = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK));
contactA = new BitmessageAddress(tmp.getAddress());
contactA.setPubkey(tmp.getPubkey());
addressRepo.save(contactA);
contactB = new BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj");
addressRepo.save(contactB);
- identity = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000));
+ identity = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK));
addressRepo.save(identity);
inbox = repo.getLabels(Label.Type.INBOX).get(0);
@@ -123,6 +125,18 @@ public class JdbcMessageRepositoryTest extends TestBase {
assertThat(other, is(message));
}
+ @Test
+ public void ensureAckMessageCanBeUpdatedAndRetrieved() {
+ byte[] initialHash = new byte[64];
+ Plaintext message = repo.findMessages(contactA).get(0);
+ message.setInitialHash(initialHash);
+ ObjectMessage ackMessage = message.getAckMessage();
+ repo.save(message);
+ Plaintext other = repo.getMessage(initialHash);
+ assertThat(other, is(message));
+ assertThat(other.getAckMessage(), is(ackMessage));
+ }
+
@Test
public void testFindMessagesByStatus() throws Exception {
List messages = repo.findMessages(Plaintext.Status.RECEIVED);
From c3d8a07e8385335856040bdfe8fdd13aa961ad5d Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 20 May 2016 23:00:27 +0200
Subject: [PATCH 43/87] Added unit tests and fixed bug
---
.../dissem/bitmessage/entity/Plaintext.java | 8 ++--
.../bitmessage/BitmessageContextTest.java | 25 ++++++++++--
.../repository/JdbcMessageRepositoryTest.java | 38 +++++++++++++++++--
3 files changed, 59 insertions(+), 12 deletions(-)
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index 90c3c50..4bfaba4 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -22,13 +22,11 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.factory.Factory;
-import ch.dissem.bitmessage.utils.Decode;
-import ch.dissem.bitmessage.utils.Encode;
-import ch.dissem.bitmessage.utils.TTL;
-import ch.dissem.bitmessage.utils.UnixTime;
+import ch.dissem.bitmessage.utils.*;
import java.io.*;
import java.util.*;
+import java.util.Collections;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
@@ -245,7 +243,7 @@ public class Plaintext implements Streamable {
public void updateNextTry() {
if (nextTry == null) {
if (sent != null && to.has(Feature.DOES_ACK)) {
- nextTry = sent + ttl;
+ nextTry = UnixTime.now(+ttl);
retries++;
}
} else {
diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
index 6e49853..5ad91cb 100644
--- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
@@ -20,6 +20,7 @@ import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.Plaintext.Type;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
@@ -63,6 +64,7 @@ public class BitmessageContextTest {
.messageRepo(mock(MessageRepository.class))
.networkHandler(mock(NetworkHandler.class))
.nodeRegistry(mock(NodeRegistry.class))
+ .labeler(spy(new DefaultLabeler()))
.powRepo(spy(new ProofOfWorkRepository() {
Map items = new HashMap<>();
@@ -205,7 +207,7 @@ public class BitmessageContextTest {
assertEquals(2, ctx.internals().getProofOfWorkRepository().getItems().size());
verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce())
.putObject(object(MSG), eq(1000L), eq(1000L));
- verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Plaintext.Type.MSG));
+ verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Type.MSG));
}
@Test
@@ -215,7 +217,7 @@ public class BitmessageContextTest {
"Subject", "Message");
verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce())
.putObject(object(GET_PUBKEY), eq(1000L), eq(1000L));
- verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Plaintext.Type.MSG));
+ verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Type.MSG));
}
@Test(expected = IllegalArgumentException.class)
@@ -234,12 +236,12 @@ public class BitmessageContextTest {
verify(ctx.internals().getProofOfWorkEngine())
.calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class));
verify(ctx.messages(), timeout(10000).atLeastOnce())
- .save(MessageMatchers.plaintext(Plaintext.Type.BROADCAST));
+ .save(MessageMatchers.plaintext(Type.BROADCAST));
}
@Test(expected = IllegalArgumentException.class)
public void ensureSenderWithoutPrivateKeyThrowsException() {
- Plaintext msg = new Plaintext.Builder(Plaintext.Type.BROADCAST)
+ Plaintext msg = new Plaintext.Builder(Type.BROADCAST)
.from(new BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.message("Subject", "Message")
.build();
@@ -295,4 +297,19 @@ public class BitmessageContextTest {
assertEquals(chan.getVersion(), Pubkey.LATEST_VERSION);
assertTrue(chan.isChan());
}
+
+ @Test
+ public void ensureUnacknowledgedMessageIsResent() throws Exception {
+ Plaintext plaintext = new Plaintext.Builder(Type.MSG)
+ .ttl(1)
+ .message("subject", "message")
+ .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
+ .to(TestUtils.loadContact())
+ .build();
+ assertTrue(plaintext.getTo().has(Pubkey.Feature.DOES_ACK));
+ when(ctx.messages().findMessagesToResend()).thenReturn(Collections.singletonList(plaintext));
+ when(ctx.messages().getMessage(any(byte[].class))).thenReturn(plaintext);
+ ctx.internals().resendUnacknowledged();
+ verify(ctx.labeler(), timeout(1000).times(1)).markAsSent(eq(plaintext));
+ }
}
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 df8a42e..5fa88e7 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
@@ -26,6 +26,8 @@ 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.UnixTime;
+import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
@@ -36,6 +38,7 @@ import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class JdbcMessageRepositoryTest extends TestBase {
@@ -192,11 +195,40 @@ public class JdbcMessageRepositoryTest extends TestBase {
}
@Test
- public void testRemove() throws Exception {
+ public void ensureMessageIsRemoved() throws Exception {
Plaintext toRemove = repo.findMessages(Plaintext.Status.DRAFT, contactB).get(0);
- repo.remove(toRemove);
List messages = repo.findMessages(Plaintext.Status.DRAFT);
- assertEquals(1, messages.size());
+ assertEquals(2, messages.size());
+ repo.remove(toRemove);
+ messages = repo.findMessages(Plaintext.Status.DRAFT);
+ assertThat(messages, hasSize(1));
+ }
+
+ @Test
+ public void ensureUnacknowledgedMessagesAreFoundForResend() throws Exception {
+ Plaintext message = new Plaintext.Builder(MSG)
+ .IV(new InventoryVector(cryptography().randomBytes(32)))
+ .from(identity)
+ .to(contactA)
+ .message("Subject", "Message")
+ .status(Plaintext.Status.SENT)
+ .ttl(1)
+ .build();
+ message.updateNextTry();
+ assertThat(message.getRetries(), is(1));
+ assertThat(message.getNextTry(), greaterThan(UnixTime.now()));
+ assertThat(message.getNextTry(), lessThanOrEqualTo(UnixTime.now(+1)));
+ repo.save(message);
+ Thread.sleep(2100);
+ List messagesToResend = repo.findMessagesToResend();
+ assertThat(messagesToResend, hasSize(1));
+
+ message.updateNextTry();
+ assertThat(message.getRetries(), is(2));
+ assertThat(message.getNextTry(), greaterThan(UnixTime.now()));
+ repo.save(message);
+ messagesToResend = repo.findMessagesToResend();
+ assertThat(messagesToResend, empty());
}
private void addMessage(BitmessageAddress from, BitmessageAddress to, Plaintext.Status status, Label... labels) {
From 14849a82ea67136190f6ce1699596fbc328f94fc Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Fri, 20 May 2016 23:58:08 +0200
Subject: [PATCH 44/87] Refactored JdbcMessageRepository so that alternative
implementations can be done easier
---
.gitignore | 1 +
.../ports/AbstractMessageRepository.java | 123 +++++++++++++++++
.../dissem/bitmessage/utils/SqlStrings.java | 59 ++++++++
.../bitmessage/utils/SqlStringsTest.java | 8 +-
.../bitmessage/repository/JdbcHelper.java | 46 +------
.../bitmessage/repository/JdbcInventory.java | 1 +
.../repository/JdbcMessageRepository.java | 127 +++---------------
7 files changed, 206 insertions(+), 159 deletions(-)
create mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java
create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java
rename repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcHelperTest.java => core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java (81%)
diff --git a/.gitignore b/.gitignore
index fd29962..98d31cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
### Gradle ###
.gradle
build/
+classes/
# Ignore Gradle GUI config
gradle-app.setting
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java
new file mode 100644
index 0000000..e037c4f
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016 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.BitmessageAddress;
+import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.valueobject.Label;
+import ch.dissem.bitmessage.exception.ApplicationException;
+import ch.dissem.bitmessage.utils.Strings;
+import ch.dissem.bitmessage.utils.UnixTime;
+
+import java.util.Collection;
+import java.util.List;
+
+import static ch.dissem.bitmessage.utils.SqlStrings.join;
+
+public abstract class AbstractMessageRepository implements MessageRepository, InternalContext.ContextHolder {
+ protected InternalContext ctx;
+
+ @Override
+ public void setContext(InternalContext context) {
+ this.ctx = context;
+ }
+
+ protected void safeSenderIfNecessary(Plaintext message) {
+ if (message.getId() == null) {
+ BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(message.getFrom().getAddress());
+ if (savedAddress == null) {
+ ctx.getAddressRepository().save(message.getFrom());
+ } else if (savedAddress.getPubkey() == null && message.getFrom().getPubkey() != null) {
+ savedAddress.setPubkey(message.getFrom().getPubkey());
+ ctx.getAddressRepository().save(savedAddress);
+ }
+ }
+ }
+
+ @Override
+ public Plaintext getMessage(Object id) {
+ if (id instanceof Long) {
+ return single(find("id=" + id));
+ } else {
+ throw new IllegalArgumentException("Long expected for ID");
+ }
+ }
+
+ @Override
+ public Plaintext getMessage(byte[] initialHash) {
+ return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'"));
+ }
+
+ @Override
+ public Plaintext getMessageForAck(byte[] ackData) {
+ return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'"));
+ }
+
+ @Override
+ public List findMessages(Label label) {
+ return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")");
+ }
+
+ @Override
+ public List findMessages(Plaintext.Status status, BitmessageAddress recipient) {
+ return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'");
+ }
+
+ @Override
+ public List findMessages(Plaintext.Status status) {
+ return find("status='" + status.name() + "'");
+ }
+
+ @Override
+ public List findMessages(BitmessageAddress sender) {
+ return find("sender='" + sender.getAddress() + "'");
+ }
+
+ @Override
+ public List findMessagesToResend() {
+ return find("status='" + Plaintext.Status.SENT.name() + "'" +
+ " AND next_try < " + UnixTime.now());
+ }
+
+ @Override
+ public List getLabels() {
+ return findLabels("1=1");
+ }
+
+ @Override
+ public List getLabels(Label.Type... types) {
+ return findLabels("type IN (" + join(types) + ")");
+ }
+
+ protected abstract List findLabels(String where);
+
+
+ protected T single(Collection collection) {
+ switch (collection.size()) {
+ case 0:
+ return null;
+ case 1:
+ return collection.iterator().next();
+ default:
+ throw new ApplicationException("This shouldn't happen, found " + collection.size() +
+ " items, one or none was expected");
+ }
+ }
+
+ protected abstract List find(String where);
+}
diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java b/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java
new file mode 100644
index 0000000..82d06e3
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 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.payload.ObjectType;
+
+import static ch.dissem.bitmessage.utils.Strings.hex;
+
+public class SqlStrings {
+ public static StringBuilder join(long... objects) {
+ StringBuilder streamList = new StringBuilder();
+ for (int i = 0; i < objects.length; i++) {
+ if (i > 0) streamList.append(", ");
+ streamList.append(objects[i]);
+ }
+ return streamList;
+ }
+
+ public static StringBuilder join(byte[]... objects) {
+ StringBuilder streamList = new StringBuilder();
+ for (int i = 0; i < objects.length; i++) {
+ if (i > 0) streamList.append(", ");
+ streamList.append(hex(objects[i]));
+ }
+ return streamList;
+ }
+
+ public static StringBuilder join(ObjectType... types) {
+ StringBuilder streamList = new StringBuilder();
+ for (int i = 0; i < types.length; i++) {
+ if (i > 0) streamList.append(", ");
+ streamList.append(types[i].getNumber());
+ }
+ return streamList;
+ }
+
+ public static StringBuilder join(Enum... types) {
+ StringBuilder streamList = new StringBuilder();
+ for (int i = 0; i < types.length; i++) {
+ if (i > 0) streamList.append(", ");
+ streamList.append('\'').append(types[i].name()).append('\'');
+ }
+ return streamList;
+ }
+}
diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcHelperTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java
similarity index 81%
rename from repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcHelperTest.java
rename to core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java
index ee3a580..2aa1113 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcHelperTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Christian Basler
+ * Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package ch.dissem.bitmessage.repository;
+package ch.dissem.bitmessage.utils;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
-public class JdbcHelperTest {
+public class SqlStringsTest {
@Test
public void ensureJoinWorksWithLongArray() {
long[] test = {1L, 2L};
- assertEquals("1, 2", JdbcHelper.join(test).toString());
+ assertEquals("1, 2", SqlStrings.join(test).toString());
}
}
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 beb4c80..b0be9c1 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
@@ -42,43 +42,7 @@ public abstract class JdbcHelper {
this.config = config;
}
- public static StringBuilder join(long... objects) {
- StringBuilder streamList = new StringBuilder();
- for (int i = 0; i < objects.length; i++) {
- if (i > 0) streamList.append(", ");
- streamList.append(objects[i]);
- }
- return streamList;
- }
-
- public static StringBuilder join(byte[]... objects) {
- StringBuilder streamList = new StringBuilder();
- for (int i = 0; i < objects.length; i++) {
- if (i > 0) streamList.append(", ");
- streamList.append(hex(objects[i]));
- }
- return streamList;
- }
-
- public static StringBuilder join(ObjectType... types) {
- StringBuilder streamList = new StringBuilder();
- for (int i = 0; i < types.length; i++) {
- if (i > 0) streamList.append(", ");
- streamList.append(types[i].getNumber());
- }
- return streamList;
- }
-
- public static StringBuilder join(Enum... types) {
- StringBuilder streamList = new StringBuilder();
- for (int i = 0; i < types.length; i++) {
- if (i > 0) streamList.append(", ");
- streamList.append('\'').append(types[i].name()).append('\'');
- }
- return streamList;
- }
-
- protected void writeBlob(PreparedStatement ps, int parameterIndex, Streamable data) throws SQLException, IOException {
+ public static void writeBlob(PreparedStatement ps, int parameterIndex, Streamable data) throws SQLException, IOException {
if (data == null) {
ps.setBytes(parameterIndex, null);
} else {
@@ -87,12 +51,4 @@ public abstract class JdbcHelper {
ps.setBytes(parameterIndex, os.toByteArray());
}
}
-
- protected T single(Collection collection) {
- if (collection.size() > 1) {
- throw new ApplicationException("This shouldn't happen, found " + collection.size() +
- " messages, one or none was expected");
- }
- return collection.stream().findAny().orElse(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 0cc9f3b..13d3725 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java
@@ -31,6 +31,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import static ch.dissem.bitmessage.utils.SqlStrings.join;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static ch.dissem.bitmessage.utils.UnixTime.now;
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 7ac42f9..403754a 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java
@@ -16,16 +16,12 @@
package ch.dissem.bitmessage.repository;
-import ch.dissem.bitmessage.InternalContext;
-import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.exception.ApplicationException;
+import ch.dissem.bitmessage.ports.AbstractMessageRepository;
import ch.dissem.bitmessage.ports.MessageRepository;
-import ch.dissem.bitmessage.utils.Strings;
-import ch.dissem.bitmessage.utils.TTL;
-import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,34 +29,30 @@ import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
-public class JdbcMessageRepository extends JdbcHelper implements MessageRepository, InternalContext.ContextHolder {
+import static ch.dissem.bitmessage.repository.JdbcHelper.writeBlob;
+
+public class JdbcMessageRepository extends AbstractMessageRepository implements MessageRepository {
private static final Logger LOG = LoggerFactory.getLogger(JdbcMessageRepository.class);
- private InternalContext ctx;
+ private final JdbcConfig config;
public JdbcMessageRepository(JdbcConfig config) {
- super(config);
+ this.config = config;
}
@Override
- public List getLabels() {
- List result = new LinkedList<>();
+ protected List findLabels(String where) {
try (
- Connection connection = config.getConnection();
- Statement stmt = connection.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label ORDER BY ord")
+ Connection connection = config.getConnection()
) {
- while (rs.next()) {
- result.add(getLabel(rs));
- }
+ return findLabels(connection, where);
} catch (SQLException e) {
- throw new ApplicationException(e);
+ LOG.error(e.getMessage(), e);
}
- return result;
+ return new ArrayList<>();
}
private Label getLabel(ResultSet rs) throws SQLException {
@@ -75,24 +67,6 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
return label;
}
- @Override
- public List getLabels(Label.Type... types) {
- List result = new LinkedList<>();
- try (
- Connection connection = config.getConnection();
- Statement stmt = connection.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE type IN (" + join(types) +
- ") ORDER BY ord")
- ) {
- while (rs.next()) {
- result.add(getLabel(rs));
- }
- } catch (SQLException e) {
- LOG.error(e.getMessage(), e);
- }
- return result;
- }
-
@Override
public int countUnread(Label label) {
String where;
@@ -120,60 +94,7 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
}
@Override
- public Plaintext getMessage(Object id) {
- if (id instanceof Long) {
- List plaintexts = find("id=" + id);
- switch (plaintexts.size()) {
- case 0:
- return null;
- case 1:
- return plaintexts.get(0);
- default:
- throw new ApplicationException("This shouldn't happen, found " + plaintexts.size() +
- " messages, one or none was expected");
- }
- } else {
- throw new IllegalArgumentException("Long expected for ID");
- }
- }
-
- @Override
- public Plaintext getMessage(byte[] initialHash) {
- return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'"));
- }
-
- @Override
- public Plaintext getMessageForAck(byte[] ackData) {
- return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'"));
- }
-
- @Override
- public List findMessages(Label label) {
- return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")");
- }
-
- @Override
- public List findMessages(Plaintext.Status status, BitmessageAddress recipient) {
- return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'");
- }
-
- @Override
- public List findMessages(Plaintext.Status status) {
- return find("status='" + status.name() + "'");
- }
-
- @Override
- public List findMessages(BitmessageAddress sender) {
- return find("sender='" + sender.getAddress() + "'");
- }
-
- @Override
- public List findMessagesToResend() {
- return find("status='" + Plaintext.Status.SENT.name() + "'" +
- " AND next_try < " + UnixTime.now());
- }
-
- private List find(String where) {
+ protected List find(String where) {
List result = new LinkedList<>();
try (
Connection connection = config.getConnection();
@@ -199,7 +120,8 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
builder.ttl(rs.getLong("ttl"));
builder.retries(rs.getInt("retries"));
builder.nextTry(rs.getLong("next_try"));
- builder.labels(findLabels(connection, id));
+ builder.labels(findLabels(connection,
+ "WHERE id IN (SELECT label_id FROM Message_Label WHERE message_id=" + id + ") ORDER BY ord"));
Plaintext message = builder.build();
message.setInitialHash(rs.getBytes("initial_hash"));
result.add(message);
@@ -210,12 +132,11 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
return result;
}
- private Collection findLabels(Connection connection, long messageId) {
+ private List findLabels(Connection connection, String where) {
List result = new ArrayList<>();
try (
Statement stmt = connection.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label " +
- "WHERE id IN (SELECT label_id FROM Message_Label WHERE message_id=" + messageId + ")")
+ ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE " + where)
) {
while (rs.next()) {
result.add(getLabel(rs));
@@ -228,16 +149,7 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
@Override
public void save(Plaintext message) {
- // save from address if necessary
- if (message.getId() == null) {
- BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(message.getFrom().getAddress());
- if (savedAddress == null) {
- ctx.getAddressRepository().save(message.getFrom());
- } else if (savedAddress.getPubkey() == null && message.getFrom().getPubkey() != null) {
- savedAddress.setPubkey(message.getFrom().getPubkey());
- ctx.getAddressRepository().save(savedAddress);
- }
- }
+ safeSenderIfNecessary(message);
try (Connection connection = config.getConnection()) {
try {
@@ -350,9 +262,4 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
LOG.error(e.getMessage(), e);
}
}
-
- @Override
- public void setContext(InternalContext context) {
- this.ctx = context;
- }
}
From ed6344c6627ddd11a128aaa670f3364bdead5c47 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Mon, 23 May 2016 20:11:44 +0200
Subject: [PATCH 45/87] Added BitmessageContext method to resent unacknowledged
messages and updated README.md
---
README.md | 50 +++++++++++++------
.../dissem/bitmessage/BitmessageContext.java | 23 +++++++++
.../bitmessage/BitmessageContextTest.java | 2 +-
3 files changed, 60 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index 6c44558..fd9d955 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,10 @@ Jabit
[](https://raw.githubusercontent.com/Dissem/Jabit/master/LICENSE)
[](https://kiwiirc.com/client/irc.freenode.net/#jabit)
-A Java implementation for the Bitmessage protocol. To build, use command `gradle build` or `./gradlew build`.
+A Java implementation for the Bitmessage protocol. To build, use command
+`gradle build` or `./gradlew build`.
-Please note that it still has its limitations, but the API should now be stable. Jabit uses Semantic Versioning, meaning
-as long as the major version doesn't change, nothing should break if you update.
+Please note that it still has its limitations, but the API should now be stable. Jabit uses Semantic Versioning, meaning as long as the major version doesn't change, nothing should break if you update.
#### Master
[](https://travis-ci.org/Dissem/Jabit)
@@ -23,9 +23,7 @@ as long as the major version doesn't change, nothing should break if you update.
Security
--------
-There are most probably some security issues, me programming this thing all by myself. Jabit doesn't do anything against
-timing attacks yet, for example. Please feel free to use the library, report bugs and maybe even help out. I hope the
-code is easy to understand and work with.
+There are most probably some security issues, me programming this thing all by myself. Jabit doesn't do anything against timing attacks yet, for example. Please feel free to use the library, report bugs and maybe even help out. I hope the code is easy to understand and work with.
Project Status
--------------
@@ -74,17 +72,22 @@ BitmessageContext ctx = new BitmessageContext.Builder()
.nodeRegistry(new MemoryNodeRegistry())
.networkHandler(new NetworkNode())
.cryptography(new BouncyCryptography())
+ .listener(System.out::println)
.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
-start the context and decide what happens if a message arrives:
+This creates a simple context using a H2 database that will be created in the user's home directory. In the listener you decide what happens when a message arrives. If you can't use lambdas, you may instead write
```Java
-ctx.startup(new BitmessageContext.Listener() {
- @Override
- public void receive(Plaintext plaintext) {
- // TODO: Notify the user
- }
-});
+ .listener(new BitmessageContext.Listener() {
+ @Override
+ public void receive(Plaintext plaintext) {
+ // TODO: Notify the user
+ }
+ })
+```
+
+Next you'll need to start the context:
+```Java
+ctx.startup()
```
Then you might want to create an identity
```Java
@@ -100,3 +103,22 @@ to which you can send some messages
```Java
ctx.send(identity, contact, "Test", "Hello Chris, this is a message.");
```
+
+### Housekeeping
+
+As Bitmessage stores all currently valid messages, we'll need to delete expired objects from time to time:
+```Java
+ctx.cleanup();
+```
+If the client runs all the time, it might be a good idea to do this daily or at least weekly. Otherwise, you might just want to clean up on shutdown.
+
+Also, if some messages weren't acknowledged when it expired, they can be resent:
+```Java
+ctx.resendUnacknowledgedMessages();
+```
+This could be triggered periodically, or manually by the user. Please be aware that _if_ there is a message to resend, proof of work needs to be calculated, so to not annoy your users you might not want to trigger it on shutdown. As the client might have been offline for some time, it might as well be wise to wait until it caught up downloading new messages before resending those messages, after all they might be acknowledged by now.
+
+There probably won't happen extremely bad things if you don't - at least not more than otherwise - but you can properly shutdown the network connection by calling
+```Java
+ctx.shutdown();
+```
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index ad21e72..032431d 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -221,10 +221,33 @@ public class BitmessageContext {
return ctx.getNetworkHandler().send(server, port, request);
}
+ /**
+ * Removes expired objects from the inventory. You should call this method regularly,
+ * e.g. daily and on each shutdown.
+ */
public void cleanup() {
ctx.getInventory().cleanup();
}
+ /**
+ * Sends messages again whose time to live expired without being acknowledged. (And whose
+ * recipient is expected to send acknowledgements.
+ *
+ * You should call this method regularly, but be aware of the following:
+ *
+ *
As messages might be sent, POW will be done. It is therefore not advised to
+ * call it on shutdown.
+ *
It shouldn't be called right after startup, as it's possible the missing
+ * acknowledgement was sent while the client was offline.
+ *
Other than that, the call isn't expensive as long as there is no message
+ * to send, so it might be a good idea to just call it every few minutes.
+ *
+ *
+ */
+ public void resendUnacknowledgedMessages() {
+ ctx.resendUnacknowledged();
+ }
+
public boolean isRunning() {
return ctx.getNetworkHandler().isRunning();
}
diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
index 5ad91cb..813ff20 100644
--- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
+++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java
@@ -309,7 +309,7 @@ public class BitmessageContextTest {
assertTrue(plaintext.getTo().has(Pubkey.Feature.DOES_ACK));
when(ctx.messages().findMessagesToResend()).thenReturn(Collections.singletonList(plaintext));
when(ctx.messages().getMessage(any(byte[].class))).thenReturn(plaintext);
- ctx.internals().resendUnacknowledged();
+ ctx.resendUnacknowledgedMessages();
verify(ctx.labeler(), timeout(1000).times(1)).markAsSent(eq(plaintext));
}
}
From 409dccd0beba0215ee899365a10edd0bed517cdd Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Tue, 24 May 2016 07:45:34 +0200
Subject: [PATCH 46/87] Fixed broken JavaDoc and removed unused import
---
.../dissem/bitmessage/BitmessageContext.java | 19 +++++++++----------
.../entity/payload/ObjectPayload.java | 1 -
2 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 032431d..1b9b402 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -233,16 +233,15 @@ public class BitmessageContext {
* Sends messages again whose time to live expired without being acknowledged. (And whose
* recipient is expected to send acknowledgements.
*
- * You should call this method regularly, but be aware of the following:
- *
- *
As messages might be sent, POW will be done. It is therefore not advised to
- * call it on shutdown.
- *
It shouldn't be called right after startup, as it's possible the missing
- * acknowledgement was sent while the client was offline.
- *
Other than that, the call isn't expensive as long as there is no message
- * to send, so it might be a good idea to just call it every few minutes.
- *
- *
+ * You should call this method regularly, but be aware of the following:
+ *
+ *
As messages might be sent, POW will be done. It is therefore not advised to
+ * call it on shutdown.
+ *
It shouldn't be called right after startup, as it's possible the missing
+ * acknowledgement was sent while the client was offline.
+ *
Other than that, the call isn't expensive as long as there is no message
+ * to send, so it might be a good idea to just call it every few minutes.
+ *
*/
public void resendUnacknowledgedMessages() {
ctx.resendUnacknowledged();
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
index a7dbaff..33da28d 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java
@@ -18,7 +18,6 @@ package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Streamable;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.io.IOException;
import java.io.OutputStream;
From 725d2b848e018cc8995feccf4a95c18bb5510f14 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Tue, 24 May 2016 17:19:29 +0200
Subject: [PATCH 47/87] Fixed migration and added resend and cleanup options to
demo application
---
.../dissem/bitmessage/BitmessageContext.java | 3 ++-
.../dissem/bitmessage/demo/Application.java | 23 +++++++++++++++++--
.../migration/V3.2__Update_table_message.sql | 2 +-
3 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 1b9b402..b06cf9a 100644
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -281,7 +281,8 @@ public class BitmessageContext {
public Property status() {
return new Property("status", null,
- ctx.getNetworkHandler().getNetworkStatus()
+ ctx.getNetworkHandler().getNetworkStatus(),
+ new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size())
);
}
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 89b91dc..583669b 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
@@ -118,8 +118,27 @@ public class Application {
}
private void info() {
- System.out.println();
- System.out.println(ctx.status());
+ String command;
+ do {
+ System.out.println();
+ System.out.println(ctx.status());
+ System.out.println();
+ System.out.println("c) cleanup inventory");
+ System.out.println("r) resend unacknowledged messages");
+ System.out.println(COMMAND_BACK);
+
+ command = commandLine.nextCommand();
+ switch (command) {
+ case "c":
+ ctx.cleanup();
+ break;
+ case "r":
+ ctx.resendUnacknowledgedMessages();
+ break;
+ case "b":
+ return;
+ }
+ } while (!"b".equals(command));
}
private void identities() {
diff --git a/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql b/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql
index 38847cf..1eba39f 100644
--- a/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql
+++ b/repositories/src/main/resources/db/migration/V3.2__Update_table_message.sql
@@ -1,4 +1,4 @@
ALTER TABLE Message ADD COLUMN ack_data BINARY(32);
-ALTER TABLE Message ADD COLUMN ttl BIGINT NOT NULL;
+ALTER TABLE Message ADD COLUMN ttl BIGINT NOT NULL DEFAULT 0;
ALTER TABLE Message ADD COLUMN retries INT NOT NULL DEFAULT 0;
ALTER TABLE Message ADD COLUMN next_try BIGINT;
From 22108527f3083696328ec7c00355d078470ca104 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Tue, 24 May 2016 19:35:41 +0200
Subject: [PATCH 48/87] Minor update to the README file
---
README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index fd9d955..16a2528 100644
--- a/README.md
+++ b/README.md
@@ -5,11 +5,12 @@ Jabit
[](https://raw.githubusercontent.com/Dissem/Jabit/master/LICENSE)
[](https://kiwiirc.com/client/irc.freenode.net/#jabit)
-A Java implementation for the Bitmessage protocol. To build, use command
-`gradle build` or `./gradlew build`.
+A Java implementation for the Bitmessage protocol. To build, use command `./gradlew build`.
Please note that it still has its limitations, but the API should now be stable. Jabit uses Semantic Versioning, meaning as long as the major version doesn't change, nothing should break if you update.
+Be aware though that this doesn't necessarily applies for SNAPSHOT builds and the development branch, notably when it comes to database updates. _In other words, they may break your installation!_
+
#### Master
[](https://travis-ci.org/Dissem/Jabit)
[](https://www.codacy.com/app/chrigu-meyer/Jabit/dashboard?bid=3144281)
From 5c4892d153fbeb9ab10e8b2da19dec3783e3e784 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Thu, 26 May 2016 06:55:31 +0200
Subject: [PATCH 49/87] Added test
---
.../networking/NetworkHandlerTest.java | 76 ++++++++++++++++++-
1 file changed, 75 insertions(+), 1 deletion(-)
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
index accb4c5..bffc220 100644
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -17,21 +17,31 @@
package ch.dissem.bitmessage.networking;
import ch.dissem.bitmessage.BitmessageContext;
+import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
+import ch.dissem.bitmessage.factory.Factory;
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 java.io.InputStream;
+import java.io.OutputStream;
import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
import java.util.concurrent.Future;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
+import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
/**
@@ -108,6 +118,31 @@ public class NetworkHandlerTest {
}
}
+ @Test
+ public void ensureCustomMessageIsSentAndResponseRetrieved() throws Exception {
+ final CustomMessage request = new CustomMessage("test request", cryptography().randomBytes(8));
+ try (TestServer server = new TestServer(new TestServer.ConnectionTester() {
+ @Override
+ public void test(InputStream in, OutputStream out) throws Exception {
+ NetworkMessage message = Factory.getNetworkMessage(3, in);
+ assertThat(message, notNullValue());
+ assertThat(message.getPayload(), instanceOf(CustomMessage.class));
+ CustomMessage payload = (CustomMessage) message.getPayload();
+ assertThat(payload.getCustomCommand(), is("test request"));
+ assertThat(payload.getData(), is(request.getData()));
+ new NetworkMessage(new CustomMessage("test response", payload.getData())).write(out);
+ }
+ })) {
+ CustomMessage response = networkHandler.send(InetAddress.getLocalHost(), server.getPort(), request);
+ assertThat(response, notNullValue());
+ assertThat(response.getCustomCommand(), is("test response"));
+ assertThat(response.getData(), is(request.getData()));
+ if (server.getException() != null) {
+ throw server.getException();
+ }
+ }
+ }
+
@Test(timeout = 5_000)
public void ensureObjectsAreSynchronizedIfBothHaveObjects() throws Exception {
peerInventory.init(
@@ -168,4 +203,43 @@ public class NetworkHandlerTest {
}
assertEquals(expected, inventory.getInventory().size());
}
+
+ private static class TestServer implements AutoCloseable {
+ private final ServerSocket socket;
+ private Exception exception;
+
+ private TestServer(final ConnectionTester tester) throws Exception {
+ socket = new ServerSocket(0);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!socket.isClosed()) {
+ try {
+ Socket connection = socket.accept();
+ tester.test(connection.getInputStream(), connection.getOutputStream());
+ } catch (Exception e) {
+ exception = e;
+ }
+ }
+ }
+ }).start();
+ }
+
+ int getPort() {
+ return socket.getLocalPort();
+ }
+
+ public Exception getException() {
+ return exception;
+ }
+
+ @Override
+ public void close() throws Exception {
+ socket.close();
+ }
+
+ private interface ConnectionTester {
+ void test(InputStream in, OutputStream out) throws Exception;
+ }
+ }
}
From b8f88b02d1fe228df1006991554c6017d49bdf6f Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Thu, 26 May 2016 22:50:37 +0200
Subject: [PATCH 50/87] Improved tests
---
.../networking/NetworkHandlerTest.java | 116 ++++++++----------
1 file changed, 49 insertions(+), 67 deletions(-)
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
index bffc220..6e4f8d9 100644
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -19,27 +19,21 @@ package ch.dissem.bitmessage.networking;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
import ch.dissem.bitmessage.entity.CustomMessage;
-import ch.dissem.bitmessage.entity.NetworkMessage;
+import ch.dissem.bitmessage.entity.MessagePayload;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
-import ch.dissem.bitmessage.factory.Factory;
-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.exception.NodeException;
+import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Property;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
import java.util.concurrent.Future;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
@@ -85,6 +79,23 @@ public class NetworkHandlerTest {
.networkHandler(networkHandler)
.cryptography(new BouncyCryptography())
.listener(mock(BitmessageContext.Listener.class))
+ .customCommandHandler(new CustomCommandHandler() {
+ @Override
+ public MessagePayload handle(CustomMessage request) {
+ byte[] data = request.getData();
+ if (data.length > 0) {
+ switch (data[0]) {
+ case 0:
+ return null;
+ case 1:
+ break;
+ case 3:
+ data[0] = 0;
+ }
+ }
+ return new CustomMessage("test response", request.getData());
+ }
+ })
.build();
}
@@ -118,28 +129,37 @@ public class NetworkHandlerTest {
}
}
- @Test
+ @Test(timeout = 5_000)
public void ensureCustomMessageIsSentAndResponseRetrieved() throws Exception {
- final CustomMessage request = new CustomMessage("test request", cryptography().randomBytes(8));
- try (TestServer server = new TestServer(new TestServer.ConnectionTester() {
- @Override
- public void test(InputStream in, OutputStream out) throws Exception {
- NetworkMessage message = Factory.getNetworkMessage(3, in);
- assertThat(message, notNullValue());
- assertThat(message.getPayload(), instanceOf(CustomMessage.class));
- CustomMessage payload = (CustomMessage) message.getPayload();
- assertThat(payload.getCustomCommand(), is("test request"));
- assertThat(payload.getData(), is(request.getData()));
- new NetworkMessage(new CustomMessage("test response", payload.getData())).write(out);
- }
- })) {
- CustomMessage response = networkHandler.send(InetAddress.getLocalHost(), server.getPort(), request);
+ byte[] data = cryptography().randomBytes(8);
+ data[0] = (byte) 1;
+ CustomMessage request = new CustomMessage("test request", data);
+ node.startup();
+
+ CustomMessage response = networkHandler.send(InetAddress.getLocalHost(), 6002, request);
+
+ assertThat(response, notNullValue());
+ assertThat(response.getCustomCommand(), is("test response"));
+ assertThat(response.getData(), is(data));
+
+ shutdown(node);
+ }
+
+ @Test(timeout = 5_000, expected = NodeException.class)
+ public void ensureCustomMessageWithoutResponsYieldsException() throws Exception {
+ try {
+ byte[] data = cryptography().randomBytes(8);
+ data[0] = (byte) 0;
+ CustomMessage request = new CustomMessage("test request", data);
+ node.startup();
+
+ CustomMessage response = networkHandler.send(InetAddress.getLocalHost(), 6002, request);
+
assertThat(response, notNullValue());
assertThat(response.getCustomCommand(), is("test response"));
assertThat(response.getData(), is(request.getData()));
- if (server.getException() != null) {
- throw server.getException();
- }
+ } finally {
+ shutdown(node);
}
}
@@ -204,42 +224,4 @@ public class NetworkHandlerTest {
assertEquals(expected, inventory.getInventory().size());
}
- private static class TestServer implements AutoCloseable {
- private final ServerSocket socket;
- private Exception exception;
-
- private TestServer(final ConnectionTester tester) throws Exception {
- socket = new ServerSocket(0);
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (!socket.isClosed()) {
- try {
- Socket connection = socket.accept();
- tester.test(connection.getInputStream(), connection.getOutputStream());
- } catch (Exception e) {
- exception = e;
- }
- }
- }
- }).start();
- }
-
- int getPort() {
- return socket.getLocalPort();
- }
-
- public Exception getException() {
- return exception;
- }
-
- @Override
- public void close() throws Exception {
- socket.close();
- }
-
- private interface ConnectionTester {
- void test(InputStream in, OutputStream out) throws Exception;
- }
- }
}
From 08f2d5d6f112cccc997d43aeae798eea397ec61e Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Sat, 28 May 2016 10:22:47 +0200
Subject: [PATCH 51/87] Added write(ByteBuffer) to Streamable interface and a
first draft for a NioNetworkHandler
---
.../ch/dissem/bitmessage/entity/Addr.java | 15 +-
.../bitmessage/entity/CustomMessage.java | 12 ++
.../ch/dissem/bitmessage/entity/GetData.java | 9 ++
.../java/ch/dissem/bitmessage/entity/Inv.java | 9 ++
.../bitmessage/entity/NetworkMessage.java | 39 ++++-
.../bitmessage/entity/ObjectMessage.java | 11 ++
.../dissem/bitmessage/entity/Plaintext.java | 38 +++++
.../dissem/bitmessage/entity/Streamable.java | 3 +
.../ch/dissem/bitmessage/entity/VerAck.java | 6 +
.../ch/dissem/bitmessage/entity/Version.java | 13 ++
.../bitmessage/entity/payload/CryptoBox.java | 18 +++
.../entity/payload/GenericPayload.java | 6 +
.../bitmessage/entity/payload/GetPubkey.java | 6 +
.../dissem/bitmessage/entity/payload/Msg.java | 7 +
.../bitmessage/entity/payload/Pubkey.java | 5 +
.../bitmessage/entity/payload/V2Pubkey.java | 16 +-
.../entity/payload/V4Broadcast.java | 6 +
.../bitmessage/entity/payload/V4Pubkey.java | 12 ++
.../entity/valueobject/InventoryVector.java | 10 +-
.../entity/valueobject/NetworkAddress.java | 28 +++-
.../entity/valueobject/PrivateKey.java | 47 ++++--
.../bitmessage/ports/NetworkHandler.java | 2 +
.../ch/dissem/bitmessage/utils/Encode.java | 115 +++++++++-----
.../extensions/pow/ProofOfWorkRequest.java | 8 +
.../networking/DefaultNetworkHandler.java | 1 -
.../networking/nio/ConnectionInfo.java | 51 ++++++
.../networking/nio/NioNetworkHandler.java | 147 ++++++++++++++++++
27 files changed, 561 insertions(+), 79 deletions(-)
create mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java
create mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java
index 6125910..73d9995 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java
@@ -21,6 +21,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -47,10 +48,18 @@ public class Addr implements MessagePayload {
}
@Override
- public void write(OutputStream stream) throws IOException {
- Encode.varInt(addresses.size(), stream);
+ public void write(OutputStream out) throws IOException {
+ Encode.varInt(addresses.size(), out);
for (NetworkAddress address : addresses) {
- address.write(stream);
+ address.write(out);
+ }
+ }
+
+ @Override
+ public void write(ByteBuffer buffer) {
+ Encode.varInt(addresses.size(), buffer);
+ for (NetworkAddress address : addresses) {
+ address.write(buffer);
}
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
index e43f56d..439f003 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -21,6 +21,7 @@ import ch.dissem.bitmessage.utils.AccessCounter;
import ch.dissem.bitmessage.utils.Encode;
import java.io.*;
+import java.nio.ByteBuffer;
import static ch.dissem.bitmessage.utils.Decode.bytes;
import static ch.dissem.bitmessage.utils.Decode.varString;
@@ -85,6 +86,17 @@ public class CustomMessage implements MessagePayload {
}
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ if (data != null) {
+ Encode.varString(command, buffer);
+ buffer.put(data);
+ } else {
+ throw new ApplicationException("Tried to write custom message without data. " +
+ "Programmer: did you forget to override #write()?");
+ }
+ }
+
public boolean isError() {
return COMMAND_ERROR.equals(command);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
index 44fab5b..7d14fa0 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
@@ -21,6 +21,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
@@ -55,6 +56,14 @@ public class GetData implements MessagePayload {
}
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ Encode.varInt(inventory.size(), buffer);
+ for (InventoryVector iv : inventory) {
+ iv.write(buffer);
+ }
+ }
+
public static final class Builder {
private List inventory = new LinkedList<>();
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
index fd2d40d..8d0f592 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
@@ -21,6 +21,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
@@ -53,6 +54,14 @@ public class Inv implements MessagePayload {
}
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ Encode.varInt(inventory.size(), buffer);
+ for (InventoryVector iv : inventory) {
+ iv.write(buffer);
+ }
+ }
+
public static final class Builder {
private List inventory = new LinkedList<>();
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
index 860c6ed..e549101 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
@@ -22,6 +22,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
@@ -74,9 +75,7 @@ public class NetworkMessage implements Streamable {
out.write('\0');
}
- ByteArrayOutputStream payloadStream = new ByteArrayOutputStream();
- payload.write(payloadStream);
- byte[] payloadBytes = payloadStream.toByteArray();
+ byte[] payloadBytes = Encode.bytes(payload);
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
@@ -93,4 +92,38 @@ public class NetworkMessage implements Streamable {
// message payload
out.write(payloadBytes);
}
+
+ @Override
+ public void write(ByteBuffer out) {
+ // magic
+ Encode.int32(MAGIC, out);
+
+ // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
+ String command = payload.getCommand().name().toLowerCase();
+ try {
+ out.put(command.getBytes("ASCII"));
+ } catch (UnsupportedEncodingException e) {
+ throw new ApplicationException(e);
+ }
+ for (int i = command.length(); i < 12; i++) {
+ out.put((byte) 0);
+ }
+
+ byte[] payloadBytes = Encode.bytes(payload);
+
+ // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
+ // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
+ // larger than this.
+ Encode.int32(payloadBytes.length, out);
+
+ // checksum
+ try {
+ out.put(getChecksum(payloadBytes));
+ } catch (GeneralSecurityException e) {
+ throw new ApplicationException(e);
+ }
+
+ // message payload
+ out.put(payloadBytes);
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
index 6f74257..fee761c 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
@@ -29,6 +29,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
@@ -168,6 +169,16 @@ public class ObjectMessage implements MessagePayload {
out.write(getPayloadBytesWithoutNonce());
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ if (nonce == null) {
+ buffer.put(new byte[8]);
+ } else {
+ buffer.put(nonce);
+ }
+ buffer.put(getPayloadBytesWithoutNonce());
+ }
+
private void writeHeaderWithoutNonce(OutputStream out) throws IOException {
Encode.int64(expiresTime, out);
Encode.int32(objectType, out);
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index 4bfaba4..242eb95 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -25,6 +25,7 @@ import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.*;
import java.io.*;
+import java.nio.ByteBuffer;
import java.util.*;
import java.util.Collections;
@@ -197,12 +198,49 @@ public class Plaintext implements Streamable {
}
}
}
+ public void write(ByteBuffer buffer, boolean includeSignature) {
+ Encode.varInt(from.getVersion(), buffer);
+ Encode.varInt(from.getStream(), buffer);
+ Encode.int32(from.getPubkey().getBehaviorBitfield(), buffer);
+ buffer.put(from.getPubkey().getSigningKey(), 1, 64);
+ buffer.put(from.getPubkey().getEncryptionKey(), 1, 64);
+ if (from.getVersion() >= 3) {
+ Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), buffer);
+ Encode.varInt(from.getPubkey().getExtraBytes(), buffer);
+ }
+ if (type == Type.MSG) {
+ buffer.put(to.getRipe());
+ }
+ Encode.varInt(encoding, buffer);
+ Encode.varInt(message.length, buffer);
+ buffer.put(message);
+ if (type == Type.MSG) {
+ if (to.has(Feature.DOES_ACK) && getAckMessage() != null) {
+ Encode.varBytes(Encode.bytes(getAckMessage()), buffer);
+ } else {
+ Encode.varInt(0, buffer);
+ }
+ }
+ if (includeSignature) {
+ if (signature == null) {
+ Encode.varInt(0, buffer);
+ } else {
+ Encode.varInt(signature.length, buffer);
+ buffer.put(signature);
+ }
+ }
+ }
@Override
public void write(OutputStream out) throws IOException {
write(out, true);
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ write(buffer, true);
+ }
+
public Object getId() {
return id;
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
index cc12050..e75a926 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java
@@ -19,10 +19,13 @@ package ch.dissem.bitmessage.entity;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
+import java.nio.ByteBuffer;
/**
* An object that can be written to an {@link OutputStream}
*/
public interface Streamable extends Serializable {
void write(OutputStream stream) throws IOException;
+
+ void write(ByteBuffer buffer);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java b/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java
index 815a20f..3d30f32 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java
@@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
/**
* The 'verack' command answers a 'version' command, accepting the other node's version.
@@ -34,4 +35,9 @@ public class VerAck implements MessagePayload {
public void write(OutputStream stream) throws IOException {
// 'verack' doesn't have any payload, so there is nothing to write
}
+
+ @Override
+ public void write(ByteBuffer buffer) {
+ // 'verack' doesn't have any payload, so there is nothing to write
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java b/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
index 8539be1..48022c4 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
@@ -23,6 +23,7 @@ import ch.dissem.bitmessage.utils.UnixTime;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.Random;
/**
@@ -134,6 +135,18 @@ public class Version implements MessagePayload {
Encode.varIntList(streams, stream);
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ Encode.int32(version, buffer);
+ Encode.int64(services, buffer);
+ Encode.int64(timestamp, buffer);
+ addrRecv.write(buffer, true);
+ addrFrom.write(buffer, true);
+ Encode.int64(nonce, buffer);
+ Encode.varString(userAgent, buffer);
+ Encode.varIntList(streams, buffer);
+ }
+
public static final class Builder {
private int version;
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
index 89d7bd7..f7f3c15 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
@@ -24,6 +24,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
@@ -144,12 +145,29 @@ public class CryptoBox implements Streamable {
out.write(x, offset, length);
}
+ private void writeCoordinateComponent(ByteBuffer buffer, byte[] x) {
+ int offset = Bytes.numberOfLeadingZeros(x);
+ int length = x.length - offset;
+ Encode.int16(length, buffer);
+ buffer.put(x, offset, length);
+ }
+
@Override
public void write(OutputStream stream) throws IOException {
writeWithoutMAC(stream);
stream.write(mac);
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ buffer.put(initializationVector);
+ Encode.int16(curveType, buffer);
+ writeCoordinateComponent(buffer, Points.getX(R));
+ writeCoordinateComponent(buffer, Points.getY(R));
+ buffer.put(encrypted);
+ buffer.put(mac);
+ }
+
public static final class Builder {
private byte[] initializationVector;
private int curveType;
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
index 176d938..9312160 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java
@@ -21,6 +21,7 @@ import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.Arrays;
/**
@@ -62,6 +63,11 @@ public class GenericPayload extends ObjectPayload {
stream.write(data);
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ buffer.put(data);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java
index 06e623a..d889489 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java
@@ -22,6 +22,7 @@ import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
/**
* Request for a public key.
@@ -73,4 +74,9 @@ public class GetPubkey extends ObjectPayload {
public void write(OutputStream stream) throws IOException {
stream.write(ripeTag);
}
+
+ @Override
+ public void write(ByteBuffer buffer) {
+ buffer.put(ripeTag);
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
index 64a010c..dc36bb1 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
@@ -24,6 +24,7 @@ import ch.dissem.bitmessage.exception.DecryptionFailedException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.Objects;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
@@ -111,6 +112,12 @@ public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
encrypted.write(out);
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it.");
+ encrypted.write(buffer);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
index c476bf9..27d2da9 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java
@@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
@@ -60,6 +61,10 @@ public abstract class Pubkey extends ObjectPayload {
write(out);
}
+ public void writeUnencrypted(ByteBuffer buffer){
+ write(buffer);
+ }
+
protected byte[] add0x04(byte[] key) {
if (key.length == 65) return key;
byte[] result = new byte[65];
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java
index 0eceb91..d2901c1 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java
@@ -22,6 +22,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
/**
* A version 2 public key.
@@ -86,10 +87,17 @@ public class V2Pubkey extends Pubkey {
}
@Override
- public void write(OutputStream os) throws IOException {
- Encode.int32(behaviorBitfield, os);
- os.write(publicSigningKey, 1, 64);
- os.write(publicEncryptionKey, 1, 64);
+ public void write(OutputStream out) throws IOException {
+ Encode.int32(behaviorBitfield, out);
+ out.write(publicSigningKey, 1, 64);
+ out.write(publicEncryptionKey, 1, 64);
+ }
+
+ @Override
+ public void write(ByteBuffer buffer) {
+ Encode.int32(behaviorBitfield, buffer);
+ buffer.put(publicSigningKey, 1, 64);
+ buffer.put(publicEncryptionKey, 1, 64);
}
public static class Builder {
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java
index 7781455..323da33 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java
@@ -22,6 +22,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
@@ -58,4 +59,9 @@ public class V4Broadcast extends Broadcast {
public void write(OutputStream out) throws IOException {
encrypted.write(out);
}
+
+ @Override
+ public void write(ByteBuffer buffer) {
+ encrypted.write(buffer);
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java
index 8aa0ee4..179a475 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java
@@ -24,6 +24,7 @@ import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.Arrays;
/**
@@ -85,11 +86,22 @@ public class V4Pubkey extends Pubkey implements Encrypted {
encrypted.write(stream);
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ buffer.put(tag);
+ encrypted.write(buffer);
+ }
+
@Override
public void writeUnencrypted(OutputStream out) throws IOException {
decrypted.write(out);
}
+ @Override
+ public void writeUnencrypted(ByteBuffer buffer) {
+ decrypted.write(buffer);
+ }
+
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
out.write(tag);
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
index 127b90a..9a3b258 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java
@@ -22,6 +22,7 @@ import ch.dissem.bitmessage.utils.Strings;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
+import java.nio.ByteBuffer;
import java.util.Arrays;
public class InventoryVector implements Streamable, Serializable {
@@ -56,8 +57,13 @@ public class InventoryVector implements Streamable, Serializable {
}
@Override
- public void write(OutputStream stream) throws IOException {
- stream.write(hash);
+ public void write(OutputStream out) throws IOException {
+ out.write(hash);
+ }
+
+ @Override
+ public void write(ByteBuffer buffer) {
+ buffer.put(hash);
}
@Override
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java
index d0324a4..0fef152 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java
@@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
import java.util.Arrays;
/**
@@ -119,14 +120,29 @@ public class NetworkAddress implements Streamable {
write(stream, false);
}
- public void write(OutputStream stream, boolean light) throws IOException {
+ public void write(OutputStream out, boolean light) throws IOException {
if (!light) {
- Encode.int64(time, stream);
- Encode.int32(this.stream, stream);
+ Encode.int64(time, out);
+ Encode.int32(stream, out);
}
- Encode.int64(services, stream);
- stream.write(ipv6);
- Encode.int16(port, stream);
+ Encode.int64(services, out);
+ out.write(ipv6);
+ Encode.int16(port, out);
+ }
+
+ @Override
+ public void write(ByteBuffer buffer) {
+ write(buffer, false);
+ }
+
+ public void write(ByteBuffer buffer, boolean light) {
+ if (!light) {
+ Encode.int64(time, buffer);
+ Encode.int32(stream, buffer);
+ }
+ Encode.int64(services, buffer);
+ buffer.put(ipv6);
+ Encode.int16(port, buffer);
}
public static final class Builder {
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
index 716afc6..7621ca5 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java
@@ -27,6 +27,7 @@ import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import java.io.*;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -113,24 +114,20 @@ public class PrivateKey implements Streamable {
}
Builder generate() {
- try {
- long signingKeyNonce = nextNonce;
- long encryptionKeyNonce = nextNonce + 1;
- byte[] ripe;
- do {
- privEK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32);
- privSK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(signingKeyNonce)), 32);
- pubSK = cryptography().createPublicKey(privSK);
- pubEK = cryptography().createPublicKey(privEK);
- ripe = cryptography().ripemd160(cryptography().sha512(pubSK, pubEK));
+ long signingKeyNonce = nextNonce;
+ long encryptionKeyNonce = nextNonce + 1;
+ byte[] ripe;
+ do {
+ privEK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32);
+ privSK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(signingKeyNonce)), 32);
+ pubSK = cryptography().createPublicKey(privSK);
+ pubEK = cryptography().createPublicKey(privEK);
+ ripe = cryptography().ripemd160(cryptography().sha512(pubSK, pubEK));
- signingKeyNonce += 2;
- encryptionKeyNonce += 2;
- } while (ripe[0] != 0 || (shorter && ripe[1] != 0));
- nextNonce = signingKeyNonce;
- } catch (IOException e) {
- throw new ApplicationException(e);
- }
+ signingKeyNonce += 2;
+ encryptionKeyNonce += 2;
+ } while (ripe[0] != 0 || (shorter && ripe[1] != 0));
+ nextNonce = signingKeyNonce;
return this;
}
}
@@ -182,4 +179,20 @@ public class PrivateKey implements Streamable {
Encode.varInt(privateEncryptionKey.length, out);
out.write(privateEncryptionKey);
}
+
+
+ @Override
+ public void write(ByteBuffer buffer) {
+ Encode.varInt(pubkey.getVersion(), buffer);
+ Encode.varInt(pubkey.getStream(), buffer);
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ pubkey.writeUnencrypted(baos);
+ Encode.varBytes(baos.toByteArray(), buffer);
+ } catch (IOException e) {
+ throw new ApplicationException(e);
+ }
+ Encode.varBytes(privateSigningKey, buffer);
+ Encode.varBytes(privateEncryptionKey, buffer);
+ }
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
index 909d3dd..bfbb48c 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
@@ -29,6 +29,8 @@ import java.util.concurrent.Future;
* Handles incoming messages
*/
public interface NetworkHandler {
+ int NETWORK_MAGIC_NUMBER = 8;
+
/**
* Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
*
diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java
index f5eac43..a60c027 100644
--- a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java
@@ -17,10 +17,13 @@
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.Streamable;
+import ch.dissem.bitmessage.exception.ApplicationException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.Buffer;
import java.nio.ByteBuffer;
import static ch.dissem.bitmessage.utils.AccessCounter.inc;
@@ -37,62 +40,52 @@ public class Encode {
}
}
+ public static void varIntList(long[] values, ByteBuffer buffer) {
+ varInt(values.length, buffer);
+ for (long value : values) {
+ varInt(value, buffer);
+ }
+ }
+
public static void varInt(long value, OutputStream stream) throws IOException {
varInt(value, stream, null);
}
- public static byte[] varInt(long value) throws IOException {
- final byte[] result;
+ public static void varInt(long value, ByteBuffer buffer) {
if (value < 0) {
// This is due to the fact that Java doesn't really support unsigned values.
// Please be aware that this might be an error due to a smaller negative value being cast to long.
- // Normally, negative values shouldn't occur within the protocol, and I large enough longs
- // to being recognized as negatives aren't realistic.
- ByteBuffer buffer = ByteBuffer.allocate(9);
+ // Normally, negative values shouldn't occur within the protocol, and longs large enough for being
+ // recognized as negatives aren't realistic.
buffer.put((byte) 0xff);
- result = buffer.putLong(value).array();
+ buffer.putLong(value);
} else if (value < 0xfd) {
- result = new byte[]{(byte) value};
+ buffer.put((byte) value);
} else if (value <= 0xffffL) {
- ByteBuffer buffer = ByteBuffer.allocate(3);
buffer.put((byte) 0xfd);
- result = buffer.putShort((short) value).array();
+ buffer.putShort((short) value);
} else if (value <= 0xffffffffL) {
- ByteBuffer buffer = ByteBuffer.allocate(5);
buffer.put((byte) 0xfe);
- result = buffer.putInt((int) value).array();
+ buffer.putInt((int) value);
} else {
- ByteBuffer buffer = ByteBuffer.allocate(9);
buffer.put((byte) 0xff);
- result = buffer.putLong(value).array();
+ buffer.putLong(value);
}
- return result;
+ }
+
+ public static byte[] varInt(long value) {
+ ByteBuffer buffer = ByteBuffer.allocate(9);
+ varInt(value, buffer);
+ buffer.flip();
+ return Bytes.truncate(buffer.array(), buffer.limit());
}
public static void varInt(long value, OutputStream stream, AccessCounter counter) throws IOException {
- if (value < 0) {
- // This is due to the fact that Java doesn't really support unsigned values.
- // Please be aware that this might be an error due to a smaller negative value being cast to long.
- // Normally, negative values shouldn't occur within the protocol, and I large enough longs
- // to being recognized as negatives aren't realistic.
- stream.write(0xff);
- inc(counter);
- int64(value, stream, counter);
- } else if (value < 0xfd) {
- int8(value, stream, counter);
- } else if (value <= 0xffffL) {
- stream.write(0xfd);
- inc(counter);
- int16(value, stream, counter);
- } else if (value <= 0xffffffffL) {
- stream.write(0xfe);
- inc(counter);
- int32(value, stream, counter);
- } else {
- stream.write(0xff);
- inc(counter);
- int64(value, stream, counter);
- }
+ ByteBuffer buffer = ByteBuffer.allocate(9);
+ varInt(value, buffer);
+ buffer.flip();
+ stream.write(buffer.array(), 0, buffer.limit());
+ inc(counter, buffer.limit());
}
public static void int8(long value, OutputStream stream) throws IOException {
@@ -113,6 +106,10 @@ public class Encode {
inc(counter, 2);
}
+ public static void int16(long value, ByteBuffer buffer) {
+ buffer.putShort((short) value);
+ }
+
public static void int32(long value, OutputStream stream) throws IOException {
int32(value, stream, null);
}
@@ -122,6 +119,10 @@ public class Encode {
inc(counter, 4);
}
+ public static void int32(long value, ByteBuffer buffer) {
+ buffer.putInt((int) value);
+ }
+
public static void int64(long value, OutputStream stream) throws IOException {
int64(value, stream, null);
}
@@ -131,6 +132,10 @@ public class Encode {
inc(counter, 8);
}
+ public static void int64(long value, ByteBuffer buffer) {
+ buffer.putLong(value);
+ }
+
public static void varString(String value, OutputStream out) throws IOException {
byte[] bytes = value.getBytes("utf-8");
// Technically, it says the length in characters, but I think this one might be correct.
@@ -140,23 +145,44 @@ public class Encode {
out.write(bytes);
}
+ public static void varString(String value, ByteBuffer buffer) {
+ try {
+ byte[] bytes = value.getBytes("utf-8");
+ // 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()
+ buffer.put(varInt(bytes.length));
+ buffer.put(bytes);
+ } catch (UnsupportedEncodingException e) {
+ throw new ApplicationException(e);
+ }
+ }
+
public static void varBytes(byte[] data, OutputStream out) throws IOException {
varInt(data.length, out);
out.write(data);
}
+ public static void varBytes(byte[] data, ByteBuffer buffer) {
+ varInt(data.length, buffer);
+ buffer.put(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 {
+ public static byte[] bytes(Streamable streamable) {
if (streamable == null) return null;
ByteArrayOutputStream stream = new ByteArrayOutputStream();
- streamable.write(stream);
+ try {
+ streamable.write(stream);
+ } catch (IOException e) {
+ throw new ApplicationException(e);
+ }
return stream.toByteArray();
}
@@ -164,11 +190,14 @@ public class Encode {
* @param streamable the object to be serialized
* @param padding the result will be padded such that its length is a multiple of padding
* @return the bytes of the given {@link Streamable} object, 0-padded such that the final length is x*padding.
- * @throws IOException if an I/O error occurs.
*/
- public static byte[] bytes(Streamable streamable, int padding) throws IOException {
+ public static byte[] bytes(Streamable streamable, int padding) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
- streamable.write(stream);
+ try {
+ streamable.write(stream);
+ } catch (IOException e) {
+ throw new ApplicationException(e);
+ }
int offset = padding - stream.size() % padding;
int length = stream.size() + offset;
byte[] result = new byte[length];
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
index d661c50..e5ba4f8 100644
--- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
@@ -24,6 +24,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import static ch.dissem.bitmessage.utils.Decode.*;
@@ -83,6 +84,13 @@ public class ProofOfWorkRequest implements Streamable {
Encode.varBytes(data, out);
}
+ @Override
+ public void write(ByteBuffer buffer) {
+ buffer.put(initialHash);
+ Encode.varString(request.name(), buffer);
+ Encode.varBytes(data, buffer);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index 635aed2..2b06373 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -45,7 +45,6 @@ import static java.util.Collections.newSetFromMap;
* Handles all the networky stuff.
*/
public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
- public final static int NETWORK_MAGIC_NUMBER = 8;
final Collection connections = new ConcurrentLinkedQueue<>();
private final ExecutorService pool = Executors.newCachedThreadPool(
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java
new file mode 100644
index 0000000..3361693
--- /dev/null
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 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.nio;
+
+import ch.dissem.bitmessage.entity.MessagePayload;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+/**
+ * Created by chrig on 27.05.2016.
+ */
+public class ConnectionInfo {
+ private State state;
+ private final Queue sendingQueue = new ConcurrentLinkedDeque<>();
+ private ByteBuffer in = ByteBuffer.allocate(10);
+ private ByteBuffer out = ByteBuffer.allocate(10);
+
+ public State getState() {
+ return state;
+ }
+
+ public Queue getSendingQueue() {
+ return sendingQueue;
+ }
+
+ public ByteBuffer getInBuffer() {
+ return in;
+ }
+
+ public ByteBuffer getOutBuffer() {
+ return out;
+ }
+
+ public enum State {CONNECTING, ACTIVE, DISCONNECTED}
+}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java
new file mode 100644
index 0000000..4f56d42
--- /dev/null
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2016 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.nio;
+
+import ch.dissem.bitmessage.InternalContext;
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.GetData;
+import ch.dissem.bitmessage.entity.MessagePayload;
+import ch.dissem.bitmessage.entity.NetworkMessage;
+import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
+import ch.dissem.bitmessage.exception.ApplicationException;
+import ch.dissem.bitmessage.ports.NetworkHandler;
+import ch.dissem.bitmessage.utils.Property;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import static java.nio.channels.SelectionKey.*;
+
+/**
+ * Network handler using java.nio, resulting in less threads.
+ */
+public class NioNetworkHandler implements NetworkHandler, InternalContext.ContextHolder {
+ private static final Logger LOG = LoggerFactory.getLogger(NioNetworkHandler.class);
+
+ private InternalContext ctx;
+ private Selector selector;
+
+ @Override
+ public Future> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds) {
+ return null;
+ }
+
+ @Override
+ public CustomMessage send(InetAddress server, int port, CustomMessage request) {
+ return null;
+ }
+
+ @Override
+ public void start(MessageListener listener) {
+ if (listener == null) {
+ throw new IllegalStateException("Listener must be set at start");
+ }
+ if (selector != null && selector.isOpen()) {
+ throw new IllegalStateException("Network already running - you need to stop first.");
+ }
+ try {
+ final Set requestedObjects = new HashSet<>();
+ selector = Selector.open();
+ {
+ ServerSocketChannel server = ServerSocketChannel.open();
+ server.configureBlocking(false);
+ server.bind(new InetSocketAddress(ctx.getPort()));
+ server.register(selector, OP_ACCEPT);
+ }
+ while (selector.isOpen()) {
+ // TODO: establish outgoing connections
+ selector.select();
+ Iterator keyIterator = selector.selectedKeys().iterator();
+
+ while (keyIterator.hasNext()) {
+ SelectionKey key = keyIterator.next();
+ if (key.isAcceptable()) {
+ SocketChannel accepted = ((ServerSocketChannel) key.channel()).accept();
+ accepted.configureBlocking(false);
+ accepted.register(selector, OP_READ | OP_WRITE).attach(new ConnectionInfo());
+ }
+ if (key.attachment() instanceof ConnectionInfo) {
+ SocketChannel channel = (SocketChannel) key.channel();
+ ConnectionInfo connection = (ConnectionInfo) key.attachment();
+
+ if (key.isWritable()) {
+ if (connection.getOutBuffer().hasRemaining()) {
+ channel.write(connection.getOutBuffer());
+ }
+ while (!connection.getOutBuffer().hasRemaining() && !connection.getSendingQueue().isEmpty()) {
+ MessagePayload payload = connection.getSendingQueue().poll();
+ if (payload instanceof GetData) {
+ requestedObjects.addAll(((GetData) payload).getInventory());
+ }
+ new NetworkMessage(payload).write(connection.getOutBuffer());
+ }
+ }
+ if (key.isReadable()) {
+ // TODO
+ channel.read(connection.getInBuffer());
+ }
+ }
+ keyIterator.remove();
+ }
+ }
+ selector.close();
+ } catch (IOException e) {
+ throw new ApplicationException(e);
+ }
+ }
+
+ @Override
+ public void stop() {
+
+ }
+
+ @Override
+ public void offer(InventoryVector iv) {
+
+ }
+
+ @Override
+ public Property getNetworkStatus() {
+ return null;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return false;
+ }
+
+ @Override
+ public void setContext(InternalContext context) {
+ this.ctx = context;
+ }
+}
From c1fa642b4eef704f4710a973c613c6fb5effad80 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Sat, 28 May 2016 11:04:47 +0200
Subject: [PATCH 52/87] Made tests more stable, albeit slightly slower
---
.../networking/NetworkHandlerTest.java | 71 ++++++++-----------
.../repository/JdbcMessageRepositoryTest.java | 7 +-
2 files changed, 34 insertions(+), 44 deletions(-)
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
index 6e4f8d9..3841be3 100644
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -24,9 +24,7 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Property;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import org.junit.*;
import java.net.InetAddress;
import java.util.concurrent.Future;
@@ -44,15 +42,15 @@ import static org.mockito.Mockito.mock;
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 TestInventory peerInventory;
+ private TestInventory nodeInventory;
- private static BitmessageContext peer;
- private static BitmessageContext node;
- private static NetworkHandler networkHandler;
+ private BitmessageContext peer;
+ private BitmessageContext node;
+ private NetworkHandler networkHandler;
- @BeforeClass
- public static void setUp() {
+ @Before
+ public void setUp() {
peerInventory = new TestInventory();
peer = new BitmessageContext.Builder()
.addressRepo(mock(AddressRepository.class))
@@ -99,34 +97,33 @@ public class NetworkHandlerTest {
.build();
}
- @AfterClass
- public static void cleanUp() {
+ @After
+ public void cleanUp() {
shutdown(peer);
+ shutdown(node);
}
- private static void shutdown(BitmessageContext node) {
- node.shutdown();
+ private static void shutdown(BitmessageContext ctx) {
+ if (!ctx.isRunning()) return;
+
+ ctx.shutdown();
do {
try {
Thread.sleep(100);
} catch (InterruptedException ignore) {
}
- } while (node.isRunning());
+ } while (ctx.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);
- }
+ node.startup();
+ Property status;
+ do {
+ Thread.yield();
+ status = node.status().getProperty("network", "connections", "stream 0");
+ } while (status == null);
+ assertEquals(1, status.getProperty("outgoing").getValue());
}
@Test(timeout = 5_000)
@@ -141,26 +138,20 @@ public class NetworkHandlerTest {
assertThat(response, notNullValue());
assertThat(response.getCustomCommand(), is("test response"));
assertThat(response.getData(), is(data));
-
- shutdown(node);
}
@Test(timeout = 5_000, expected = NodeException.class)
public void ensureCustomMessageWithoutResponsYieldsException() throws Exception {
- try {
- byte[] data = cryptography().randomBytes(8);
- data[0] = (byte) 0;
- CustomMessage request = new CustomMessage("test request", data);
- node.startup();
+ byte[] data = cryptography().randomBytes(8);
+ data[0] = (byte) 0;
+ CustomMessage request = new CustomMessage("test request", data);
+ node.startup();
- CustomMessage response = networkHandler.send(InetAddress.getLocalHost(), 6002, request);
+ CustomMessage response = networkHandler.send(InetAddress.getLocalHost(), 6002, request);
- assertThat(response, notNullValue());
- assertThat(response.getCustomCommand(), is("test response"));
- assertThat(response.getData(), is(request.getData()));
- } finally {
- shutdown(node);
- }
+ assertThat(response, notNullValue());
+ assertThat(response.getCustomCommand(), is("test response"));
+ assertThat(response.getData(), is(request.getData()));
}
@Test(timeout = 5_000)
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 5fa88e7..9d9b002 100644
--- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
+++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java
@@ -27,7 +27,6 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.UnixTime;
-import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
@@ -212,14 +211,14 @@ public class JdbcMessageRepositoryTest extends TestBase {
.to(contactA)
.message("Subject", "Message")
.status(Plaintext.Status.SENT)
- .ttl(1)
+ .ttl(2)
.build();
message.updateNextTry();
assertThat(message.getRetries(), is(1));
assertThat(message.getNextTry(), greaterThan(UnixTime.now()));
- assertThat(message.getNextTry(), lessThanOrEqualTo(UnixTime.now(+1)));
+ assertThat(message.getNextTry(), lessThanOrEqualTo(UnixTime.now(+2)));
repo.save(message);
- Thread.sleep(2100);
+ Thread.sleep(4100);
List messagesToResend = repo.findMessagesToResend();
assertThat(messagesToResend, hasSize(1));
From cde4f7b3ce50e5649b6b60e4fdd644d8bd43a281 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Wed, 1 Jun 2016 17:38:49 +0200
Subject: [PATCH 53/87] Some refactoring to move some common code into an
AbstractConnection
---
.../entity/valueobject/NetworkAddress.java | 13 +
.../bitmessage/factory/V3MessageFactory.java | 2 +-
.../bitmessage/factory/V3MessageReader.java | 143 ++++++++
.../ports/AbstractCryptography.java | 6 +
.../dissem/bitmessage/ports/Cryptography.java | 12 +
.../bitmessage/ports/NetworkHandler.java | 10 +
.../ch/dissem/bitmessage/utils/Decode.java | 4 +
.../networking/AbstractConnection.java | 318 ++++++++++++++++++
.../bitmessage/networking/Connection.java | 281 ++--------------
.../networking/ConnectionOrganizer.java | 8 +-
.../networking/DefaultNetworkHandler.java | 11 +-
.../bitmessage/networking/ServerRunnable.java | 8 +-
.../networking/nio/ConnectionInfo.java | 53 ++-
.../networking/nio/NioNetworkHandler.java | 210 +++++++++---
.../networking/NetworkHandlerTest.java | 9 +-
15 files changed, 748 insertions(+), 340 deletions(-)
create mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java
create mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java
index 0fef152..a496d19 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java
@@ -24,6 +24,8 @@ import ch.dissem.bitmessage.utils.UnixTime;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -215,6 +217,17 @@ public class NetworkAddress implements Streamable {
return this;
}
+ public Builder address(SocketAddress address) {
+ if (address instanceof InetSocketAddress) {
+ InetSocketAddress inetAddress = (InetSocketAddress) address;
+ ip(inetAddress.getAddress());
+ port(inetAddress.getPort());
+ } else {
+ throw new IllegalArgumentException("Unknown type of address: " + address.getClass());
+ }
+ return this;
+ }
+
public NetworkAddress build() {
if (time == 0) {
time = UnixTime.now();
diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
index af9839f..478d77c 100644
--- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
@@ -62,7 +62,7 @@ class V3MessageFactory {
}
}
- private static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException {
+ static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException {
switch (command) {
case "version":
return parseVersion(stream);
diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java
new file mode 100644
index 0000000..7006cdb
--- /dev/null
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016 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.factory;
+
+import ch.dissem.bitmessage.entity.MessagePayload;
+import ch.dissem.bitmessage.entity.NetworkMessage;
+import ch.dissem.bitmessage.exception.ApplicationException;
+import ch.dissem.bitmessage.exception.NodeException;
+import ch.dissem.bitmessage.utils.Decode;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
+import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
+
+/**
+ * Similar to the {@link V3MessageFactory}, but used for NIO buffers which may or may not contain a whole message.
+ */
+public class V3MessageReader {
+ private ReaderState state = ReaderState.MAGIC;
+ private String command;
+ private int length;
+ private byte[] checksum;
+
+ private List messages = new LinkedList<>();
+
+ public void update(ByteBuffer buffer) {
+ while (buffer.hasRemaining()) {
+ switch (state) {
+ case MAGIC:
+ if (!findMagicBytes(buffer)) return;
+ state = ReaderState.HEADER;
+ case HEADER:
+ if (buffer.remaining() < 20) {
+ buffer.compact();
+ return;
+ }
+ command = getCommand(buffer);
+ length = (int) Decode.uint32(buffer);
+ if (length > MAX_PAYLOAD_SIZE) {
+ throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected.");
+ }
+ checksum = new byte[4];
+ buffer.get(checksum);
+ state = ReaderState.DATA;
+ if (buffer.remaining() < length) {
+ // We need to compact the buffer to make sure the message fits even if it's really big.
+ buffer.compact();
+ }
+ case DATA:
+ if (buffer.remaining() < length) return;
+ if (!testChecksum(buffer)) {
+ throw new NodeException("Checksum failed for message '" + command + "'");
+ }
+ try {
+ MessagePayload payload = V3MessageFactory.getPayload(
+ command,
+ new ByteArrayInputStream(buffer.array(), buffer.arrayOffset() + buffer.position(), length),
+ length);
+ if (payload != null) {
+ messages.add(new NetworkMessage(payload));
+ }
+ } catch (IOException e) {
+ throw new NodeException(e.getMessage());
+ }
+ state = ReaderState.MAGIC;
+ }
+ }
+ }
+
+ public List getMessages() {
+ return messages;
+ }
+
+ private boolean findMagicBytes(ByteBuffer buffer) {
+ int i = 0;
+ while (buffer.hasRemaining()) {
+ if (buffer.get() == MAGIC_BYTES[i]) {
+ buffer.mark();
+ i++;
+ if (i == MAGIC_BYTES.length) return true;
+ } else {
+ i = 0;
+ }
+ }
+ if (i > 0) {
+ buffer.reset();
+ buffer.compact();
+ } else {
+ buffer.clear();
+ }
+ return false;
+ }
+
+ private static String getCommand(ByteBuffer buffer) {
+ int start = buffer.position();
+ int i = 0;
+ while (i < 12 && buffer.get() != 0) i++;
+ int end = start + i;
+ while (i < 12) {
+ if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command");
+ i++;
+ }
+ try {
+ return new String(buffer.array(), start, end, "ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new ApplicationException(e);
+ }
+ }
+
+ private boolean testChecksum(ByteBuffer buffer) {
+ byte[] payloadChecksum = cryptography().sha512(buffer.array(),
+ buffer.arrayOffset() + buffer.position(), length);
+ for (int i = 0; i < checksum.length; i++) {
+ if (checksum[i] != payloadChecksum[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private enum ReaderState {MAGIC, HEADER, DATA}
+}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java
index 3b08377..f67b6d4 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java
@@ -61,6 +61,12 @@ public abstract class AbstractCryptography implements Cryptography, InternalCont
this.context = context;
}
+ public byte[] sha512(byte[] data, int offset, int length) {
+ MessageDigest mda = md("SHA-512");
+ mda.update(data, offset, length);
+ return mda.digest();
+ }
+
public byte[] sha512(byte[]... data) {
return hash("SHA-512", data);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java
index 48739ea..9ea6a9d 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java
@@ -30,6 +30,18 @@ import java.security.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
+ * @param offset of the data to be hashed
+ * @param length of the data to be hashed
+ * @return SHA-512 hash of data within the given range
+ */
+ byte[] sha512(byte[] data, int offset, int length);
+
/**
* 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
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
index bfbb48c..96dec8e 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
@@ -23,6 +23,7 @@ import ch.dissem.bitmessage.utils.Property;
import java.io.IOException;
import java.net.InetAddress;
+import java.util.Collection;
import java.util.concurrent.Future;
/**
@@ -30,6 +31,8 @@ import java.util.concurrent.Future;
*/
public interface NetworkHandler {
int NETWORK_MAGIC_NUMBER = 8;
+ int MAX_PAYLOAD_SIZE = 1600003;
+ int MAX_MESSAGE_SIZE = 24 + MAX_PAYLOAD_SIZE;
/**
* Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
@@ -65,6 +68,13 @@ public interface NetworkHandler {
*/
void offer(InventoryVector iv);
+ /**
+ * Request each of those objects from a node that knows of the requested object.
+ *
+ * @param inventoryVectors of the objects to be requested
+ */
+ void request(Collection inventoryVectors);
+
Property getNetworkStatus();
boolean isRunning();
diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
index 47b0ee3..fb2f3c8 100644
--- a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
@@ -111,6 +111,10 @@ public class Decode {
return stream.read() * 16777216L + stream.read() * 65536L + stream.read() * 256L + stream.read();
}
+ public static long uint32(ByteBuffer buffer) {
+ return buffer.get() * 16777216L + buffer.get() * 65536L + buffer.get() * 256L + buffer.get();
+ }
+
public static int int32(InputStream stream) throws IOException {
return int32(stream, null);
}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java
new file mode 100644
index 0000000..3027f04
--- /dev/null
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2016 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.InternalContext;
+import ch.dissem.bitmessage.entity.*;
+import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
+import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
+import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
+import ch.dissem.bitmessage.exception.NodeException;
+import ch.dissem.bitmessage.ports.NetworkHandler;
+import ch.dissem.bitmessage.utils.UnixTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
+import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
+import static ch.dissem.bitmessage.networking.AbstractConnection.State.*;
+import static ch.dissem.bitmessage.utils.Singleton.cryptography;
+import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
+
+/**
+ * Contains everything used by both the old streams-oriented NetworkHandler and the new NioNetworkHandler,
+ * respectively their connection objects.
+ */
+public abstract class AbstractConnection {
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractConnection.class);
+ protected final InternalContext ctx;
+ protected final Mode mode;
+ protected final NetworkAddress host;
+ protected final NetworkAddress node;
+ protected final NetworkHandler.MessageListener listener;
+ protected final Map ivCache;
+ protected final Deque sendingQueue;
+ protected final Set commonRequestedObjects;
+ protected final Set requestedObjects;
+
+ protected volatile State state;
+ protected long lastObjectTime;
+
+ protected long peerNonce;
+ protected int version;
+ protected long[] streams;
+
+ public AbstractConnection(InternalContext context, Mode mode,
+ NetworkAddress node,
+ NetworkHandler.MessageListener listener,
+ Set commonRequestedObjects,
+ boolean threadsafe) {
+ this.ctx = context;
+ this.mode = mode;
+ 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();
+ this.node = node;
+ this.listener = listener;
+ if (threadsafe) {
+ this.ivCache = new ConcurrentHashMap<>();
+ this.sendingQueue = new ConcurrentLinkedDeque<>();
+ this.requestedObjects = Collections.newSetFromMap(new ConcurrentHashMap(10_000));
+ } else {
+ this.ivCache = new HashMap<>();
+ this.sendingQueue = new LinkedList<>();
+ this.requestedObjects = new HashSet<>();
+ }
+ this.state = CONNECTING;
+ this.commonRequestedObjects = commonRequestedObjects;
+ }
+
+ public Mode getMode() {
+ return mode;
+ }
+
+ public NetworkAddress getNode() {
+ return node;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ protected void handleMessage(MessagePayload payload) {
+ switch (state) {
+ case ACTIVE:
+ receiveMessage(payload);
+ break;
+
+ default:
+ handleCommand(payload);
+ break;
+ }
+ }
+
+ private void receiveMessage(MessagePayload messagePayload) {
+ switch (messagePayload.getCommand()) {
+ case INV:
+ receiveMessage((Inv) messagePayload);
+ break;
+ case GETDATA:
+ receiveMessage((GetData) messagePayload);
+ break;
+ case OBJECT:
+ receiveMessage((ObjectMessage) messagePayload);
+ break;
+ case ADDR:
+ receiveMessage((Addr) messagePayload);
+ break;
+ case CUSTOM:
+ case VERACK:
+ case VERSION:
+ default:
+ throw new IllegalStateException("Unexpectedly received '" + messagePayload.getCommand() + "' command");
+ }
+ }
+
+ private void receiveMessage(Inv inv) {
+ int originalSize = inv.getInventory().size();
+ updateIvCache(inv.getInventory());
+ List missing = ctx.getInventory().getMissing(inv.getInventory(), streams);
+ missing.removeAll(commonRequestedObjects);
+ LOG.debug("Received inventory with " + originalSize + " elements, of which are "
+ + missing.size() + " missing.");
+ send(new GetData.Builder().inventory(missing).build());
+ }
+
+ private void receiveMessage(GetData getData) {
+ for (InventoryVector iv : getData.getInventory()) {
+ ObjectMessage om = ctx.getInventory().getObject(iv);
+ if (om != null) sendingQueue.offer(om);
+ }
+ }
+
+ private void receiveMessage(ObjectMessage objectMessage) {
+ requestedObjects.remove(objectMessage.getInventoryVector());
+ if (ctx.getInventory().contains(objectMessage)) {
+ LOG.trace("Received object " + objectMessage.getInventoryVector() + " - already in inventory");
+ return;
+ }
+ try {
+ listener.receive(objectMessage);
+ cryptography().checkProofOfWork(objectMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES);
+ ctx.getInventory().storeObject(objectMessage);
+ // offer object to some random nodes so it gets distributed throughout the network:
+ ctx.getNetworkHandler().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);
+ } finally {
+ if (commonRequestedObjects.remove(objectMessage.getInventoryVector())) {
+ LOG.debug("Received object that wasn't requested.");
+ }
+ }
+ }
+
+ private void receiveMessage(Addr addr) {
+ LOG.debug("Received " + addr.getAddresses().size() + " addresses.");
+ ctx.getNodeRegistry().offerAddresses(addr.getAddresses());
+ }
+
+ private void updateIvCache(List inventory) {
+ cleanupIvCache();
+ Long now = UnixTime.now();
+ for (InventoryVector iv : inventory) {
+ ivCache.put(iv, now);
+ }
+ }
+
+ public void offer(InventoryVector iv) {
+ sendingQueue.offer(new Inv.Builder()
+ .addInventoryVector(iv)
+ .build());
+ updateIvCache(Collections.singletonList(iv));
+ }
+
+ public boolean knowsOf(InventoryVector iv) {
+ return ivCache.containsKey(iv);
+ }
+
+ protected void cleanupIvCache() {
+ Long fiveMinutesAgo = UnixTime.now(-5 * MINUTE);
+ for (Map.Entry entry : ivCache.entrySet()) {
+ if (entry.getValue() < fiveMinutesAgo) {
+ ivCache.remove(entry.getKey());
+ }
+ }
+ }
+
+ private void handleCommand(MessagePayload payload) {
+ switch (payload.getCommand()) {
+ case VERSION:
+ handleVersion((Version) payload);
+ break;
+ case VERACK:
+ switch (mode) {
+ case SERVER:
+ activateConnection();
+ break;
+ case CLIENT:
+ case SYNC:
+ default:
+ // NO OP
+ break;
+ }
+ break;
+ case CUSTOM:
+ MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) payload);
+ if (response != null) {
+ send(response);
+ }
+ disconnect();
+ break;
+ default:
+ throw new NodeException("Command 'version' or 'verack' expected, but was '"
+ + payload.getCommand() + "'");
+ }
+ }
+
+ protected void activateConnection() {
+ LOG.info("Successfully established connection with node " + node);
+ state = ACTIVE;
+ node.setTime(UnixTime.now());
+ if (mode != SYNC) {
+ sendAddresses();
+ ctx.getNodeRegistry().offerAddresses(Collections.singletonList(node));
+ }
+ sendInventory();
+ }
+
+ private void sendAddresses() {
+ List addresses = ctx.getNodeRegistry().getKnownAddresses(1000, streams);
+ sendingQueue.offer(new Addr.Builder().addresses(addresses).build());
+ }
+
+ private void sendInventory() {
+ List inventory = ctx.getInventory().getInventory(streams);
+ for (int i = 0; i < inventory.size(); i += 50000) {
+ sendingQueue.offer(new Inv.Builder()
+ .inventory(inventory.subList(i, Math.min(inventory.size(), i + 50000)))
+ .build());
+ }
+ }
+
+ private void handleVersion(Version version) {
+ if (version.getNonce() == ctx.getClientNonce()) {
+ LOG.info("Tried to connect to self, disconnecting.");
+ disconnect();
+ } else if (version.getVersion() >= BitmessageContext.CURRENT_VERSION) {
+ this.peerNonce = version.getNonce();
+ if (peerNonce == ctx.getClientNonce()) disconnect();
+
+ this.version = version.getVersion();
+ this.streams = version.getStreams();
+ send(new VerAck());
+ switch (mode) {
+ case SERVER:
+ send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build());
+ break;
+ case CLIENT:
+ case SYNC:
+ activateConnection();
+ break;
+ default:
+ // NO OP
+ }
+ } else {
+ LOG.info("Received unsupported version " + version.getVersion() + ", disconnecting.");
+ disconnect();
+ }
+ }
+
+ public void disconnect() {
+ state = DISCONNECTED;
+
+ // Make sure objects that are still missing are requested from other nodes
+ ctx.getNetworkHandler().request(requestedObjects);
+ }
+
+ protected abstract void send(MessagePayload payload);
+
+ public enum Mode {SERVER, CLIENT, SYNC}
+
+ public enum State {CONNECTING, ACTIVE, DISCONNECTED}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AbstractConnection that = (AbstractConnection) o;
+ return Objects.equals(node, that.node);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(node);
+ }
+}
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 9ed19bc..3fe1b62 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -16,13 +16,13 @@
package ch.dissem.bitmessage.networking;
-import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.InternalContext;
-import ch.dissem.bitmessage.entity.*;
+import ch.dissem.bitmessage.entity.GetData;
+import ch.dissem.bitmessage.entity.MessagePayload;
+import ch.dissem.bitmessage.entity.NetworkMessage;
+import ch.dissem.bitmessage.entity.Version;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
-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.UnixTime;
@@ -36,94 +36,62 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.ConcurrentMap;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
-import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
-import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
-import static ch.dissem.bitmessage.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.cryptography;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
+import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE;
+import static ch.dissem.bitmessage.networking.AbstractConnection.State.DISCONNECTED;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
/**
* A connection to a specific node
*/
-class Connection {
+class Connection extends AbstractConnection {
public static final int READ_TIMEOUT = 2000;
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 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 final long clientNonce;
- private volatile State state;
private InputStream in;
private OutputStream out;
- private int version;
- private long[] streams;
private int readTimeoutCounter;
private boolean socketInitialized;
- private long lastObjectTime;
public Connection(InternalContext context, Mode mode, Socket socket, MessageListener listener,
- Set requestedObjectsMap, long clientNonce) throws IOException {
+ 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, clientNonce);
+ 0);
}
public Connection(InternalContext context, Mode mode, NetworkAddress node, MessageListener listener,
- Set requestedObjectsMap, long clientNonce) {
+ Set requestedObjectsMap) {
this(context, mode, listener, new Socket(), requestedObjectsMap,
- Collections.newSetFromMap(new ConcurrentHashMap(10_000)),
- node, 0, clientNonce);
+ node, 0);
}
private Connection(InternalContext context, Mode mode, MessageListener listener, Socket socket,
- Set commonRequestedObjects, Set requestedObjects,
- NetworkAddress node, long syncTimeout, long clientNonce) {
+ Set commonRequestedObjects, NetworkAddress node, long syncTimeout) {
+ super(context, mode, node, listener, commonRequestedObjects, true);
this.startTime = UnixTime.now();
- this.ctx = context;
- this.mode = mode;
- this.state = CONNECTING;
- this.listener = listener;
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();
- this.node = node;
this.syncTimeout = (syncTimeout > 0 ? UnixTime.now(+syncTimeout) : 0);
- this.ivCache = new ConcurrentHashMap<>();
- this.networkHandler = (DefaultNetworkHandler) ctx.getNetworkHandler();
- this.clientNonce = clientNonce;
}
public static Connection sync(InternalContext ctx, InetAddress address, int port, MessageListener listener,
long timeoutInSeconds) throws IOException {
return new Connection(ctx, SYNC, listener, new Socket(address, port),
- new HashSet(),
new HashSet(),
new NetworkAddress.Builder().ip(address).port(port).stream(1).build(),
- timeoutInSeconds, cryptography().randomNonce());
+ timeoutInSeconds);
}
public long getStartTime() {
@@ -169,133 +137,8 @@ class Connection {
}
}
- private void activateConnection() {
- LOG.info("Successfully established connection with node " + node);
- state = ACTIVE;
- if (mode != SYNC) {
- sendAddresses();
- ctx.getNodeRegistry().offerAddresses(Collections.singletonList(node));
- }
- sendInventory();
- node.setTime(UnixTime.now());
- }
-
- private void cleanupIvCache() {
- Long fiveMinutesAgo = UnixTime.now(-5 * MINUTE);
- for (Map.Entry entry : ivCache.entrySet()) {
- if (entry.getValue() < fiveMinutesAgo) {
- ivCache.remove(entry.getKey());
- }
- }
- }
-
- private void updateIvCache(InventoryVector... inventory) {
- cleanupIvCache();
- Long now = UnixTime.now();
- for (InventoryVector iv : inventory) {
- ivCache.put(iv, now);
- }
- }
-
- private void updateIvCache(List inventory) {
- cleanupIvCache();
- Long now = UnixTime.now();
- for (InventoryVector iv : inventory) {
- ivCache.put(iv, now);
- }
- }
-
- private void receiveMessage(MessagePayload messagePayload) {
- switch (messagePayload.getCommand()) {
- case INV:
- receiveMessage((Inv) messagePayload);
- break;
- case GETDATA:
- receiveMessage((GetData) messagePayload);
- break;
- case OBJECT:
- receiveMessage((ObjectMessage) messagePayload);
- break;
- case ADDR:
- receiveMessage((Addr) messagePayload);
- break;
- case CUSTOM:
- case VERACK:
- case VERSION:
- default:
- throw new IllegalStateException("Unexpectedly received '" + messagePayload.getCommand() + "' command");
- }
- }
-
- private void receiveMessage(Inv inv) {
- int originalSize = inv.getInventory().size();
- updateIvCache(inv.getInventory());
- List missing = ctx.getInventory().getMissing(inv.getInventory(), streams);
- missing.removeAll(commonRequestedObjects);
- LOG.debug("Received inventory with " + originalSize + " elements, of which are "
- + missing.size() + " missing.");
- send(new GetData.Builder().inventory(missing).build());
- }
-
- private void receiveMessage(GetData getData) {
- for (InventoryVector iv : getData.getInventory()) {
- ObjectMessage om = ctx.getInventory().getObject(iv);
- if (om != null) sendingQueue.offer(om);
- }
- }
-
- private void receiveMessage(ObjectMessage objectMessage) {
- requestedObjects.remove(objectMessage.getInventoryVector());
- if (ctx.getInventory().contains(objectMessage)) {
- LOG.trace("Received object " + objectMessage.getInventoryVector() + " - already in inventory");
- return;
- }
- try {
- listener.receive(objectMessage);
- cryptography().checkProofOfWork(objectMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES);
- ctx.getInventory().storeObject(objectMessage);
- // offer object to some random nodes so it gets distributed throughout the network:
- 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);
- } finally {
- if (commonRequestedObjects.remove(objectMessage.getInventoryVector())) {
- LOG.debug("Received object that wasn't requested.");
- }
- }
- }
-
- private void receiveMessage(Addr addr) {
- LOG.debug("Received " + addr.getAddresses().size() + " addresses.");
- ctx.getNodeRegistry().offerAddresses(addr.getAddresses());
- }
-
- private void sendAddresses() {
- List addresses = ctx.getNodeRegistry().getKnownAddresses(1000, streams);
- sendingQueue.offer(new Addr.Builder().addresses(addresses).build());
- }
-
- private void sendInventory() {
- List inventory = ctx.getInventory().getInventory(streams);
- for (int i = 0; i < inventory.size(); i += 50000) {
- sendingQueue.offer(new Inv.Builder()
- .inventory(inventory.subList(i, Math.min(inventory.size(), i + 50000)))
- .build());
- }
- }
-
- public void disconnect() {
- state = DISCONNECTED;
-
- // Make sure objects that are still missing are requested from other nodes
- networkHandler.request(requestedObjects);
- }
-
- void send(MessagePayload payload) {
+ @Override
+ protected void send(MessagePayload payload) {
try {
if (payload instanceof GetData) {
requestedObjects.addAll(((GetData) payload).getInventory());
@@ -309,17 +152,6 @@ class Connection {
}
}
- public void offer(InventoryVector iv) {
- sendingQueue.offer(new Inv.Builder()
- .addInventoryVector(iv)
- .build());
- updateIvCache(iv);
- }
-
- public boolean knowsOf(InventoryVector iv) {
- return ivCache.containsKey(iv);
- }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -354,18 +186,13 @@ class Connection {
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(clientNonce).addrFrom(host).addrRecv(node).build());
+ send(new Version.Builder().defaults(peerNonce).addrFrom(host).addrRecv(node).build());
}
while (state != DISCONNECTED) {
if (mode != SYNC) {
@@ -394,75 +221,13 @@ class Connection {
NetworkMessage msg = Factory.getNetworkMessage(version, in);
if (msg == null)
return;
- switch (state) {
- case ACTIVE:
- receiveMessage(msg.getPayload());
- break;
-
- default:
- handleCommand(msg.getPayload());
- break;
- }
+ handleMessage(msg.getPayload());
if (socket.isClosed() || syncFinished(msg) || checkOpenRequests()) disconnect();
} catch (SocketTimeoutException ignore) {
if (state == ACTIVE && syncFinished(null)) disconnect();
}
}
- private void handleCommand(MessagePayload payload) {
- switch (payload.getCommand()) {
- case VERSION:
- handleVersion((Version) payload);
- break;
- case VERACK:
- switch (mode) {
- case SERVER:
- activateConnection();
- break;
- case CLIENT:
- case SYNC:
- default:
- // NO OP
- break;
- }
- break;
- case CUSTOM:
- MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) payload);
- if (response != null) {
- send(response);
- }
- disconnect();
- break;
- default:
- throw new NodeException("Command 'version' or 'verack' expected, but was '"
- + payload.getCommand() + "'");
- }
- }
-
- private void handleVersion(Version version) {
- if (version.getNonce() == ctx.getClientNonce()) {
- LOG.info("Tried to connect to self, disconnecting.");
- disconnect();
- } else if (version.getVersion() >= BitmessageContext.CURRENT_VERSION) {
- Connection.this.version = version.getVersion();
- streams = version.getStreams();
- send(new VerAck());
- switch (mode) {
- case SERVER:
- send(new Version.Builder().defaults(clientNonce).addrFrom(host).addrRecv(node).build());
- break;
- case CLIENT:
- case SYNC:
- activateConnection();
- break;
- default:
- // NO OP
- }
- } else {
- LOG.info("Received unsupported version " + version.getVersion() + ", disconnecting.");
- disconnect();
- }
- }
}
private boolean checkOpenRequests() {
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java
index c25a31c..5c976e2 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java
@@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.List;
-import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT;
import static ch.dissem.bitmessage.networking.DefaultNetworkHandler.NETWORK_MAGIC_NUMBER;
/**
@@ -38,17 +38,15 @@ public class ConnectionOrganizer implements Runnable {
private final InternalContext ctx;
private final DefaultNetworkHandler networkHandler;
private final NetworkHandler.MessageListener listener;
- private final long clientNonce;
private Connection initialConnection;
public ConnectionOrganizer(InternalContext ctx,
DefaultNetworkHandler networkHandler,
- NetworkHandler.MessageListener listener, long clientNonce) {
+ NetworkHandler.MessageListener listener) {
this.ctx = ctx;
this.networkHandler = networkHandler;
this.listener = listener;
- this.clientNonce = clientNonce;
}
@Override
@@ -94,7 +92,7 @@ public class ConnectionOrganizer implements Runnable {
boolean first = active == 0 && initialConnection == null;
for (NetworkAddress address : addresses) {
Connection c = new Connection(ctx, CLIENT, address, listener,
- networkHandler.requestedObjects, clientNonce);
+ networkHandler.requestedObjects);
if (first) {
initialConnection = c;
first = false;
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index 2b06373..c4a4554 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -35,8 +35,8 @@ import java.net.Socket;
import java.util.*;
import java.util.concurrent.*;
-import static ch.dissem.bitmessage.networking.Connection.Mode.SERVER;
-import static ch.dissem.bitmessage.networking.Connection.State.ACTIVE;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
+import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE;
import static ch.dissem.bitmessage.utils.DebugUtils.inc;
import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool;
import static java.util.Collections.newSetFromMap;
@@ -107,9 +107,9 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
try {
running = true;
connections.clear();
- server = new ServerRunnable(ctx, this, listener, ctx.getClientNonce());
+ server = new ServerRunnable(ctx, this, listener);
pool.execute(server);
- pool.execute(new ConnectionOrganizer(ctx, this, listener, ctx.getClientNonce()));
+ pool.execute(new ConnectionOrganizer(ctx, this, listener));
} catch (IOException e) {
throw new ApplicationException(e);
}
@@ -198,7 +198,8 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
);
}
- void request(Set inventoryVectors) {
+ @Override
+ public void request(Collection inventoryVectors) {
if (!running || inventoryVectors.isEmpty()) return;
Map> distribution = new HashMap<>();
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java
index 5a866b9..99b6a88 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java
@@ -26,7 +26,7 @@ import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
-import static ch.dissem.bitmessage.networking.Connection.Mode.SERVER;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
/**
* @author Christian Basler
@@ -37,15 +37,13 @@ public class ServerRunnable implements Runnable, Closeable {
private final ServerSocket serverSocket;
private final DefaultNetworkHandler networkHandler;
private final NetworkHandler.MessageListener listener;
- private final long clientNonce;
public ServerRunnable(InternalContext ctx, DefaultNetworkHandler networkHandler,
- NetworkHandler.MessageListener listener, long clientNonce) throws IOException {
+ NetworkHandler.MessageListener listener) throws IOException {
this.ctx = ctx;
this.networkHandler = networkHandler;
this.listener = listener;
this.serverSocket = new ServerSocket(ctx.getPort());
- this.clientNonce = clientNonce;
}
@Override
@@ -55,7 +53,7 @@ public class ServerRunnable implements Runnable, Closeable {
Socket socket = serverSocket.accept();
socket.setSoTimeout(Connection.READ_TIMEOUT);
networkHandler.startConnection(new Connection(ctx, SERVER, socket, listener,
- networkHandler.requestedObjects, clientNonce));
+ networkHandler.requestedObjects));
} catch (IOException e) {
LOG.debug(e.getMessage(), e);
}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java
index 3361693..223722a 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java
@@ -16,25 +16,43 @@
package ch.dissem.bitmessage.networking.nio;
+import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.MessagePayload;
+import ch.dissem.bitmessage.entity.NetworkMessage;
+import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
+import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
+import ch.dissem.bitmessage.factory.V3MessageReader;
+import ch.dissem.bitmessage.networking.AbstractConnection;
+import ch.dissem.bitmessage.ports.NetworkHandler;
import java.nio.ByteBuffer;
-import java.util.Queue;
+import java.util.*;
import java.util.concurrent.ConcurrentLinkedDeque;
+import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_MESSAGE_SIZE;
+
/**
- * Created by chrig on 27.05.2016.
+ * Represents the current state of a connection.
*/
-public class ConnectionInfo {
- private State state;
- private final Queue sendingQueue = new ConcurrentLinkedDeque<>();
- private ByteBuffer in = ByteBuffer.allocate(10);
- private ByteBuffer out = ByteBuffer.allocate(10);
+public class ConnectionInfo extends AbstractConnection {
+ private ByteBuffer in = ByteBuffer.allocate(MAX_MESSAGE_SIZE);
+ private ByteBuffer out = ByteBuffer.allocate(MAX_MESSAGE_SIZE);
+ private V3MessageReader reader = new V3MessageReader();
+
+ public ConnectionInfo(InternalContext context, Mode mode,
+ NetworkAddress node, NetworkHandler.MessageListener listener,
+ Set commonRequestedObjects) {
+ super(context, mode, node, listener, commonRequestedObjects, false);
+ }
public State getState() {
return state;
}
+ public boolean knowsOf(InventoryVector iv) {
+ return ivCache.containsKey(iv);
+ }
+
public Queue getSendingQueue() {
return sendingQueue;
}
@@ -47,5 +65,24 @@ public class ConnectionInfo {
return out;
}
- public enum State {CONNECTING, ACTIVE, DISCONNECTED}
+ public void updateReader() {
+ reader.update(in);
+ if (!reader.getMessages().isEmpty()) {
+ Iterator iterator = reader.getMessages().iterator();
+ while (iterator.hasNext()) {
+ NetworkMessage msg = iterator.next();
+ handleMessage(msg.getPayload());
+ iterator.remove();
+ }
+ }
+ }
+
+ public List getMessages() {
+ return reader.getMessages();
+ }
+
+ @Override
+ protected void send(MessagePayload payload) {
+ sendingQueue.addFirst(payload);
+ }
}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java
index 4f56d42..c3689af 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java
@@ -22,7 +22,10 @@ import ch.dissem.bitmessage.entity.GetData;
import ch.dissem.bitmessage.entity.MessagePayload;
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.ApplicationException;
+import ch.dissem.bitmessage.exception.NodeException;
+import ch.dissem.bitmessage.factory.V3MessageReader;
import ch.dissem.bitmessage.ports.NetworkHandler;
import ch.dissem.bitmessage.utils.Property;
import org.slf4j.Logger;
@@ -31,16 +34,16 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
+import java.nio.ByteBuffer;
+import java.nio.channels.*;
+import java.util.*;
import java.util.concurrent.Future;
-import static java.nio.channels.SelectionKey.*;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
+import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE;
+import static ch.dissem.bitmessage.utils.DebugUtils.inc;
+import static java.nio.channels.SelectionKey.OP_READ;
+import static java.nio.channels.SelectionKey.OP_WRITE;
/**
* Network handler using java.nio, resulting in less threads.
@@ -50,6 +53,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
private InternalContext ctx;
private Selector selector;
+ private ServerSocketChannel serverChannel;
@Override
public Future> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds) {
@@ -58,11 +62,38 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
@Override
public CustomMessage send(InetAddress server, int port, CustomMessage request) {
- return null;
+ try (SocketChannel channel = SocketChannel.open(new InetSocketAddress(server, port))) {
+ channel.configureBlocking(true);
+ ByteBuffer buffer = ByteBuffer.allocate(MAX_MESSAGE_SIZE);
+ new NetworkMessage(request).write(buffer);
+ channel.write(buffer);
+ buffer.clear();
+
+ V3MessageReader reader = new V3MessageReader();
+ while (reader.getMessages().isEmpty()) {
+ channel.read(buffer);
+ buffer.flip();
+ reader.update(buffer);
+ }
+ NetworkMessage networkMessage = reader.getMessages().get(0);
+
+ 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 ApplicationException(e);
+ }
}
@Override
- public void start(MessageListener listener) {
+ public void start(final MessageListener listener) {
if (listener == null) {
throw new IllegalStateException("Listener must be set at start");
}
@@ -70,50 +101,83 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
throw new IllegalStateException("Network already running - you need to stop first.");
}
try {
- final Set requestedObjects = new HashSet<>();
selector = Selector.open();
- {
- ServerSocketChannel server = ServerSocketChannel.open();
- server.configureBlocking(false);
- server.bind(new InetSocketAddress(ctx.getPort()));
- server.register(selector, OP_ACCEPT);
- }
- while (selector.isOpen()) {
- // TODO: establish outgoing connections
- selector.select();
- Iterator keyIterator = selector.selectedKeys().iterator();
+ } catch (IOException e) {
+ throw new ApplicationException(e);
+ }
+ final Set requestedObjects = new HashSet<>();
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ serverChannel = ServerSocketChannel.open();
+ serverChannel.bind(new InetSocketAddress(ctx.getPort()));
- while (keyIterator.hasNext()) {
- SelectionKey key = keyIterator.next();
- if (key.isAcceptable()) {
- SocketChannel accepted = ((ServerSocketChannel) key.channel()).accept();
- accepted.configureBlocking(false);
- accepted.register(selector, OP_READ | OP_WRITE).attach(new ConnectionInfo());
- }
- if (key.attachment() instanceof ConnectionInfo) {
- SocketChannel channel = (SocketChannel) key.channel();
- ConnectionInfo connection = (ConnectionInfo) key.attachment();
-
- if (key.isWritable()) {
- if (connection.getOutBuffer().hasRemaining()) {
- channel.write(connection.getOutBuffer());
- }
- while (!connection.getOutBuffer().hasRemaining() && !connection.getSendingQueue().isEmpty()) {
- MessagePayload payload = connection.getSendingQueue().poll();
- if (payload instanceof GetData) {
- requestedObjects.addAll(((GetData) payload).getInventory());
- }
- new NetworkMessage(payload).write(connection.getOutBuffer());
- }
- }
- if (key.isReadable()) {
- // TODO
- channel.read(connection.getInBuffer());
- }
- }
- keyIterator.remove();
+ SocketChannel accepted = serverChannel.accept();
+ accepted.configureBlocking(false);
+ // FIXME: apparently it isn't good practice to generally listen for OP_WRITE
+ accepted.register(selector, OP_READ | OP_WRITE).attach(
+ new ConnectionInfo(ctx, SERVER,
+ new NetworkAddress.Builder().address(accepted.getRemoteAddress()).stream(1).build(),
+ listener,
+ requestedObjects
+ ));
+ } catch (ClosedSelectorException | AsynchronousCloseException ignore) {
+ } catch (IOException e) {
+ throw new ApplicationException(e);
}
}
+ }, "Server").start();
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ while (selector.isOpen()) {
+ // TODO: establish outgoing connections
+ Iterator keyIterator = selector.selectedKeys().iterator();
+
+ while (keyIterator.hasNext()) {
+ SelectionKey key = keyIterator.next();
+ if (key.attachment() instanceof ConnectionInfo) {
+ SocketChannel channel = (SocketChannel) key.channel();
+ ConnectionInfo connection = (ConnectionInfo) key.attachment();
+
+ if (key.isWritable()) {
+ if (connection.getOutBuffer().hasRemaining()) {
+ channel.write(connection.getOutBuffer());
+ }
+ while (!connection.getOutBuffer().hasRemaining() && !connection.getSendingQueue().isEmpty()) {
+ MessagePayload payload = connection.getSendingQueue().poll();
+ if (payload instanceof GetData) {
+ requestedObjects.addAll(((GetData) payload).getInventory());
+ }
+ new NetworkMessage(payload).write(connection.getOutBuffer());
+ }
+ }
+ if (key.isReadable()) {
+ channel.read(connection.getInBuffer());
+ connection.updateReader();
+ }
+ }
+ keyIterator.remove();
+ }
+ }
+ selector.close();
+ } catch (ClosedSelectorException ignore) {
+ } catch (IOException e) {
+ throw new ApplicationException(e);
+ }
+ }
+ }, "Connections").start();
+ }
+
+ @Override
+ public void stop() {
+ try {
+ serverChannel.close();
+ for (SelectionKey key : selector.keys()) {
+ key.channel().close();
+ }
selector.close();
} catch (IOException e) {
throw new ApplicationException(e);
@@ -121,23 +185,57 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
}
@Override
- public void stop() {
-
+ public void offer(InventoryVector iv) {
+ // TODO
}
@Override
- public void offer(InventoryVector iv) {
-
+ public void request(Collection inventoryVectors) {
+ // TODO
}
@Override
public Property getNetworkStatus() {
- return null;
+ TreeSet streams = new TreeSet<>();
+ TreeMap incomingConnections = new TreeMap<>();
+ TreeMap outgoingConnections = new TreeMap<>();
+
+ for (SelectionKey key : selector.keys()) {
+ if (key.attachment() instanceof ConnectionInfo) {
+ ConnectionInfo connection = (ConnectionInfo) key.attachment();
+ 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", isRunning() ? "running" : "stopped"),
+ new Property("connections", null, streamProperties),
+ new Property("requestedObjects", "requestedObjects.size()") // TODO
+ );
}
@Override
public boolean isRunning() {
- return false;
+ return selector != null && selector.isOpen();
}
@Override
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
index 3841be3..5dbf077 100644
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -24,7 +24,9 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Property;
-import org.junit.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
import java.net.InetAddress;
import java.util.concurrent.Future;
@@ -89,6 +91,9 @@ public class NetworkHandlerTest {
break;
case 3:
data[0] = 0;
+ break;
+ default:
+ break;
}
}
return new CustomMessage("test response", request.getData());
@@ -115,7 +120,7 @@ public class NetworkHandlerTest {
} while (ctx.isRunning());
}
- @Test(timeout = 5_000)
+ @Test//(timeout = 5_000)
public void ensureNodesAreConnecting() {
node.startup();
Property status;
From 12fb794203ef99ed09bf3f155ef1b147b86b7424 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Thu, 9 Jun 2016 17:40:26 +0200
Subject: [PATCH 54/87] Minor test improvements
---
.../networking/NetworkHandlerTest.java | 74 ++++++++++++-------
1 file changed, 47 insertions(+), 27 deletions(-)
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
index 5dbf077..2cadfb0 100644
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -27,8 +27,9 @@ import ch.dissem.bitmessage.utils.Property;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import java.net.InetAddress;
import java.util.concurrent.Future;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
@@ -42,7 +43,8 @@ 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 final Logger LOG = LoggerFactory.getLogger(NetworkHandlerTest.class);
+ private static NetworkAddress peerAddress = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(6001).build();
private TestInventory peerInventory;
private TestInventory nodeInventory;
@@ -59,26 +61,11 @@ public class NetworkHandlerTest {
.inventory(peerInventory)
.messageRepo(mock(MessageRepository.class))
.powRepo(mock(ProofOfWorkRepository.class))
- .port(6001)
+ .port(peerAddress.getPort())
.nodeRegistry(new TestNodeRegistry())
.networkHandler(new DefaultNetworkHandler())
.cryptography(new BouncyCryptography())
.listener(mock(BitmessageContext.Listener.class))
- .build();
- peer.startup();
-
- nodeInventory = new TestInventory();
- networkHandler = new DefaultNetworkHandler();
- node = new BitmessageContext.Builder()
- .addressRepo(mock(AddressRepository.class))
- .inventory(nodeInventory)
- .messageRepo(mock(MessageRepository.class))
- .powRepo(mock(ProofOfWorkRepository.class))
- .port(6002)
- .nodeRegistry(new TestNodeRegistry(localhost))
- .networkHandler(networkHandler)
- .cryptography(new BouncyCryptography())
- .listener(mock(BitmessageContext.Listener.class))
.customCommandHandler(new CustomCommandHandler() {
@Override
public MessagePayload handle(CustomMessage request) {
@@ -100,12 +87,28 @@ public class NetworkHandlerTest {
}
})
.build();
+ peer.startup();
+
+ nodeInventory = new TestInventory();
+ networkHandler = new DefaultNetworkHandler();
+ node = new BitmessageContext.Builder()
+ .addressRepo(mock(AddressRepository.class))
+ .inventory(nodeInventory)
+ .messageRepo(mock(MessageRepository.class))
+ .powRepo(mock(ProofOfWorkRepository.class))
+ .port(6002)
+ .nodeRegistry(new TestNodeRegistry(peerAddress))
+ .networkHandler(networkHandler)
+ .cryptography(new BouncyCryptography())
+ .listener(mock(BitmessageContext.Listener.class))
+ .build();
}
@After
public void cleanUp() {
shutdown(peer);
shutdown(node);
+ shutdown(networkHandler);
}
private static void shutdown(BitmessageContext ctx) {
@@ -120,12 +123,29 @@ public class NetworkHandlerTest {
} while (ctx.isRunning());
}
- @Test//(timeout = 5_000)
- public void ensureNodesAreConnecting() {
+ private static void shutdown(NetworkHandler networkHandler) {
+ if (!networkHandler.isRunning()) return;
+
+ networkHandler.stop();
+ do {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignore) {
+ if (networkHandler.isRunning()) {
+ LOG.warn("Thread interrupted while waiting for network shutdown - " +
+ "this could cause problems in subsequent tests.");
+ }
+ return;
+ }
+ } while (networkHandler.isRunning());
+ }
+
+ @Test(timeout = 5_000)
+ public void ensureNodesAreConnecting() throws Exception {
node.startup();
Property status;
do {
- Thread.yield();
+ Thread.sleep(100);
status = node.status().getProperty("network", "connections", "stream 0");
} while (status == null);
assertEquals(1, status.getProperty("outgoing").getValue());
@@ -138,7 +158,7 @@ public class NetworkHandlerTest {
CustomMessage request = new CustomMessage("test request", data);
node.startup();
- CustomMessage response = networkHandler.send(InetAddress.getLocalHost(), 6002, request);
+ CustomMessage response = networkHandler.send(peerAddress.toInetAddress(), peerAddress.getPort(), request);
assertThat(response, notNullValue());
assertThat(response.getCustomCommand(), is("test response"));
@@ -146,13 +166,13 @@ public class NetworkHandlerTest {
}
@Test(timeout = 5_000, expected = NodeException.class)
- public void ensureCustomMessageWithoutResponsYieldsException() throws Exception {
+ public void ensureCustomMessageWithoutResponseYieldsException() throws Exception {
byte[] data = cryptography().randomBytes(8);
data[0] = (byte) 0;
CustomMessage request = new CustomMessage("test request", data);
node.startup();
- CustomMessage response = networkHandler.send(InetAddress.getLocalHost(), 6002, request);
+ CustomMessage response = networkHandler.send(peerAddress.toInetAddress(), peerAddress.getPort(), request);
assertThat(response, notNullValue());
assertThat(response.getCustomCommand(), is("test response"));
@@ -171,7 +191,7 @@ public class NetworkHandlerTest {
"V4Pubkey.payload"
);
- Future> future = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ Future> future = networkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(),
mock(NetworkHandler.MessageListener.class),
10);
future.get();
@@ -188,7 +208,7 @@ public class NetworkHandlerTest {
nodeInventory.init();
- Future> future = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ Future> future = networkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(),
mock(NetworkHandler.MessageListener.class),
10);
future.get();
@@ -204,7 +224,7 @@ public class NetworkHandlerTest {
"V1Msg.payload"
);
- Future> future = networkHandler.synchronize(InetAddress.getLocalHost(), 6001,
+ Future> future = networkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(),
mock(NetworkHandler.MessageListener.class),
10);
future.get();
From 62d40fb2c3cf65138e73fab56887bc7ca30dc58d Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Sun, 12 Jun 2016 20:43:23 +0200
Subject: [PATCH 55/87] Improved unsigned byte comparison
---
core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java b/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
index 8107eb0..986c288 100644
--- a/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
@@ -23,6 +23,8 @@ package ch.dissem.bitmessage.utils;
* situations.
*/
public class Bytes {
+ public static final byte BYTE_0x80 = (byte) 0x80;
+
public static void inc(byte[] nonce) {
for (int i = nonce.length - 1; i >= 0; i--) {
nonce[i]++;
@@ -82,11 +84,7 @@ public class Bytes {
}
private static boolean lt(byte a, byte b) {
- 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);
+ return (a ^ BYTE_0x80) < (b ^ BYTE_0x80);
}
/**
From ed4fd1002b17042dce1fb944e5e53cbbd55bbd0a Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Sun, 12 Jun 2016 20:53:05 +0200
Subject: [PATCH 56/87] Improved uint reading
---
.../ch/dissem/bitmessage/utils/Decode.java | 102 +++++++++---------
1 file changed, 54 insertions(+), 48 deletions(-)
diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
index fb2f3c8..c15f397 100644
--- a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
+++ b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
@@ -27,30 +27,29 @@ import static ch.dissem.bitmessage.utils.AccessCounter.inc;
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
*/
public class Decode {
- public static byte[] shortVarBytes(InputStream stream, AccessCounter counter) throws IOException {
- int length = uint16(stream, counter);
- return bytes(stream, length, counter);
+ public static byte[] shortVarBytes(InputStream in, AccessCounter counter) throws IOException {
+ int length = uint16(in, counter);
+ return bytes(in, length, counter);
}
- public static byte[] varBytes(InputStream stream) throws IOException {
- int length = (int) varInt(stream, null);
- return bytes(stream, length, null);
+ public static byte[] varBytes(InputStream in) throws IOException {
+ return varBytes(in, null);
}
- public static byte[] varBytes(InputStream stream, AccessCounter counter) throws IOException {
- int length = (int) varInt(stream, counter);
- return bytes(stream, length, counter);
+ public static byte[] varBytes(InputStream in, AccessCounter counter) throws IOException {
+ int length = (int) varInt(in, counter);
+ return bytes(in, length, counter);
}
- public static byte[] bytes(InputStream stream, int count) throws IOException {
- return bytes(stream, count, null);
+ public static byte[] bytes(InputStream in, int count) throws IOException {
+ return bytes(in, count, null);
}
- public static byte[] bytes(InputStream stream, int count, AccessCounter counter) throws IOException {
+ public static byte[] bytes(InputStream in, int count, AccessCounter counter) throws IOException {
byte[] result = new byte[count];
int off = 0;
while (off < count) {
- int read = stream.read(result, off, count - off);
+ int read = in.read(result, off, count - off);
if (read < 0) {
throw new IOException("Unexpected end of stream, wanted to read " + count + " bytes but only got " + off);
}
@@ -60,87 +59,94 @@ public class Decode {
return result;
}
- public static long[] varIntList(InputStream stream) throws IOException {
- int length = (int) varInt(stream);
+ public static long[] varIntList(InputStream in) throws IOException {
+ int length = (int) varInt(in);
long[] result = new long[length];
for (int i = 0; i < length; i++) {
- result[i] = varInt(stream);
+ result[i] = varInt(in);
}
return result;
}
- public static long varInt(InputStream stream) throws IOException {
- return varInt(stream, null);
+ public static long varInt(InputStream in) throws IOException {
+ return varInt(in, null);
}
- public static long varInt(InputStream stream, AccessCounter counter) throws IOException {
- int first = stream.read();
+ public static long varInt(InputStream in, AccessCounter counter) throws IOException {
+ int first = in.read();
inc(counter);
switch (first) {
case 0xfd:
- return uint16(stream, counter);
+ return uint16(in, counter);
case 0xfe:
- return uint32(stream, counter);
+ return uint32(in, counter);
case 0xff:
- return int64(stream, counter);
+ return int64(in, counter);
default:
return first;
}
}
- public static int uint8(InputStream stream) throws IOException {
- return stream.read();
+ public static int uint8(InputStream in) throws IOException {
+ return in.read();
}
- public static int uint16(InputStream stream) throws IOException {
- return uint16(stream, null);
+ public static int uint16(InputStream in) throws IOException {
+ return uint16(in, null);
}
- public static int uint16(InputStream stream, AccessCounter counter) throws IOException {
+ public static int uint16(InputStream in, AccessCounter counter) throws IOException {
inc(counter, 2);
- return stream.read() * 256 + stream.read();
+ return in.read() << 8 | in.read();
}
- public static long uint32(InputStream stream) throws IOException {
- return uint32(stream, null);
+ public static long uint32(InputStream in) throws IOException {
+ return uint32(in, null);
}
- public static long uint32(InputStream stream, AccessCounter counter) throws IOException {
+ public static long uint32(InputStream in, AccessCounter counter) throws IOException {
inc(counter, 4);
- return stream.read() * 16777216L + stream.read() * 65536L + stream.read() * 256L + stream.read();
+ return in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read();
}
- public static long uint32(ByteBuffer buffer) {
- return buffer.get() * 16777216L + buffer.get() * 65536L + buffer.get() * 256L + buffer.get();
+ public static long uint32(ByteBuffer in) {
+ return u(in.get()) << 24 | u(in.get()) << 16 | u(in.get()) << 8 | u(in.get());
}
- public static int int32(InputStream stream) throws IOException {
- return int32(stream, null);
+ public static int int32(InputStream in) throws IOException {
+ return int32(in, null);
}
- public static int int32(InputStream stream, AccessCounter counter) throws IOException {
+ public static int int32(InputStream in, AccessCounter counter) throws IOException {
inc(counter, 4);
- return ByteBuffer.wrap(bytes(stream, 4)).getInt();
+ return ByteBuffer.wrap(bytes(in, 4)).getInt();
}
- public static long int64(InputStream stream) throws IOException {
- return int64(stream, null);
+ public static long int64(InputStream in) throws IOException {
+ return int64(in, null);
}
- public static long int64(InputStream stream, AccessCounter counter) throws IOException {
+ public static long int64(InputStream in, AccessCounter counter) throws IOException {
inc(counter, 8);
- return ByteBuffer.wrap(bytes(stream, 8)).getLong();
+ return ByteBuffer.wrap(bytes(in, 8)).getLong();
}
- public static String varString(InputStream stream) throws IOException {
- return varString(stream, null);
+ public static String varString(InputStream in) throws IOException {
+ return varString(in, null);
}
- public static String varString(InputStream stream, AccessCounter counter) throws IOException {
- int length = (int) varInt(stream, counter);
+ public static String varString(InputStream in, AccessCounter counter) throws IOException {
+ int length = (int) varInt(in, 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, counter), "utf-8");
+ return new String(bytes(in, length, counter), "utf-8");
+ }
+
+ /**
+ * Returns the given byte as if it were unsigned.
+ */
+ private static int u(byte b) {
+ return b & 0xFF;
}
}
From 0fadb40c6cca6d97c0f8aa02c8ba7695fea93663 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Thu, 16 Jun 2016 19:47:59 +0200
Subject: [PATCH 57/87] Improved tests and fixed some
---
core/build.gradle | 2 +-
.../ch/dissem/bitmessage/entity/Version.java | 1 -
.../ch/dissem/bitmessage/factory/Factory.java | 2 +-
.../bitmessage/factory/V3MessageReader.java | 31 ++--
.../ports/AbstractCryptography.java | 2 +-
cryptography-bc/build.gradle | 2 +-
cryptography-sc/build.gradle | 2 +-
demo/build.gradle | 2 +-
.../java/ch/dissem/bitmessage/SystemTest.java | 4 +-
extensions/build.gradle | 2 +-
networking/build.gradle | 2 +-
.../networking/AbstractConnection.java | 35 ++--
.../networking/nio/ConnectionInfo.java | 18 +-
.../networking/nio/NioNetworkHandler.java | 173 ++++++++++++++----
.../networking/NetworkHandlerTest.java | 59 ++++--
wif/build.gradle | 2 +-
16 files changed, 234 insertions(+), 105 deletions(-)
diff --git a/core/build.gradle b/core/build.gradle
index dd100e6..73f8fdf 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -25,7 +25,7 @@ artifacts {
dependencies {
compile 'org.slf4j:slf4j-api:1.7.12'
- testCompile 'junit:junit:4.11'
+ testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile project(':cryptography-bc')
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java b/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
index 48022c4..4d0fd05 100644
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
+++ b/core/src/main/java/ch/dissem/bitmessage/entity/Version.java
@@ -24,7 +24,6 @@ import ch.dissem.bitmessage.utils.UnixTime;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.util.Random;
/**
* The 'version' command advertises this node's latest supported protocol version upon initiation.
diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
index 0597f6a..07b3161 100644
--- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
@@ -40,7 +40,7 @@ import static ch.dissem.bitmessage.utils.Singleton.cryptography;
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
*/
public class Factory {
- public static final Logger LOG = LoggerFactory.getLogger(Factory.class);
+ private static final Logger LOG = LoggerFactory.getLogger(Factory.class);
public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException {
try {
diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java
index 7006cdb..80220f1 100644
--- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java
+++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java
@@ -52,23 +52,21 @@ public class V3MessageReader {
state = ReaderState.HEADER;
case HEADER:
if (buffer.remaining() < 20) {
- buffer.compact();
return;
}
command = getCommand(buffer);
length = (int) Decode.uint32(buffer);
if (length > MAX_PAYLOAD_SIZE) {
- throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected.");
+ throw new NodeException("Payload of " + length + " bytes received, no more than " +
+ MAX_PAYLOAD_SIZE + " was expected.");
}
checksum = new byte[4];
buffer.get(checksum);
state = ReaderState.DATA;
- if (buffer.remaining() < length) {
- // We need to compact the buffer to make sure the message fits even if it's really big.
- buffer.compact();
- }
case DATA:
- if (buffer.remaining() < length) return;
+ if (buffer.remaining() < length) {
+ return;
+ }
if (!testChecksum(buffer)) {
throw new NodeException("Checksum failed for message '" + command + "'");
}
@@ -95,34 +93,35 @@ public class V3MessageReader {
private boolean findMagicBytes(ByteBuffer buffer) {
int i = 0;
while (buffer.hasRemaining()) {
- if (buffer.get() == MAGIC_BYTES[i]) {
+ if (i == 0) {
buffer.mark();
+ }
+ if (buffer.get() == MAGIC_BYTES[i]) {
i++;
- if (i == MAGIC_BYTES.length) return true;
+ if (i == MAGIC_BYTES.length) {
+ return true;
+ }
} else {
i = 0;
}
}
if (i > 0) {
buffer.reset();
- buffer.compact();
- } else {
- buffer.clear();
}
return false;
}
private static String getCommand(ByteBuffer buffer) {
int start = buffer.position();
- int i = 0;
- while (i < 12 && buffer.get() != 0) i++;
- int end = start + i;
+ int l = 0;
+ while (l < 12 && buffer.get() != 0) l++;
+ int i = l + 1;
while (i < 12) {
if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command");
i++;
}
try {
- return new String(buffer.array(), start, end, "ASCII");
+ return new String(buffer.array(), start, l, "ASCII");
} catch (UnsupportedEncodingException e) {
throw new ApplicationException(e);
}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java
index f67b6d4..b02b12d 100644
--- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java
+++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java
@@ -43,7 +43,7 @@ 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);
+ protected 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);
diff --git a/cryptography-bc/build.gradle b/cryptography-bc/build.gradle
index c09b0db..0b87c17 100644
--- a/cryptography-bc/build.gradle
+++ b/cryptography-bc/build.gradle
@@ -13,6 +13,6 @@ uploadArchives {
dependencies {
compile project(':core')
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
- testCompile 'junit:junit:4.11'
+ testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
}
diff --git a/cryptography-sc/build.gradle b/cryptography-sc/build.gradle
index 16771fc..a052c2e 100644
--- a/cryptography-sc/build.gradle
+++ b/cryptography-sc/build.gradle
@@ -13,5 +13,5 @@ uploadArchives {
dependencies {
compile project(':core')
compile 'com.madgag.spongycastle:prov:1.52.0.0'
- testCompile 'junit:junit:4.11'
+ testCompile 'junit:junit:4.12'
}
diff --git a/demo/build.gradle b/demo/build.gradle
index c1571ed..0cf1781 100644
--- a/demo/build.gradle
+++ b/demo/build.gradle
@@ -32,6 +32,6 @@ dependencies {
compile 'args4j:args4j:2.32'
compile 'com.h2database:h2:1.4.190'
compile 'org.apache.commons:commons-lang3:3.4'
- testCompile 'junit:junit:4.11'
+ testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
}
diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java
index 03cbf5e..f2f02f2 100644
--- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java
+++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java
@@ -88,7 +88,7 @@ public class SystemTest {
bob.shutdown();
}
- @Test
+ @Test(timeout = 60_000)
public void ensureAliceCanSendMessageToBob() throws Exception {
String originalMessage = UUID.randomUUID().toString();
alice.send(aliceIdentity, new BitmessageAddress(bobIdentity.getAddress()), "Subject", originalMessage);
@@ -102,7 +102,7 @@ public class SystemTest {
.markAsAcknowledged(any());
}
- @Test
+ @Test(timeout = 30_000)
public void ensureBobCanReceiveBroadcastFromAlice() throws Exception {
String originalMessage = UUID.randomUUID().toString();
bob.addSubscribtion(new BitmessageAddress(aliceIdentity.getAddress()));
diff --git a/extensions/build.gradle b/extensions/build.gradle
index d44f900..42b175b 100644
--- a/extensions/build.gradle
+++ b/extensions/build.gradle
@@ -28,7 +28,7 @@ uploadArchives {
dependencies {
compile project(':core')
- testCompile 'junit:junit:4.11'
+ testCompile 'junit:junit:4.12'
testCompile 'org.slf4j:slf4j-simple:1.7.12'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile project(path: ':core', configuration: 'testArtifacts')
diff --git a/networking/build.gradle b/networking/build.gradle
index 984f585..49cafa5 100644
--- a/networking/build.gradle
+++ b/networking/build.gradle
@@ -12,7 +12,7 @@ uploadArchives {
dependencies {
compile project(':core')
- testCompile 'junit:junit:4.11'
+ testCompile 'junit:junit:4.12'
testCompile 'org.slf4j:slf4j-simple:1.7.12'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile project(path: ':core', configuration: 'testArtifacts')
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java
index 3027f04..54897cb 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java
@@ -35,6 +35,7 @@ import java.util.concurrent.ConcurrentLinkedDeque;
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
import static ch.dissem.bitmessage.networking.AbstractConnection.State.*;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
@@ -62,6 +63,8 @@ public abstract class AbstractConnection {
protected long peerNonce;
protected int version;
protected long[] streams;
+ private boolean verackSent;
+ private boolean verackReceived;
public AbstractConnection(InternalContext context, Mode mode,
NetworkAddress node,
@@ -198,7 +201,7 @@ public abstract class AbstractConnection {
return ivCache.containsKey(iv);
}
- protected void cleanupIvCache() {
+ private void cleanupIvCache() {
Long fiveMinutesAgo = UnixTime.now(-5 * MINUTE);
for (Map.Entry entry : ivCache.entrySet()) {
if (entry.getValue() < fiveMinutesAgo) {
@@ -213,16 +216,10 @@ public abstract class AbstractConnection {
handleVersion((Version) payload);
break;
case VERACK:
- switch (mode) {
- case SERVER:
- activateConnection();
- break;
- case CLIENT:
- case SYNC:
- default:
- // NO OP
- break;
+ if (verackSent) {
+ activateConnection();
}
+ verackReceived = true;
break;
case CUSTOM:
MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) payload);
@@ -237,7 +234,7 @@ public abstract class AbstractConnection {
}
}
- protected void activateConnection() {
+ private void activateConnection() {
LOG.info("Successfully established connection with node " + node);
state = ACTIVE;
node.setTime(UnixTime.now());
@@ -272,17 +269,13 @@ public abstract class AbstractConnection {
this.version = version.getVersion();
this.streams = version.getStreams();
+ verackSent = true;
send(new VerAck());
- switch (mode) {
- case SERVER:
- send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build());
- break;
- case CLIENT:
- case SYNC:
- activateConnection();
- break;
- default:
- // NO OP
+ if (mode == SERVER) {
+ send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build());
+ }
+ if (verackReceived) {
+ activateConnection();
}
} else {
LOG.info("Received unsupported version " + version.getVersion() + ", disconnecting.");
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java
index 223722a..36bb463 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java
@@ -19,6 +19,7 @@ package ch.dissem.bitmessage.networking.nio;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.MessagePayload;
import ch.dissem.bitmessage.entity.NetworkMessage;
+import ch.dissem.bitmessage.entity.Version;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.factory.V3MessageReader;
@@ -26,9 +27,12 @@ import ch.dissem.bitmessage.networking.AbstractConnection;
import ch.dissem.bitmessage.ports.NetworkHandler;
import java.nio.ByteBuffer;
-import java.util.*;
-import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.Iterator;
+import java.util.Queue;
+import java.util.Set;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_MESSAGE_SIZE;
/**
@@ -43,6 +47,10 @@ public class ConnectionInfo extends AbstractConnection {
NetworkAddress node, NetworkHandler.MessageListener listener,
Set commonRequestedObjects) {
super(context, mode, node, listener, commonRequestedObjects, false);
+ out.flip();
+ if (mode == CLIENT || mode == SYNC) {
+ send(new Version.Builder().defaults(peerNonce).addrFrom(host).addrRecv(node).build());
+ }
}
public State getState() {
@@ -77,12 +85,8 @@ public class ConnectionInfo extends AbstractConnection {
}
}
- public List getMessages() {
- return reader.getMessages();
- }
-
@Override
protected void send(MessagePayload payload) {
- sendingQueue.addFirst(payload);
+ sendingQueue.add(payload);
}
}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java
index c3689af..cfb6c2e 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java
@@ -37,11 +37,14 @@ import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.*;
-import java.util.concurrent.Future;
+import java.util.concurrent.*;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT;
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
+import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE;
import static ch.dissem.bitmessage.utils.DebugUtils.inc;
+import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool;
import static java.nio.channels.SelectionKey.OP_READ;
import static java.nio.channels.SelectionKey.OP_WRITE;
@@ -51,13 +54,40 @@ import static java.nio.channels.SelectionKey.OP_WRITE;
public class NioNetworkHandler implements NetworkHandler, InternalContext.ContextHolder {
private static final Logger LOG = LoggerFactory.getLogger(NioNetworkHandler.class);
+ private final ExecutorService pool = Executors.newCachedThreadPool(
+ pool("network")
+ .lowPrio()
+ .daemon()
+ .build());
+
private InternalContext ctx;
private Selector selector;
private ServerSocketChannel serverChannel;
@Override
- public Future> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds) {
- return null;
+ public Future synchronize(final InetAddress server, final int port, final MessageListener listener, long timeoutInSeconds) {
+ return pool.submit(new Callable() {
+ @Override
+ public Void call() throws Exception {
+ Set requestedObjects = new HashSet<>();
+ try (SocketChannel channel = SocketChannel.open(new InetSocketAddress(server, port))) {
+ channel.finishConnect();
+ channel.configureBlocking(false);
+ ConnectionInfo connection = new ConnectionInfo(ctx, SYNC,
+ new NetworkAddress.Builder().ip(server).port(port).stream(1).build(),
+ listener, new HashSet());
+ while (channel.isConnected() &&
+ (connection.getState() != ACTIVE
+ || connection.getSendingQueue().isEmpty()
+ || requestedObjects.isEmpty())) {
+ write(requestedObjects, channel, connection);
+ read(channel, connection);
+ Thread.sleep(10);
+ }
+ }
+ return null;
+ }
+ });
}
@Override
@@ -66,7 +96,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
channel.configureBlocking(true);
ByteBuffer buffer = ByteBuffer.allocate(MAX_MESSAGE_SIZE);
new NetworkMessage(request).write(buffer);
- channel.write(buffer);
+ while (buffer.hasRemaining()) {
+ channel.write(buffer);
+ }
buffer.clear();
V3MessageReader reader = new V3MessageReader();
@@ -106,34 +138,74 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
throw new ApplicationException(e);
}
final Set requestedObjects = new HashSet<>();
- new Thread(new Runnable() {
+ start("connection listener", new Runnable() {
@Override
public void run() {
try {
serverChannel = ServerSocketChannel.open();
- serverChannel.bind(new InetSocketAddress(ctx.getPort()));
-
- SocketChannel accepted = serverChannel.accept();
- accepted.configureBlocking(false);
- // FIXME: apparently it isn't good practice to generally listen for OP_WRITE
- accepted.register(selector, OP_READ | OP_WRITE).attach(
- new ConnectionInfo(ctx, SERVER,
- new NetworkAddress.Builder().address(accepted.getRemoteAddress()).stream(1).build(),
- listener,
- requestedObjects
- ));
+ serverChannel.socket().bind(new InetSocketAddress(ctx.getPort()));
+ while (selector.isOpen() && serverChannel.isOpen()) {
+ try {
+ SocketChannel accepted = serverChannel.accept();
+ accepted.configureBlocking(false);
+ accepted.register(selector, OP_READ | OP_WRITE,
+ new ConnectionInfo(ctx, SERVER,
+ new NetworkAddress.Builder().address(accepted.getRemoteAddress()).stream(1).build(),
+ listener,
+ requestedObjects
+ ));
+ } catch (AsynchronousCloseException ignore) {
+ } catch (IOException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
} catch (ClosedSelectorException | AsynchronousCloseException ignore) {
} catch (IOException e) {
throw new ApplicationException(e);
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ throw e;
}
}
- }, "Server").start();
- new Thread(new Runnable() {
+ });
+
+ start("connection starter", new Runnable() {
+ @Override
+ public void run() {
+ while (selector.isOpen()) {
+ List addresses = ctx.getNodeRegistry().getKnownAddresses(
+ 2, ctx.getStreams());
+ for (NetworkAddress address : addresses) {
+ try {
+ SocketChannel channel = SocketChannel.open(
+ new InetSocketAddress(address.toInetAddress(), address.getPort()));
+ channel.configureBlocking(false);
+ channel.register(selector, OP_READ | OP_WRITE,
+ new ConnectionInfo(ctx, CLIENT,
+ address,
+ listener,
+ requestedObjects
+ ));
+ } catch (AsynchronousCloseException ignore) {
+ } catch (IOException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
+ try {
+ Thread.sleep(30_000);
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+ }
+ });
+
+ start("processor", new Runnable() {
@Override
public void run() {
try {
while (selector.isOpen()) {
- // TODO: establish outgoing connections
+ selector.select(1000);
Iterator keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
@@ -141,22 +213,16 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
if (key.attachment() instanceof ConnectionInfo) {
SocketChannel channel = (SocketChannel) key.channel();
ConnectionInfo connection = (ConnectionInfo) key.attachment();
-
if (key.isWritable()) {
- if (connection.getOutBuffer().hasRemaining()) {
- channel.write(connection.getOutBuffer());
- }
- while (!connection.getOutBuffer().hasRemaining() && !connection.getSendingQueue().isEmpty()) {
- MessagePayload payload = connection.getSendingQueue().poll();
- if (payload instanceof GetData) {
- requestedObjects.addAll(((GetData) payload).getInventory());
- }
- new NetworkMessage(payload).write(connection.getOutBuffer());
- }
+ write(requestedObjects, channel, connection);
}
if (key.isReadable()) {
- channel.read(connection.getInBuffer());
- connection.updateReader();
+ read(channel, connection);
+ }
+ if (connection.getSendingQueue().isEmpty()) {
+ key.interestOps(OP_READ);
+ } else {
+ key.interestOps(OP_READ | OP_WRITE);
}
}
keyIterator.remove();
@@ -168,13 +234,52 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
throw new ApplicationException(e);
}
}
- }, "Connections").start();
+ });
+ }
+
+ private static void write(Set requestedObjects, SocketChannel channel, ConnectionInfo connection)
+ throws IOException {
+ if (!connection.getSendingQueue().isEmpty()) {
+ ByteBuffer buffer = connection.getOutBuffer();
+ if (buffer.hasRemaining()) {
+ channel.write(buffer);
+ }
+ while (!buffer.hasRemaining()
+ && !connection.getSendingQueue().isEmpty()) {
+ buffer.clear();
+ MessagePayload payload = connection.getSendingQueue().poll();
+ if (payload instanceof GetData) {
+ requestedObjects.addAll(((GetData) payload).getInventory());
+ }
+ new NetworkMessage(payload).write(buffer);
+ buffer.flip();
+ if (buffer.hasRemaining()) {
+ channel.write(buffer);
+ }
+ }
+ }
+ }
+
+ private static void read(SocketChannel channel, ConnectionInfo connection) throws IOException {
+ ByteBuffer buffer = connection.getInBuffer();
+ while (channel.read(buffer) > 0) {
+ buffer.flip();
+ connection.updateReader();
+ buffer.compact();
+ }
+ }
+
+ private void start(String threadName, Runnable runnable) {
+ Thread thread = new Thread(runnable, threadName);
+ thread.setDaemon(true);
+ thread.setPriority(Thread.MIN_PRIORITY);
+ thread.start();
}
@Override
public void stop() {
try {
- serverChannel.close();
+ serverChannel.socket().close();
for (SelectionKey key : selector.keys()) {
key.channel().close();
}
diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
index 2cadfb0..1b7994a 100644
--- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
+++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java
@@ -22,14 +22,23 @@ import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.entity.MessagePayload;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.NodeException;
+import ch.dissem.bitmessage.networking.nio.NioNetworkHandler;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Property;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.Future;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
@@ -42,6 +51,7 @@ import static org.mockito.Mockito.mock;
/**
* FIXME: there really should be sensible tests for the network handler
*/
+@RunWith(Parameterized.class)
public class NetworkHandlerTest {
private static final Logger LOG = LoggerFactory.getLogger(NetworkHandlerTest.class);
private static NetworkAddress peerAddress = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(6001).build();
@@ -51,7 +61,27 @@ public class NetworkHandlerTest {
private BitmessageContext peer;
private BitmessageContext node;
- private NetworkHandler networkHandler;
+
+ private final NetworkHandler peerNetworkHandler;
+ private final NetworkHandler nodeNetworkHandler;
+
+ @Rule
+ public final TestRule timeout = new DisableOnDebug(Timeout.seconds(5));
+
+ public NetworkHandlerTest(NetworkHandler peer, NetworkHandler node) {
+ this.peerNetworkHandler = peer;
+ this.nodeNetworkHandler = node;
+ }
+
+ @Parameterized.Parameters
+ public static List