From 2fae90c43373f3255226bfa5870a9e7d11b8e2bb Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Sun, 8 Nov 2015 19:29:26 +0100
Subject: [PATCH 01/20] 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
+ * 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 19/20] 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 20/20] 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
[![Apache 2](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](https://raw.githubusercontent.com/Dissem/Jabit/master/LICENSE)
[![Visit our IRC channel](https://img.shields.io/badge/irc-%23jabit-blue.svg)](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
[![Build Status](https://travis-ci.org/Dissem/Jabit.svg?branch=master)](https://travis-ci.org/Dissem/Jabit)
[![Code Quality](https://img.shields.io/codacy/e9938d2adbb74a0db553115bef692ff3/master.svg)](https://www.codacy.com/app/chrigu-meyer/Jabit/dashboard?bid=3144281)