From 99266712faeff0b392557512f73a751dd4fa7d7b Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Sat, 28 Nov 2015 20:27:05 +0100
Subject: [PATCH 1/6] Some extensions for server POW
---
.../dissem/bitmessage/BitmessageContext.java | 18 ++-
.../ch/dissem/bitmessage/InternalContext.java | 6 +
.../bitmessage/entity/BitmessageAddress.java | 2 +-
.../bitmessage/entity/CustomMessage.java | 68 +++++++++
.../bitmessage/entity/MessagePayload.java | 2 +-
.../bitmessage/entity/payload/Broadcast.java | 1 -
.../bitmessage/entity/payload/CryptoBox.java | 9 +-
.../bitmessage/factory/V3MessageFactory.java | 6 +
.../ports/CustomCommandHandler.java | 27 ++++
.../ch/dissem/bitmessage/utils/Encode.java | 16 +-
extensions/build.gradle | 36 +++++
.../extensions/CryptoCustomMessage.java | 139 ++++++++++++++++++
.../extensions/pow/ProofOfWorkRequest.java | 86 +++++++++++
.../extensions/CryptoCustomMessageTest.java | 58 ++++++++
.../bitmessage/networking/Connection.java | 6 +
.../bitmessage/repository/JdbcHelper.java | 2 +-
settings.gradle | 2 +
17 files changed, 472 insertions(+), 12 deletions(-)
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java
create mode 100644 extensions/build.gradle
create mode 100644 extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
create mode 100644 extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
create mode 100644 extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 9f4d9a3..9d4abd7 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -16,9 +16,7 @@
package ch.dissem.bitmessage;
-import ch.dissem.bitmessage.entity.BitmessageAddress;
-import ch.dissem.bitmessage.entity.ObjectMessage;
-import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.*;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
@@ -297,6 +295,7 @@ public class BitmessageContext {
ProofOfWorkEngine proofOfWorkEngine;
Security security;
MessageCallback messageCallback;
+ CustomCommandHandler customCommandHandler;
Listener listener;
int connectionLimit = 150;
long connectionTTL = 12 * HOUR;
@@ -344,6 +343,11 @@ public class BitmessageContext {
return this;
}
+ public Builder customCommandHandler(CustomCommandHandler handler) {
+ this.customCommandHandler = handler;
+ return this;
+ }
+
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
this.proofOfWorkEngine = proofOfWorkEngine;
return this;
@@ -392,6 +396,14 @@ public class BitmessageContext {
}
};
}
+ if (customCommandHandler == null) {
+ customCommandHandler = new CustomCommandHandler() {
+ @Override
+ public MessagePayload handle(CustomMessage request) {
+ throw new RuntimeException("Received custom request, but no custom command handler configured.");
+ }
+ };
+ }
return new BitmessageContext(this);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 7a89978..95cd8d8 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -50,6 +50,7 @@ public class InternalContext {
private final MessageRepository messageRepository;
private final ProofOfWorkEngine proofOfWorkEngine;
private final MessageCallback messageCallback;
+ private final CustomCommandHandler customCommandHandler;
private final TreeSet streams = new TreeSet<>();
private final int port;
@@ -69,6 +70,7 @@ public class InternalContext {
this.proofOfWorkEngine = builder.proofOfWorkEngine;
this.clientNonce = security.randomNonce();
this.messageCallback = builder.messageCallback;
+ this.customCommandHandler = builder.customCommandHandler;
this.port = builder.port;
this.connectionLimit = builder.connectionLimit;
this.connectionTTL = builder.connectionTTL;
@@ -263,6 +265,10 @@ public class InternalContext {
return connectionLimit;
}
+ public CustomCommandHandler getCustomCommandHandler() {
+ return customCommandHandler;
+ }
+
public interface ContextHolder {
void setContext(InternalContext context);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
index 0441e6e..931776c 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
@@ -87,7 +87,7 @@ public class BitmessageAddress implements Serializable {
}
}
- BitmessageAddress(Pubkey publicKey) {
+ public BitmessageAddress(Pubkey publicKey) {
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
this.pubkey = publicKey;
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
new file mode 100644
index 0000000..b31c9f5
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.entity;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import static ch.dissem.bitmessage.utils.Decode.bytes;
+
+/**
+ * @author Christian Basler
+ */
+public class CustomMessage implements MessagePayload {
+ private final byte[] data;
+
+ public CustomMessage() {
+ this.data = null;
+ }
+
+ public CustomMessage(byte[] data) {
+ this.data = data;
+ }
+
+ public static MessagePayload read(InputStream in, int length) throws IOException {
+ return new CustomMessage(bytes(in, length));
+ }
+
+ @Override
+ public Command getCommand() {
+ return Command.CUSTOM;
+ }
+
+ public byte[] getData() throws IOException {
+ if (data != null) {
+ return data;
+ } else {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ write(out);
+ return out.toByteArray();
+ }
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ if (data != null) {
+ out.write(data);
+ } else {
+ throw new RuntimeException("Tried to write custom message without data. " +
+ "Programmer: did you forget to override #write()?");
+ }
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
index e6f6f0a..994952b 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java
@@ -23,6 +23,6 @@ public interface MessagePayload extends Streamable {
Command getCommand();
enum Command {
- VERSION, VERACK, ADDR, INV, GETDATA, OBJECT
+ VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
}
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
index bf5ff7f..47bf539 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java
@@ -21,7 +21,6 @@ import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
-import ch.dissem.bitmessage.ports.Security;
import java.io.IOException;
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
index a870b32..fe45ac5 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java
@@ -38,7 +38,14 @@ public class CryptoBox implements Streamable {
private final byte[] mac;
private byte[] encrypted;
+ private long addressVersion;
+
+
public CryptoBox(Streamable data, byte[] K) throws IOException {
+ this(Encode.bytes(data), K);
+ }
+
+ public CryptoBox(byte[] data, byte[] K) throws IOException {
curveType = 0x02CA;
// 1. The destination public key is called K.
@@ -58,7 +65,7 @@ public class CryptoBox implements Streamable {
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
- encrypted = security().crypt(true, Encode.bytes(data), key_e, initializationVector);
+ encrypted = security().crypt(true, data, key_e, initializationVector);
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
mac = calculateMac(key_m);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
index 8dca6d2..9e15b3d 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java
@@ -73,12 +73,18 @@ class V3MessageFactory {
return parseGetData(stream);
case "object":
return readObject(stream, length);
+ case "custom":
+ return readCustom(stream, length);
default:
LOG.debug("Unknown command: " + command);
return null;
}
}
+ private static MessagePayload readCustom(InputStream in, int length) throws IOException {
+ return CustomMessage.read(in, length);
+ }
+
public static ObjectMessage readObject(InputStream in, int length) throws IOException {
AccessCounter counter = new AccessCounter();
byte nonce[] = Decode.bytes(in, 8, counter);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java
new file mode 100644
index 0000000..8e49586
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.ports;
+
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.MessagePayload;
+
+/**
+ * @author Christian Basler
+ */
+public interface CustomCommandHandler {
+ MessagePayload handle(CustomMessage request);
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
index a78d03f..2cdc262 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java
@@ -103,15 +103,23 @@ public class Encode {
inc(counter, 8);
}
- public static void varString(String value, OutputStream stream) throws IOException {
+ public static void varString(String value, OutputStream out) throws IOException {
byte[] bytes = value.getBytes("utf-8");
- // FIXME: technically, it says the length in characters, but I think this one might be correct
+ // Technically, it says the length in characters, but I think this one might be correct.
+ // It doesn't really matter, as only ASCII characters are being used.
// see also Decode#varString()
- varInt(bytes.length, stream);
- stream.write(bytes);
+ varInt(bytes.length, out);
+ out.write(bytes);
+ }
+
+ public static void varBytes(byte[] data, OutputStream out) throws IOException {
+ varInt(data.length, out);
+ out.write(data);
}
/**
+ * Serializes a {@link Streamable} object and returns the byte array.
+ *
* @param streamable the object to be serialized
* @return an array of bytes representing the given streamable object.
* @throws IOException if an I/O error occurs.
diff --git a/extensions/build.gradle b/extensions/build.gradle
new file mode 100644
index 0000000..0ae9fd4
--- /dev/null
+++ b/extensions/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.project {
+ name 'Jabit Extensions'
+ artifactId = 'jabit-extensions'
+ description 'Protocol extensions used for some extended features, e.g. server and mobile client.'
+ }
+ }
+ }
+}
+
+dependencies {
+ compile project(':domain')
+ testCompile 'junit:junit:4.11'
+ testCompile 'org.slf4j:slf4j-simple:1.7.12'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile project(path: ':domain', configuration: 'testArtifacts')
+ testCompile project(':security-bc')
+}
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
new file mode 100644
index 0000000..5d82f4d
--- /dev/null
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.extensions;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.Streamable;
+import ch.dissem.bitmessage.entity.payload.CryptoBox;
+import ch.dissem.bitmessage.entity.payload.Pubkey;
+import ch.dissem.bitmessage.exception.DecryptionFailedException;
+import ch.dissem.bitmessage.factory.Factory;
+import ch.dissem.bitmessage.utils.Encode;
+
+import java.io.*;
+
+import static ch.dissem.bitmessage.utils.Decode.*;
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
+/**
+ * A {@link CustomMessage} implementation that contains signed and encrypted data.
+ *
+ * @author Christian Basler
+ */
+public class CryptoCustomMessage extends CustomMessage {
+ private final Reader dataReader;
+ private CryptoBox container;
+ private BitmessageAddress sender;
+ private T data;
+
+ public CryptoCustomMessage(T data) throws IOException {
+ this.data = data;
+ this.dataReader = null;
+ }
+
+ private CryptoCustomMessage(CryptoBox container, Reader dataReader) {
+ this.container = container;
+ this.dataReader = dataReader;
+ }
+
+ public static CryptoCustomMessage read(byte[] data, Reader dataReader) throws IOException {
+ CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data), data.length);
+ return new CryptoCustomMessage<>(cryptoBox, dataReader);
+ }
+
+ public BitmessageAddress getSender() {
+ return sender;
+ }
+
+ public void signAndEncrypt(BitmessageAddress identity, byte[] publicKey) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ Encode.varInt(identity.getVersion(), out);
+ Encode.varInt(identity.getStream(), out);
+ Encode.int32(identity.getPubkey().getBehaviorBitfield(), out);
+ out.write(identity.getPubkey().getSigningKey(), 1, 64);
+ out.write(identity.getPubkey().getEncryptionKey(), 1, 64);
+ if (identity.getVersion() >= 3) {
+ Encode.varInt(identity.getPubkey().getNonceTrialsPerByte(), out);
+ Encode.varInt(identity.getPubkey().getExtraBytes(), out);
+ }
+
+ data.write(out);
+ Encode.varBytes(security().getSignature(out.toByteArray(), identity.getPrivateKey()), out);
+ container = new CryptoBox(out.toByteArray(), publicKey);
+ }
+
+ public T decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
+ SignatureCheckingInputStream in = new SignatureCheckingInputStream(container.decrypt(privateKey));
+
+ long addressVersion = varInt(in);
+ long stream = varInt(in);
+ int behaviorBitfield = int32(in);
+ byte[] publicSigningKey = bytes(in, 64);
+ byte[] publicEncryptionKey = bytes(in, 64);
+ long nonceTrialsPerByte = addressVersion >= 3 ? varInt(in) : 0;
+ long extraBytes = addressVersion >= 3 ? varInt(in) : 0;
+
+ sender = new BitmessageAddress(Factory.createPubkey(
+ addressVersion,
+ stream,
+ publicSigningKey,
+ publicEncryptionKey,
+ nonceTrialsPerByte,
+ extraBytes,
+ behaviorBitfield
+ ));
+
+ data = dataReader.read(sender, in);
+
+ in.checkSignature(sender.getPubkey());
+
+ return data;
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ container.write(out);
+ }
+
+ public interface Reader {
+ T read(BitmessageAddress sender, InputStream in) throws IOException;
+ }
+
+ private class SignatureCheckingInputStream extends InputStream {
+ private final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ private final InputStream wrapped;
+
+ private SignatureCheckingInputStream(InputStream wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public int read() throws IOException {
+ int read = wrapped.read();
+ if (read >= 0) out.write(read);
+ return read;
+ }
+
+ public void checkSignature(Pubkey pubkey) throws IOException, RuntimeException {
+ if (!security().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) {
+ throw new RuntimeException("Signature check failed");
+ }
+ }
+ }
+}
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
new file mode 100644
index 0000000..b247b59
--- /dev/null
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.extensions.pow;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.Streamable;
+import ch.dissem.bitmessage.utils.Encode;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import static ch.dissem.bitmessage.utils.Decode.*;
+
+/**
+ * @author Christian Basler
+ */
+public class ProofOfWorkRequest implements Streamable {
+ private final BitmessageAddress sender;
+ private final byte[] initialHash;
+ private final Request request;
+ private final byte[] data;
+
+ private ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request, byte[] data) {
+ this.sender = sender;
+ this.initialHash = initialHash;
+ this.request = request;
+ this.data = data;
+ }
+
+ public static ProofOfWorkRequest read(BitmessageAddress client, InputStream in) throws IOException {
+ return new ProofOfWorkRequest(
+ client,
+ bytes(in, 64),
+ Request.valueOf(varString(in)),
+ varBytes(in)
+ );
+ }
+
+ public BitmessageAddress getSender() {
+ return sender;
+ }
+
+ public byte[] getInitialHash() {
+ return initialHash;
+ }
+
+ public Request getRequest() {
+ return request;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ out.write(initialHash);
+ Encode.varString(request.name(), out);
+ Encode.varBytes(data, out);
+ }
+
+ public enum Request {
+ CALCULATE,
+ QUERY,
+ ERROR,
+ OK,
+ QUEUED,
+ CALCULATING,
+ COMPLETE
+ }
+}
diff --git a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
new file mode 100644
index 0000000..98e97a1
--- /dev/null
+++ b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.bitmessage.extensions;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.payload.GenericPayload;
+import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
+import ch.dissem.bitmessage.utils.TestBase;
+import ch.dissem.bitmessage.utils.TestUtils;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static ch.dissem.bitmessage.utils.Singleton.security;
+import static org.junit.Assert.assertEquals;
+
+public class CryptoCustomMessageTest extends TestBase {
+ @Test
+ public void testEncryptThenDecrypt() throws Exception {
+ PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
+ BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey);
+
+ GenericPayload payloadBefore = new GenericPayload(0, 1, security().randomBytes(100));
+ CryptoCustomMessage messageBefore = new CryptoCustomMessage<>(payloadBefore);
+ messageBefore.signAndEncrypt(sendingIdentity, security().createPublicKey(sendingIdentity.getPublicDecryptionKey()));
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ messageBefore.write(out);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+
+ CryptoCustomMessage messageAfter = CryptoCustomMessage.read(out.toByteArray(), new CryptoCustomMessage.Reader() {
+ @Override
+ public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException {
+ return GenericPayload.read(0, in, 1, 100);
+ }
+ });
+ GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey());
+
+ assertEquals(payloadBefore, payloadAfter);
+ }
+}
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
index 8ed2fb4..a95adba 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -259,6 +259,12 @@ public class Connection {
LOG.debug("Received " + addr.getAddresses().size() + " addresses.");
ctx.getNodeRegistry().offerAddresses(addr.getAddresses());
break;
+ case CUSTOM:
+ MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) messagePayload);
+ if (response != null) {
+ send(response);
+ }
+ break;
case VERACK:
case VERSION:
throw new RuntimeException("Unexpectedly received '" + messagePayload.getCommand() + "' command");
diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
index 601ce29..d583a71 100644
--- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
+++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java
@@ -31,7 +31,7 @@ import static ch.dissem.bitmessage.utils.Strings.hex;
/**
* Helper class that does Flyway migration, provides JDBC connections and some helper methods.
*/
-abstract class JdbcHelper {
+public abstract class JdbcHelper {
private static final Logger LOG = LoggerFactory.getLogger(JdbcHelper.class);
protected final JdbcConfig config;
diff --git a/settings.gradle b/settings.gradle
index 2d3e1f6..adecd45 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,3 +13,5 @@ include 'wif'
include 'security-sc'
include 'security-bc'
+
+include 'extensions'
\ No newline at end of file
From 991a0e5f869a6c6087901ff405175e2039e69457 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Wed, 2 Dec 2015 17:45:50 +0100
Subject: [PATCH 2/6] Some improvements for custom message handling
---
.../bitmessage/entity/CustomMessage.java | 13 ++++++---
.../bitmessage/ports/NetworkHandler.java | 14 ++++++++-
.../extensions/pow/ProofOfWorkRequest.java | 12 ++++----
gradle/wrapper/gradle-wrapper.properties | 2 +-
.../bitmessage/networking/Connection.java | 12 ++++----
.../networking/DefaultNetworkHandler.java | 29 +++++++++++++++++--
.../bitmessage/wif/WifExporterTest.java | 16 +++++-----
7 files changed, 72 insertions(+), 26 deletions(-)
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
index b31c9f5..63f9663 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -16,10 +16,7 @@
package ch.dissem.bitmessage.entity;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.*;
import static ch.dissem.bitmessage.utils.Decode.bytes;
@@ -65,4 +62,12 @@ public class CustomMessage implements MessagePayload {
"Programmer: did you forget to override #write()?");
}
}
+
+ public static CustomMessage error(String message) {
+ try {
+ return new CustomMessage(("ERROR\n" + message).getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
index e2fd170..909d3dd 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java
@@ -16,6 +16,7 @@
package ch.dissem.bitmessage.ports;
+import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.utils.Property;
@@ -34,7 +35,18 @@ public interface NetworkHandler {
* An implementation should disconnect if either the timeout is reached or the returned thread is interrupted.
*
*/
- Future> synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds);
+ Future> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds);
+
+ /**
+ * Send a custom message to a specific node (that should implement handling for this message type) and returns
+ * the response, which in turn is expected to be a {@link CustomMessage}.
+ *
+ * @param server the node's address
+ * @param port the node's port
+ * @param request the request
+ * @return the response
+ */
+ CustomMessage send(InetAddress server, int port, CustomMessage request);
/**
* Start a full network node, accepting incoming connections and relaying objects.
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
index b247b59..2ef2f9e 100644
--- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
import static ch.dissem.bitmessage.utils.Decode.*;
/**
@@ -35,7 +36,11 @@ public class ProofOfWorkRequest implements Streamable {
private final Request request;
private final byte[] data;
- private ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request, byte[] data) {
+ public ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request) {
+ this(sender, initialHash, request, new byte[0]);
+ }
+
+ public ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request, byte[] data) {
this.sender = sender;
this.initialHash = initialHash;
this.request = request;
@@ -76,11 +81,8 @@ public class ProofOfWorkRequest implements Streamable {
public enum Request {
CALCULATE,
- QUERY,
- ERROR,
- OK,
- QUEUED,
CALCULATING,
+ QUERY,
COMPLETE
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 66e6c70..94f382d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
index a95adba..4d14fe2 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java
@@ -260,11 +260,6 @@ public class Connection {
ctx.getNodeRegistry().offerAddresses(addr.getAddresses());
break;
case CUSTOM:
- MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) messagePayload);
- if (response != null) {
- send(response);
- }
- break;
case VERACK:
case VERSION:
throw new RuntimeException("Unexpectedly received '" + messagePayload.getCommand() + "' command");
@@ -400,6 +395,13 @@ public class Connection {
break;
}
break;
+ case CUSTOM:
+ MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) msg.getPayload());
+ if (response != null) {
+ send(response);
+ }
+ disconnect();
+ break;
default:
throw new NodeException("Command 'version' or 'verack' expected, but was '"
+ msg.getPayload().getCommand() + "'");
diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
index 3944378..e934bdf 100644
--- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
+++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java
@@ -18,8 +18,12 @@ package ch.dissem.bitmessage.networking;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.InternalContext.ContextHolder;
+import ch.dissem.bitmessage.entity.CustomMessage;
+import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
+import ch.dissem.bitmessage.exception.NodeException;
+import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.NetworkHandler;
import ch.dissem.bitmessage.utils.Collections;
import ch.dissem.bitmessage.utils.Property;
@@ -71,9 +75,9 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
}
@Override
- public Future> synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds) {
+ public Future> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds) {
try {
- Connection connection = Connection.sync(ctx, trustedHost, port, listener, timeoutInSeconds);
+ Connection connection = Connection.sync(ctx, server, port, listener, timeoutInSeconds);
Future> reader = pool.submit(connection.getReader());
pool.execute(connection.getWriter());
return reader;
@@ -82,6 +86,27 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
}
}
+ @Override
+ public CustomMessage send(InetAddress server, int port, CustomMessage request) {
+ try (Socket socket = new Socket(server, port)) {
+ socket.setSoTimeout(Connection.READ_TIMEOUT);
+ new NetworkMessage(request).write(socket.getOutputStream());
+ NetworkMessage networkMessage = Factory.getNetworkMessage(3, socket.getInputStream());
+ if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) {
+ return (CustomMessage) networkMessage.getPayload();
+ } else {
+ if (networkMessage == null) {
+ throw new NodeException("No response from node " + server);
+ } else {
+ throw new NodeException("Unexpected response from node " +
+ server + ": " + networkMessage.getPayload().getCommand());
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Override
public void start(final MessageListener listener) {
if (listener == null) {
diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java
index 5ed9025..a2ee560 100644
--- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java
+++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java
@@ -72,14 +72,14 @@ public class WifExporterTest {
@Test
public void testAddIdentity() throws Exception {
- String expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]\n" +
- "label = Nuked Address\n" +
- "enabled = true\n" +
- "decoy = false\n" +
- "noncetrialsperbyte = 320\n" +
- "payloadlengthextrabytes = 14000\n" +
- "privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9\n" +
- "privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck\n\n";
+ String expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]" + System.lineSeparator() +
+ "label = Nuked Address" + System.lineSeparator() +
+ "enabled = true" + System.lineSeparator() +
+ "decoy = false" + System.lineSeparator() +
+ "noncetrialsperbyte = 320" + System.lineSeparator() +
+ "payloadlengthextrabytes = 14000" + System.lineSeparator() +
+ "privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9" + System.lineSeparator() +
+ "privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck" + System.lineSeparator() + System.lineSeparator();
importer = new WifImporter(ctx, expected);
exporter.addIdentity(importer.getIdentities().get(0));
assertEquals(expected, exporter.toString());
From ab6a3c56dd2a72d3cfc28bb5aeea3a573e7ab183 Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Tue, 8 Dec 2015 20:27:32 +0100
Subject: [PATCH 3/6] The POW callback is now a service and its state stored.
The proof of work engine therefore just has to remember its initial hash
making server based POW easier.
---
.../dissem/bitmessage/demo/Application.java | 6 +-
.../java/ch/dissem/bitmessage/demo/Main.java | 1 +
.../dissem/bitmessage/BitmessageContext.java | 32 +++++----
.../bitmessage/DefaultMessageListener.java | 16 ++---
.../ch/dissem/bitmessage/InternalContext.java | 69 +++++--------------
.../dissem/bitmessage/ProofOfWorkService.java | 62 +++++++++++++++++
.../bitmessage/entity/CustomMessage.java | 23 +++++--
.../bitmessage/entity/ObjectMessage.java | 6 +-
.../dissem/bitmessage/entity/Plaintext.java | 9 +++
.../bitmessage/ports/AbstractSecurity.java | 9 ++-
.../bitmessage/ports/MessageRepository.java | 2 +
.../ports/MultiThreadedPOWEngine.java | 6 +-
.../bitmessage/ports/ProofOfWorkEngine.java | 2 +-
.../ports/ProofOfWorkRepository.java | 16 +++++
.../ch/dissem/bitmessage/ports/Security.java | 2 +
.../bitmessage/ports/SimplePOWEngine.java | 2 +-
.../ch/dissem/bitmessage/utils/Decode.java | 8 ++-
.../ch/dissem/bitmessage/utils/Numbers.java | 10 +++
.../ports/ProofOfWorkEngineTest.java | 4 +-
.../extensions/CryptoCustomMessage.java | 3 +
.../repository/JdbcMessageRepository.java | 26 +++++--
.../repository/JdbcProofOfWorkRepository.java | 69 +++++++++++++++++++
.../migration/V2.0__Update_table_message.sql | 2 +
.../db/migration/V2.1__Create_table_POW.sql | 5 ++
.../bitmessage/security/SecurityTest.java | 2 +-
25 files changed, 289 insertions(+), 103 deletions(-)
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java
create mode 100644 domain/src/main/java/ch/dissem/bitmessage/utils/Numbers.java
create mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java
create mode 100644 repositories/src/main/resources/db/migration/V2.0__Update_table_message.sql
create mode 100644 repositories/src/main/resources/db/migration/V2.1__Create_table_POW.sql
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
index a065de9..72da50d 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java
@@ -22,10 +22,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
-import ch.dissem.bitmessage.repository.JdbcAddressRepository;
-import ch.dissem.bitmessage.repository.JdbcConfig;
-import ch.dissem.bitmessage.repository.JdbcInventory;
-import ch.dissem.bitmessage.repository.JdbcMessageRepository;
+import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,6 +47,7 @@ public class Application {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
+ .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.networkHandler(new DefaultNetworkHandler())
.security(new BouncySecurity())
.port(48444)
diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
index ac90e88..6dbfc14 100644
--- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
+++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java
@@ -51,6 +51,7 @@ public class Main {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
+ .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.networkHandler(new DefaultNetworkHandler())
.security(new BouncySecurity())
.port(48444)
diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 9d4abd7..def0a4f 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -75,7 +75,7 @@ public class BitmessageContext {
}
public AddressRepository addresses() {
- return ctx.getAddressRepo();
+ return ctx.getAddressRepository();
}
public MessageRepository messages() {
@@ -90,7 +90,7 @@ public class BitmessageContext {
ctx.getNetworkExtraBytes(),
features
));
- ctx.getAddressRepo().save(identity);
+ ctx.getAddressRepository().save(identity);
pool.submit(new Runnable() {
@Override
public void run() {
@@ -102,6 +102,7 @@ public class BitmessageContext {
public void addDistributedMailingList(String address, String alias) {
// TODO
+ throw new RuntimeException("not implemented");
}
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
@@ -120,9 +121,7 @@ public class BitmessageContext {
from,
from,
Factory.getBroadcast(from, msg),
- +2 * DAY,
- 0,
- 0
+ +2 * DAY
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
@@ -159,9 +158,7 @@ public class BitmessageContext {
from,
to,
new Msg(msg),
- +2 * DAY,
- ctx.getNonceTrialsPerByte(to),
- ctx.getExtraBytes(to)
+ +2 * DAY
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
@@ -176,9 +173,7 @@ public class BitmessageContext {
requestingIdentity,
address,
new GetPubkey(address),
- +28 * DAY,
- ctx.getNetworkNonceTrialsPerByte(),
- ctx.getNetworkExtraBytes()
+ +28 * DAY
);
}
@@ -220,7 +215,7 @@ public class BitmessageContext {
}
public void addContact(BitmessageAddress contact) {
- ctx.getAddressRepo().save(contact);
+ ctx.getAddressRepository().save(contact);
tryToFindMatchingPubkey(contact);
if (contact.getPubkey() == null) {
ctx.requestPubkey(contact);
@@ -237,7 +232,7 @@ public class BitmessageContext {
v4Pubkey.decrypt(address.getPublicDecryptionKey());
if (object.isSignatureValid(v4Pubkey)) {
address.setPubkey(v4Pubkey);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
break;
} else {
LOG.info("Found pubkey for " + address + " but signature is invalid");
@@ -246,7 +241,7 @@ public class BitmessageContext {
} else {
if (Arrays.equals(pubkey.getRipe(), address.getRipe())) {
address.setPubkey(pubkey);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
break;
}
}
@@ -258,7 +253,7 @@ public class BitmessageContext {
public void addSubscribtion(BitmessageAddress address) {
address.setSubscribed(true);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
tryToFindBroadcastsForAddress(address);
}
@@ -292,6 +287,7 @@ public class BitmessageContext {
NetworkHandler networkHandler;
AddressRepository addressRepo;
MessageRepository messageRepo;
+ ProofOfWorkRepository proofOfWorkRepository;
ProofOfWorkEngine proofOfWorkEngine;
Security security;
MessageCallback messageCallback;
@@ -333,6 +329,11 @@ public class BitmessageContext {
return this;
}
+ public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) {
+ this.proofOfWorkRepository = proofOfWorkRepository;
+ return this;
+ }
+
public Builder security(Security security) {
this.security = security;
return this;
@@ -374,6 +375,7 @@ public class BitmessageContext {
nonNull("networkHandler", networkHandler);
nonNull("addressRepo", addressRepo);
nonNull("messageRepo", messageRepo);
+ nonNull("proofOfWorkRepo", proofOfWorkRepository);
if (proofOfWorkEngine == null) {
proofOfWorkEngine = new MultiThreadedPOWEngine();
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
index e069704..eb22f03 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
@@ -69,7 +69,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
- BitmessageAddress identity = ctx.getAddressRepo().findIdentity(getPubkey.getRipeTag());
+ BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
if (identity != null && identity.getPrivateKey() != null) {
LOG.info("Got pubkey request for identity " + identity);
// FIXME: only send pubkey if it wasn't sent in the last 28 days
@@ -82,17 +82,17 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
try {
if (pubkey instanceof V4Pubkey) {
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
- address = ctx.getAddressRepo().findContact(v4Pubkey.getTag());
+ address = ctx.getAddressRepository().findContact(v4Pubkey.getTag());
if (address != null) {
v4Pubkey.decrypt(address.getPublicDecryptionKey());
}
} else {
- address = ctx.getAddressRepo().findContact(pubkey.getRipe());
+ address = ctx.getAddressRepository().findContact(pubkey.getRipe());
}
if (address != null) {
address.setPubkey(pubkey);
LOG.info("Got pubkey for contact " + address);
- ctx.getAddressRepo().save(address);
+ ctx.getAddressRepository().save(address);
List messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
LOG.info("Sending " + messages.size() + " messages for contact " + address);
for (Plaintext msg : messages) {
@@ -102,9 +102,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
msg.getFrom(),
msg.getTo(),
new Msg(msg),
- +2 * DAY,
- ctx.getNonceTrialsPerByte(msg.getTo()),
- ctx.getExtraBytes(msg.getTo())
+ +2 * DAY
);
msg.setStatus(SENT);
ctx.getMessageRepository().save(msg);
@@ -115,7 +113,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
protected void receive(ObjectMessage object, Msg msg) throws IOException {
- for (BitmessageAddress identity : ctx.getAddressRepo().getIdentities()) {
+ for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) {
try {
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
msg.getPlaintext().setTo(identity);
@@ -136,7 +134,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException {
byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null;
- for (BitmessageAddress subscription : ctx.getAddressRepo().getSubscriptions(broadcast.getVersion())) {
+ for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) {
if (tag != null && !Arrays.equals(tag, subscription.getTag())) {
continue;
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 95cd8d8..d139de5 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -48,9 +48,11 @@ public class InternalContext {
private final NetworkHandler networkHandler;
private final AddressRepository addressRepository;
private final MessageRepository messageRepository;
+ private final ProofOfWorkRepository proofOfWorkRepository;
private final ProofOfWorkEngine proofOfWorkEngine;
private final MessageCallback messageCallback;
private final CustomCommandHandler customCommandHandler;
+ private final ProofOfWorkService proofOfWorkService;
private final TreeSet streams = new TreeSet<>();
private final int port;
@@ -67,6 +69,8 @@ public class InternalContext {
this.networkHandler = builder.networkHandler;
this.addressRepository = builder.addressRepo;
this.messageRepository = builder.messageRepo;
+ this.proofOfWorkRepository = builder.proofOfWorkRepository;
+ this.proofOfWorkService = new ProofOfWorkService();
this.proofOfWorkEngine = builder.proofOfWorkEngine;
this.clientNonce = security.randomNonce();
this.messageCallback = builder.messageCallback;
@@ -88,7 +92,9 @@ public class InternalContext {
streams.add(1L);
}
- init(security, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
+ init(security, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
+ proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine,
+ messageCallback, customCommandHandler);
for (BitmessageAddress identity : addressRepository.getIdentities()) {
streams.add(identity.getStream());
}
@@ -118,7 +124,7 @@ public class InternalContext {
return networkHandler;
}
- public AddressRepository getAddressRepo() {
+ public AddressRepository getAddressRepository() {
return addressRepository;
}
@@ -126,6 +132,10 @@ public class InternalContext {
return messageRepository;
}
+ public ProofOfWorkRepository getProofOfWorkRepository() {
+ return proofOfWorkRepository;
+ }
+
public ProofOfWorkEngine getProofOfWorkEngine() {
return proofOfWorkEngine;
}
@@ -147,22 +157,12 @@ public class InternalContext {
return networkNonceTrialsPerByte;
}
- public long getNonceTrialsPerByte(BitmessageAddress address) {
- long nonceTrialsPerByte = address.getPubkey().getNonceTrialsPerByte();
- return networkNonceTrialsPerByte > nonceTrialsPerByte ? networkNonceTrialsPerByte : nonceTrialsPerByte;
- }
-
public long getNetworkExtraBytes() {
return networkExtraBytes;
}
- public long getExtraBytes(BitmessageAddress address) {
- long extraBytes = address.getPubkey().getExtraBytes();
- return networkExtraBytes > extraBytes ? networkExtraBytes : extraBytes;
- }
-
public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
- final long timeToLive, final long nonceTrialsPerByte, final long extraBytes) {
+ final long timeToLive) {
try {
if (to == null) to = from;
long expires = UnixTime.now(+timeToLive);
@@ -181,22 +181,7 @@ public class InternalContext {
object.encrypt(to.getPubkey());
}
messageCallback.proofOfWorkStarted(payload);
- security.doProofOfWork(object, nonceTrialsPerByte, extraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- object.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(payload);
- if (payload instanceof PlaintextHolder) {
- Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
- plaintext.setInventoryVector(object.getInventoryVector());
- messageRepository.save(plaintext);
- }
- inventory.storeObject(object);
- networkHandler.offer(object.getInventoryVector());
- messageCallback.messageOffered(payload, object.getInventoryVector());
- }
- });
+ proofOfWorkService.doProofOfWork(to, object);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -214,18 +199,8 @@ public class InternalContext {
response.sign(identity.getPrivateKey());
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
messageCallback.proofOfWorkStarted(identity.getPubkey());
- security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- response.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(identity.getPubkey());
- inventory.storeObject(response);
- networkHandler.offer(response.getInventoryVector());
- // TODO: save that the pubkey was just sent, and on which stream!
- messageCallback.messageOffered(identity.getPubkey(), response.getInventoryVector());
- }
- });
+ // TODO: remember that the pubkey is just about to be sent, and on which stream!
+ proofOfWorkService.doProofOfWork(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -240,17 +215,7 @@ public class InternalContext {
.payload(new GetPubkey(contact))
.build();
messageCallback.proofOfWorkStarted(response.getPayload());
- security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes,
- new ProofOfWorkEngine.Callback() {
- @Override
- public void onNonceCalculated(byte[] nonce) {
- response.setNonce(nonce);
- messageCallback.proofOfWorkCompleted(response.getPayload());
- inventory.storeObject(response);
- networkHandler.offer(response.getInventoryVector());
- messageCallback.messageOffered(response.getPayload(), response.getInventoryVector());
- }
- });
+ proofOfWorkService.doProofOfWork(response);
}
public long getClientNonce() {
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
new file mode 100644
index 0000000..3cf46ef
--- /dev/null
+++ b/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -0,0 +1,62 @@
+package ch.dissem.bitmessage;
+
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.ObjectMessage;
+import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.PlaintextHolder;
+import ch.dissem.bitmessage.ports.MessageRepository;
+import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
+import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
+import ch.dissem.bitmessage.ports.Security;
+
+import static ch.dissem.bitmessage.utils.Singleton.security;
+
+/**
+ * @author Christian Basler
+ */
+public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
+ private Security security;
+ private InternalContext ctx;
+ private ProofOfWorkRepository powRepo;
+ private MessageRepository messageRepo;
+
+ public void doProofOfWork(ObjectMessage object) {
+ doProofOfWork(null, object);
+ }
+
+ public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
+ long nonceTrialsPerByte = recipient == null ? 0 : recipient.getPubkey().getNonceTrialsPerByte();
+ long extraBytes = recipient == null ? 0 : recipient.getPubkey().getExtraBytes();
+
+ powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
+ if (object.getPayload() instanceof PlaintextHolder){
+ Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
+ plaintext.setInitialHash(security.getInitialHash(object));
+ messageRepo.save(plaintext);
+ }
+ security.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this);
+ }
+
+ @Override
+ public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
+ ObjectMessage object = powRepo.getObject(initialHash);
+ object.setNonce(nonce);
+// messageCallback.proofOfWorkCompleted(payload);
+ Plaintext plaintext = messageRepo.getMessage(initialHash);
+ if (plaintext != null) {
+ plaintext.setInventoryVector(object.getInventoryVector());
+ messageRepo.save(plaintext);
+ }
+ ctx.getInventory().storeObject(object);
+ ctx.getNetworkHandler().offer(object.getInventoryVector());
+// messageCallback.messageOffered(payload, object.getInventoryVector());
+ }
+
+ @Override
+ public void setContext(InternalContext ctx) {
+ this.ctx = ctx;
+ this.security = security();
+ this.powRepo = ctx.getProofOfWorkRepository();
+ this.messageRepo = ctx.getMessageRepository();
+ }
+}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
index 63f9663..a5caf3a 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -16,26 +16,36 @@
package ch.dissem.bitmessage.entity;
+import ch.dissem.bitmessage.utils.AccessCounter;
+import ch.dissem.bitmessage.utils.Encode;
+
import java.io.*;
import static ch.dissem.bitmessage.utils.Decode.bytes;
+import static ch.dissem.bitmessage.utils.Decode.varString;
/**
* @author Christian Basler
*/
public class CustomMessage implements MessagePayload {
+ public static final String COMMAND_ERROR = "ERROR";
+
+ private final String command;
private final byte[] data;
- public CustomMessage() {
+ public CustomMessage(String command) {
+ this.command = command;
this.data = null;
}
- public CustomMessage(byte[] data) {
+ public CustomMessage(String command, byte[] data) {
+ this.command = command;
this.data = data;
}
public static MessagePayload read(InputStream in, int length) throws IOException {
- return new CustomMessage(bytes(in, length));
+ AccessCounter counter = new AccessCounter();
+ return new CustomMessage(varString(in, counter), bytes(in, length - counter.length()));
}
@Override
@@ -56,6 +66,7 @@ public class CustomMessage implements MessagePayload {
@Override
public void write(OutputStream out) throws IOException {
if (data != null) {
+ Encode.varString(command, out);
out.write(data);
} else {
throw new RuntimeException("Tried to write custom message without data. " +
@@ -63,9 +74,13 @@ public class CustomMessage implements MessagePayload {
}
}
+ public boolean isError() {
+ return COMMAND_ERROR.equals(command);
+ }
+
public static CustomMessage error(String message) {
try {
- return new CustomMessage(("ERROR\n" + message).getBytes("UTF-8"));
+ return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
index 128084e..9e89c42 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
@@ -156,7 +156,11 @@ public class ObjectMessage implements MessagePayload {
@Override
public void write(OutputStream out) throws IOException {
- out.write(nonce);
+ if (nonce != null) {
+ out.write(nonce);
+ } else {
+ out.write(new byte[8]);
+ }
out.write(getPayloadBytesWithoutNonce());
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
index eb0a60f..fbd5d48 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
@@ -44,6 +44,7 @@ public class Plaintext implements Streamable {
private Long received;
private Set