diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
index a065de9..72da50d 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
@@ -22,10 +22,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
-import ch.dissem.bitmessage.repository.JdbcAddressRepository;
-import ch.dissem.bitmessage.repository.JdbcConfig;
-import ch.dissem.bitmessage.repository.JdbcInventory;
-import ch.dissem.bitmessage.repository.JdbcMessageRepository;
+import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,6 +47,7 @@ public class Application {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
+ .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.networkHandler(new DefaultNetworkHandler())
.security(new BouncySecurity())
.port(48444)
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
index ac90e88..6dbfc14 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
@@ -51,6 +51,7 @@ public class Main {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
+ .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.networkHandler(new DefaultNetworkHandler())
.security(new BouncySecurity())
.port(48444)
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 9f4d9a3..1c4295e 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -16,9 +16,7 @@
package ch.dissem.bitmessage;
-import ch.dissem.bitmessage.entity.BitmessageAddress;
-import ch.dissem.bitmessage.entity.ObjectMessage;
-import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.*;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
@@ -33,6 +31,8 @@ import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.util.Arrays;
+import java.util.Timer;
+import java.util.TimerTask;
import java.util.concurrent.*;
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
@@ -66,6 +66,8 @@ public class BitmessageContext {
private final Listener listener;
private final NetworkHandler.MessageListener networkListener;
+ private final boolean sendPubkeyOnIdentityCreation;
+
private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder);
listener = builder.listener;
@@ -74,10 +76,19 @@ public class BitmessageContext {
// As this thread is used for parts that do POW, which itself uses parallel threads, only
// one should be executed at any time.
pool = Executors.newFixedThreadPool(1);
+
+ sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
+
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ ctx.getProofOfWorkService().doMissingProofOfWork();
+ }
+ }, 30_000); // After 30 seconds
}
public AddressRepository addresses() {
- return ctx.getAddressRepo();
+ return ctx.getAddressRepository();
}
public MessageRepository messages() {
@@ -92,18 +103,21 @@ public class BitmessageContext {
ctx.getNetworkExtraBytes(),
features
));
- ctx.getAddressRepo().save(identity);
- pool.submit(new Runnable() {
- @Override
- public void run() {
- ctx.sendPubkey(identity, identity.getStream());
- }
- });
+ ctx.getAddressRepository().save(identity);
+ if (sendPubkeyOnIdentityCreation) {
+ pool.submit(new Runnable() {
+ @Override
+ public void run() {
+ ctx.sendPubkey(identity, identity.getStream());
+ }
+ });
+ }
return identity;
}
public void addDistributedMailingList(String address, String alias) {
// TODO
+ throw new RuntimeException("not implemented");
}
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
@@ -122,9 +136,7 @@ public class BitmessageContext {
from,
from,
Factory.getBroadcast(from, msg),
- +2 * DAY,
- 0,
- 0
+ +2 * DAY
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
@@ -161,9 +173,7 @@ public class BitmessageContext {
from,
to,
new Msg(msg),
- +2 * DAY,
- ctx.getNonceTrialsPerByte(to),
- ctx.getExtraBytes(to)
+ +2 * DAY
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
@@ -178,9 +188,7 @@ public class BitmessageContext {
requestingIdentity,
address,
new GetPubkey(address),
- +28 * DAY,
- ctx.getNetworkNonceTrialsPerByte(),
- ctx.getNetworkExtraBytes()
+ +28 * DAY
);
}
@@ -213,6 +221,19 @@ public class BitmessageContext {
}
}
+ /**
+ * Send a custom message to a specific node (that should implement handling for this message type) and returns
+ * the response, which in turn is expected to be a {@link CustomMessage}.
+ *
+ * @param server the node's address
+ * @param port the node's port
+ * @param request the request
+ * @return the response
+ */
+ public CustomMessage send(InetAddress server, int port, CustomMessage request) {
+ return ctx.getNetworkHandler().send(server, port, request);
+ }
+
public void cleanup() {
ctx.getInventory().cleanup();
}
@@ -222,7 +243,7 @@ public class BitmessageContext {
}
public void addContact(BitmessageAddress contact) {
- ctx.getAddressRepo().save(contact);
+ ctx.getAddressRepository().save(contact);
tryToFindMatchingPubkey(contact);
if (contact.getPubkey() == null) {
ctx.requestPubkey(contact);
@@ -239,7 +260,7 @@ public class BitmessageContext {
v4Pubkey.decrypt(address.getPublicDecryptionKey());
if (object.isSignatureValid(v4Pubkey)) {
address.setPubkey(v4Pubkey);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
break;
} else {
LOG.info("Found pubkey for " + address + " but signature is invalid");
@@ -248,7 +269,7 @@ public class BitmessageContext {
} else {
if (Arrays.equals(pubkey.getRipe(), address.getRipe())) {
address.setPubkey(pubkey);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
break;
}
}
@@ -260,7 +281,7 @@ public class BitmessageContext {
public void addSubscribtion(BitmessageAddress address) {
address.setSubscribed(true);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
tryToFindBroadcastsForAddress(address);
}
@@ -283,6 +304,14 @@ public class BitmessageContext {
);
}
+ /**
+ * Returns the {@link InternalContext} - normally you wouldn't need it,
+ * unless you are doing something crazy with the protocol.
+ */
+ public InternalContext internals() {
+ return ctx;
+ }
+
public interface Listener {
void receive(Plaintext plaintext);
}
@@ -294,12 +323,16 @@ public class BitmessageContext {
NetworkHandler networkHandler;
AddressRepository addressRepo;
MessageRepository messageRepo;
+ ProofOfWorkRepository proofOfWorkRepository;
ProofOfWorkEngine proofOfWorkEngine;
Security security;
MessageCallback messageCallback;
+ CustomCommandHandler customCommandHandler;
Listener listener;
int connectionLimit = 150;
long connectionTTL = 12 * HOUR;
+ boolean sendPubkeyOnIdentityCreation = true;
+ long pubkeyTTL = 28;
public Builder() {
}
@@ -334,6 +367,11 @@ public class BitmessageContext {
return this;
}
+ public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) {
+ this.proofOfWorkRepository = proofOfWorkRepository;
+ return this;
+ }
+
public Builder security(Security security) {
this.security = security;
return this;
@@ -344,6 +382,11 @@ public class BitmessageContext {
return this;
}
+ public Builder customCommandHandler(CustomCommandHandler handler) {
+ this.customCommandHandler = handler;
+ return this;
+ }
+
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
this.proofOfWorkEngine = proofOfWorkEngine;
return this;
@@ -364,12 +407,37 @@ public class BitmessageContext {
return this;
}
+ /**
+ * By default a client will send the public key when an identity is being created. On weaker devices
+ * this behaviour might not be desirable.
+ */
+ public Builder doNotSendPubkeyOnIdentityCreation() {
+ this.sendPubkeyOnIdentityCreation = false;
+ return this;
+ }
+
+ /**
+ * Time to live in seconds for public keys the client sends. Defaults to the maximum of 28 days,
+ * but on weak devices smaller values might be desirable.
+ *
+ * Please be aware that this might cause some problems where you can't receive a message (the
+ * sender can't receive your public key) in some special situations. Also note that it's probably
+ * not a good idea to set it too low.
+ *
+ */
+ public Builder pubkeyTTL(long days) {
+ if (days < 0 || days > 28 * DAY) throw new IllegalArgumentException("TTL must be between 1 and 28 days");
+ this.pubkeyTTL = days;
+ return this;
+ }
+
public BitmessageContext build() {
nonNull("inventory", inventory);
nonNull("nodeRegistry", nodeRegistry);
nonNull("networkHandler", networkHandler);
nonNull("addressRepo", addressRepo);
nonNull("messageRepo", messageRepo);
+ nonNull("proofOfWorkRepo", proofOfWorkRepository);
if (proofOfWorkEngine == null) {
proofOfWorkEngine = new MultiThreadedPOWEngine();
}
@@ -392,6 +460,14 @@ public class BitmessageContext {
}
};
}
+ if (customCommandHandler == null) {
+ customCommandHandler = new CustomCommandHandler() {
+ @Override
+ public MessagePayload handle(CustomMessage request) {
+ throw new RuntimeException("Received custom request, but no custom command handler configured.");
+ }
+ };
+ }
return new BitmessageContext(this);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index e069704..eb22f03 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -69,7 +69,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
- BitmessageAddress identity = ctx.getAddressRepo().findIdentity(getPubkey.getRipeTag());
+ BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
if (identity != null && identity.getPrivateKey() != null) {
LOG.info("Got pubkey request for identity " + identity);
// FIXME: only send pubkey if it wasn't sent in the last 28 days
@@ -82,17 +82,17 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
try {
if (pubkey instanceof V4Pubkey) {
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
- address = ctx.getAddressRepo().findContact(v4Pubkey.getTag());
+ address = ctx.getAddressRepository().findContact(v4Pubkey.getTag());
if (address != null) {
v4Pubkey.decrypt(address.getPublicDecryptionKey());
}
} else {
- address = ctx.getAddressRepo().findContact(pubkey.getRipe());
+ address = ctx.getAddressRepository().findContact(pubkey.getRipe());
}
if (address != null) {
address.setPubkey(pubkey);
LOG.info("Got pubkey for contact " + address);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
List messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
LOG.info("Sending " + messages.size() + " messages for contact " + address);
for (Plaintext msg : messages) {
@@ -102,9 +102,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
msg.getFrom(),
msg.getTo(),
new Msg(msg),
- +2 * DAY,
- ctx.getNonceTrialsPerByte(msg.getTo()),
- ctx.getExtraBytes(msg.getTo())
+ +2 * DAY
);
msg.setStatus(SENT);
ctx.getMessageRepository().save(msg);
@@ -115,7 +113,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
protected void receive(ObjectMessage object, Msg msg) throws IOException {
- for (BitmessageAddress identity : ctx.getAddressRepo().getIdentities()) {
+ for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) {
try {
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
msg.getPlaintext().setTo(identity);
@@ -136,7 +134,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException {
byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null;
- for (BitmessageAddress subscription : ctx.getAddressRepo().getSubscriptions(broadcast.getVersion())) {
+ for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) {
if (tag != null && !Arrays.equals(tag, subscription.getTag())) {
continue;
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 7a89978..1fe8007 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -16,7 +16,9 @@
package ch.dissem.bitmessage;
-import ch.dissem.bitmessage.entity.*;
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.Encrypted;
+import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Broadcast;
import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
@@ -29,8 +31,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.TreeSet;
-import static ch.dissem.bitmessage.utils.UnixTime.DAY;
-
/**
* The internal context should normally only be used for port implementations. If you need it in your client
* implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
@@ -48,14 +48,18 @@ public class InternalContext {
private final NetworkHandler networkHandler;
private final AddressRepository addressRepository;
private final MessageRepository messageRepository;
+ private final ProofOfWorkRepository proofOfWorkRepository;
private final ProofOfWorkEngine proofOfWorkEngine;
private final MessageCallback messageCallback;
+ private final CustomCommandHandler customCommandHandler;
+ private final ProofOfWorkService proofOfWorkService;
private final TreeSet streams = new TreeSet<>();
private final int port;
private final long clientNonce;
private final long networkNonceTrialsPerByte = 1000;
private final long networkExtraBytes = 1000;
+ private final long pubkeyTTL;
private long connectionTTL;
private int connectionLimit;
@@ -66,12 +70,16 @@ public class InternalContext {
this.networkHandler = builder.networkHandler;
this.addressRepository = builder.addressRepo;
this.messageRepository = builder.messageRepo;
+ this.proofOfWorkRepository = builder.proofOfWorkRepository;
+ this.proofOfWorkService = new ProofOfWorkService();
this.proofOfWorkEngine = builder.proofOfWorkEngine;
this.clientNonce = security.randomNonce();
this.messageCallback = builder.messageCallback;
+ this.customCommandHandler = builder.customCommandHandler;
this.port = builder.port;
this.connectionLimit = builder.connectionLimit;
this.connectionTTL = builder.connectionTTL;
+ this.pubkeyTTL = builder.pubkeyTTL;
Singleton.initialize(security);
@@ -86,7 +94,9 @@ public class InternalContext {
streams.add(1L);
}
- init(security, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
+ init(security, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
+ proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine,
+ messageCallback, customCommandHandler);
for (BitmessageAddress identity : addressRepository.getIdentities()) {
streams.add(identity.getStream());
}
@@ -116,7 +126,7 @@ public class InternalContext {
return networkHandler;
}
- public AddressRepository getAddressRepo() {
+ public AddressRepository getAddressRepository() {
return addressRepository;
}
@@ -124,10 +134,18 @@ public class InternalContext {
return messageRepository;
}
+ public ProofOfWorkRepository getProofOfWorkRepository() {
+ return proofOfWorkRepository;
+ }
+
public ProofOfWorkEngine getProofOfWorkEngine() {
return proofOfWorkEngine;
}
+ public ProofOfWorkService getProofOfWorkService() {
+ return proofOfWorkService;
+ }
+
public long[] getStreams() {
long[] result = new long[streams.size()];
int i = 0;
@@ -145,22 +163,12 @@ public class InternalContext {
return networkNonceTrialsPerByte;
}
- public long getNonceTrialsPerByte(BitmessageAddress address) {
- long nonceTrialsPerByte = address.getPubkey().getNonceTrialsPerByte();
- return networkNonceTrialsPerByte > nonceTrialsPerByte ? networkNonceTrialsPerByte : nonceTrialsPerByte;
- }
-
public long getNetworkExtraBytes() {
return networkExtraBytes;
}
- public long getExtraBytes(BitmessageAddress address) {
- long extraBytes = address.getPubkey().getExtraBytes();
- return networkExtraBytes > extraBytes ? networkExtraBytes : extraBytes;
- }
-
public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
- final long timeToLive, final long nonceTrialsPerByte, final long extraBytes) {
+ final long timeToLive) {
try {
if (to == null) to = from;
long expires = UnixTime.now(+timeToLive);
@@ -179,22 +187,7 @@ public class InternalContext {
object.encrypt(to.getPubkey());
}
messageCallback.proofOfWorkStarted(payload);
- security.doProofOfWork(object, nonceTrialsPerByte, extraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- object.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(payload);
- if (payload instanceof PlaintextHolder) {
- Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
- plaintext.setInventoryVector(object.getInventoryVector());
- messageRepository.save(plaintext);
- }
- inventory.storeObject(object);
- networkHandler.offer(object.getInventoryVector());
- messageCallback.messageOffered(payload, object.getInventoryVector());
- }
- });
+ proofOfWorkService.doProofOfWork(to, object);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -202,7 +195,7 @@ public class InternalContext {
public void sendPubkey(final BitmessageAddress identity, final long targetStream) {
try {
- long expires = UnixTime.now(+28 * DAY);
+ long expires = UnixTime.now(pubkeyTTL);
LOG.info("Expires at " + expires);
final ObjectMessage response = new ObjectMessage.Builder()
.stream(targetStream)
@@ -212,25 +205,15 @@ public class InternalContext {
response.sign(identity.getPrivateKey());
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
messageCallback.proofOfWorkStarted(identity.getPubkey());
- security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- response.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(identity.getPubkey());
- inventory.storeObject(response);
- networkHandler.offer(response.getInventoryVector());
- // TODO: save that the pubkey was just sent, and on which stream!
- messageCallback.messageOffered(identity.getPubkey(), response.getInventoryVector());
- }
- });
+ // TODO: remember that the pubkey is just about to be sent, and on which stream!
+ proofOfWorkService.doProofOfWork(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void requestPubkey(final BitmessageAddress contact) {
- long expires = UnixTime.now(+2 * DAY);
+ long expires = UnixTime.now(+pubkeyTTL);
LOG.info("Expires at " + expires);
final ObjectMessage response = new ObjectMessage.Builder()
.stream(contact.getStream())
@@ -238,17 +221,7 @@ public class InternalContext {
.payload(new GetPubkey(contact))
.build();
messageCallback.proofOfWorkStarted(response.getPayload());
- security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- response.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(response.getPayload());
- inventory.storeObject(response);
- networkHandler.offer(response.getInventoryVector());
- messageCallback.messageOffered(response.getPayload(), response.getInventoryVector());
- }
- });
+ proofOfWorkService.doProofOfWork(response);
}
public long getClientNonce() {
@@ -263,6 +236,10 @@ public class InternalContext {
return connectionLimit;
}
+ public CustomCommandHandler getCustomCommandHandler() {
+ return customCommandHandler;
+ }
+
public interface ContextHolder {
void setContext(InternalContext context);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
new file mode 100644
index 0000000..82c384c
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -0,0 +1,82 @@
+package ch.dissem.bitmessage;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.PlaintextHolder;
+import ch.dissem.bitmessage.ports.MessageRepository;
+import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
+import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
+import ch.dissem.bitmessage.ports.Security;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
+/**
+ * @author Christian Basler
+ */
+public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
+ private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class);
+
+ private Security security;
+ private InternalContext ctx;
+ private ProofOfWorkRepository powRepo;
+ private MessageRepository messageRepo;
+
+ public void doMissingProofOfWork() {
+ List items = powRepo.getItems();
+ if (items.isEmpty()) return;
+
+ LOG.info("Doing POW for " + items.size() + " tasks.");
+ for (byte[] initialHash : items) {
+ ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
+ security.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this);
+ }
+ }
+
+ public void doProofOfWork(ObjectMessage object) {
+ doProofOfWork(null, object);
+ }
+
+ public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
+ long nonceTrialsPerByte = recipient == null ?
+ ctx.getNetworkNonceTrialsPerByte() : recipient.getPubkey().getNonceTrialsPerByte();
+ long extraBytes = recipient == null ?
+ ctx.getNetworkExtraBytes() : recipient.getPubkey().getExtraBytes();
+
+ powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
+ if (object.getPayload() instanceof PlaintextHolder) {
+ Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
+ plaintext.setInitialHash(security.getInitialHash(object));
+ messageRepo.save(plaintext);
+ }
+ security.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this);
+ }
+
+ @Override
+ public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
+ ObjectMessage object = powRepo.getItem(initialHash).object;
+ object.setNonce(nonce);
+// messageCallback.proofOfWorkCompleted(payload);
+ Plaintext plaintext = messageRepo.getMessage(initialHash);
+ if (plaintext != null) {
+ plaintext.setInventoryVector(object.getInventoryVector());
+ messageRepo.save(plaintext);
+ }
+ ctx.getInventory().storeObject(object);
+ ctx.getProofOfWorkRepository().removeObject(initialHash);
+ ctx.getNetworkHandler().offer(object.getInventoryVector());
+// messageCallback.messageOffered(payload, object.getInventoryVector());
+ }
+
+ @Override
+ public void setContext(InternalContext ctx) {
+ this.ctx = ctx;
+ this.security = security();
+ this.powRepo = ctx.getProofOfWorkRepository();
+ this.messageRepo = ctx.getMessageRepository();
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
index 0441e6e..931776c 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
@@ -87,7 +87,7 @@ public class BitmessageAddress implements Serializable {
}
}
- BitmessageAddress(Pubkey publicKey) {
+ public BitmessageAddress(Pubkey publicKey) {
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
this.pubkey = publicKey;
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
new file mode 100644
index 0000000..5702b6e
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.entity;
+
+import ch.dissem.bitmessage.utils.AccessCounter;
+import ch.dissem.bitmessage.utils.Encode;
+
+import java.io.*;
+
+import static ch.dissem.bitmessage.utils.Decode.bytes;
+import static ch.dissem.bitmessage.utils.Decode.varString;
+
+/**
+ * @author Christian Basler
+ */
+public class CustomMessage implements MessagePayload {
+ public static final String COMMAND_ERROR = "ERROR";
+
+ private final String command;
+ private final byte[] data;
+
+ public CustomMessage(String command) {
+ this.command = command;
+ this.data = null;
+ }
+
+ public CustomMessage(String command, byte[] data) {
+ this.command = command;
+ this.data = data;
+ }
+
+ public static CustomMessage read(InputStream in, int length) throws IOException {
+ AccessCounter counter = new AccessCounter();
+ return new CustomMessage(varString(in, counter), bytes(in, length - counter.length()));
+ }
+
+ @Override
+ public Command getCommand() {
+ return Command.CUSTOM;
+ }
+
+ public String getCustomCommand() {
+ return command;
+ }
+
+ public byte[] getData() {
+ if (data != null) {
+ return data;
+ } else {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ write(out);
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ if (data != null) {
+ Encode.varString(command, out);
+ out.write(data);
+ } else {
+ throw new RuntimeException("Tried to write custom message without data. " +
+ "Programmer: did you forget to override #write()?");
+ }
+ }
+
+ public boolean isError() {
+ return COMMAND_ERROR.equals(command);
+ }
+
+ public static CustomMessage error(String message) {
+ try {
+ return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
index e6f6f0a..994952b 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
@@ -23,6 +23,6 @@ public interface MessagePayload extends Streamable {
Command getCommand();
enum Command {
- VERSION, VERACK, ADDR, INV, GETDATA, OBJECT
+ VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
}
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
index 128084e..9e89c42 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
@@ -156,7 +156,11 @@ public class ObjectMessage implements MessagePayload {
@Override
public void write(OutputStream out) throws IOException {
- out.write(nonce);
+ if (nonce != null) {
+ out.write(nonce);
+ } else {
+ out.write(new byte[8]);
+ }
out.write(getPayloadBytesWithoutNonce());
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index eb0a60f..fbd5d48 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -44,6 +44,7 @@ public class Plaintext implements Streamable {
private Long received;
private Set