Added synchronization code and unit test.

Synchronisation fails if the trusted host has no new messages - this needs to be fixed (but shouldn't be an issue for real world applications)
This commit is contained in:
2015-09-24 08:09:20 +02:00
parent f89d1a342e
commit d67c932fb2
15 changed files with 457 additions and 117 deletions

View File

@ -21,13 +21,13 @@ import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Property;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -179,19 +179,6 @@ public class BitmessageContext {
);
}
private void send(long stream, ObjectPayload payload, long timeToLive) {
long expires = UnixTime.now(+timeToLive);
LOG.info("Expires at " + expires);
ObjectMessage object = new ObjectMessage.Builder()
.stream(stream)
.expiresTime(expires)
.payload(payload)
.build();
ctx.getSecurity().doProofOfWork(object, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
ctx.getInventory().storeObject(object);
ctx.getNetworkHandler().offer(object.getInventoryVector());
}
public void startup(Listener listener) {
this.listener = listener;
ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener));
@ -280,6 +267,7 @@ public class BitmessageContext {
MessageRepository messageRepo;
ProofOfWorkEngine proofOfWorkEngine;
Security security;
MessageCallback messageCallback;
public Builder() {
}
@ -319,6 +307,11 @@ public class BitmessageContext {
return this;
}
public Builder messageCallback(MessageCallback callback) {
this.messageCallback = callback;
return this;
}
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
this.proofOfWorkEngine = proofOfWorkEngine;
return this;
@ -333,6 +326,25 @@ public class BitmessageContext {
if (proofOfWorkEngine == null) {
proofOfWorkEngine = new MultiThreadedPOWEngine();
}
if (messageCallback == null) {
messageCallback = new MessageCallback() {
@Override
public void proofOfWorkStarted(ObjectPayload message) {
}
@Override
public void proofOfWorkCompleted(ObjectPayload message) {
}
@Override
public void messageOffered(ObjectPayload message, InventoryVector iv) {
}
@Override
public void messageAcknowledged(InventoryVector iv) {
}
};
}
return new BitmessageContext(this);
}

View File

@ -49,12 +49,13 @@ public class InternalContext {
private final AddressRepository addressRepository;
private final MessageRepository messageRepository;
private final ProofOfWorkEngine proofOfWorkEngine;
private final MessageCallback messageCallback;
private final TreeSet<Long> streams = new TreeSet<>();
private final int port;
private long networkNonceTrialsPerByte = 1000;
private long networkExtraBytes = 1000;
private long clientNonce;
private final long clientNonce;
private final long networkNonceTrialsPerByte = 1000;
private final long networkExtraBytes = 1000;
public InternalContext(BitmessageContext.Builder builder) {
this.security = builder.security;
@ -65,11 +66,11 @@ public class InternalContext {
this.messageRepository = builder.messageRepo;
this.proofOfWorkEngine = builder.proofOfWorkEngine;
this.clientNonce = security.randomNonce();
this.messageCallback = builder.messageCallback;
this.port = builder.port;
Singleton.initialize(security);
port = builder.port;
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
for (BitmessageAddress address : addressRepository.getIdentities()) {
streams.add(address.getStream());
@ -81,7 +82,10 @@ public class InternalContext {
streams.add(1L);
}
init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine, security);
init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
for (BitmessageAddress identity : addressRepository.getIdentities()) {
streams.add(identity.getStream());
}
}
private void init(Object... objects) {
@ -169,7 +173,9 @@ public class InternalContext {
} else if (payload instanceof Encrypted) {
object.encrypt(to.getPubkey());
}
messageCallback.proofOfWorkStarted(payload);
security.doProofOfWork(object, nonceTrialsPerByte, extraBytes);
messageCallback.proofOfWorkCompleted(payload);
if (payload instanceof PlaintextHolder) {
Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
plaintext.setInventoryVector(object.getInventoryVector());
@ -177,6 +183,7 @@ public class InternalContext {
}
inventory.storeObject(object);
networkHandler.offer(object.getInventoryVector());
messageCallback.messageOffered(payload, object.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -193,16 +200,13 @@ public class InternalContext {
.build();
response.sign(identity.getPrivateKey());
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
messageCallback.proofOfWorkStarted(identity.getPubkey());
security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
if (response.isSigned()) {
response.sign(identity.getPrivateKey());
}
if (response instanceof Encrypted) {
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
}
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());
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -216,9 +220,12 @@ public class InternalContext {
.expiresTime(expires)
.payload(new GetPubkey(contact))
.build();
messageCallback.proofOfWorkStarted(response.getPayload());
security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
messageCallback.proofOfWorkCompleted(response.getPayload());
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
messageCallback.messageOffered(response.getPayload(), response.getInventoryVector());
}
public long getClientNonce() {

View File

@ -0,0 +1,52 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
/**
* Callback for message sending events, mostly so the user can be notified when POW is done.
*/
public interface MessageCallback {
/**
* Called before calculation of proof of work begins.
*/
void proofOfWorkStarted(ObjectPayload message);
/**
* Called after calculation of proof of work finished.
*/
void proofOfWorkCompleted(ObjectPayload message);
/**
* Called once the message is offered to the network. Please note that this doesn't mean the message was sent,
* if the client is not connected to the network it's just stored in the inventory.
* <p>
* Also, please note that this is where the original payload as well as the {@link InventoryVector} of the sent
* message is available. If the callback needs the IV for some reason, it should be retrieved here. (Plaintext
* and Broadcast messages will have their IV property set automatically though.)
* </p>
*/
void messageOffered(ObjectPayload message, InventoryVector iv);
/**
* This isn't called yet, as ACK messages aren't being processed yet. Also, this is only relevant for Plaintext
* messages.
*/
void messageAcknowledged(InventoryVector iv);
}

View File

@ -27,12 +27,24 @@ import java.net.InetAddress;
* Handles incoming messages
*/
public interface NetworkHandler {
/**
* Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
*/
Thread synchronize(InetAddress trustedHost, int port, MessageListener listener, long timeoutInSeconds);
/**
* Start a full network node, accepting incoming connections and relaying objects.
*/
void start(MessageListener listener);
/**
* Stop the full network node.
*/
void stop();
void synchronize(InetAddress trustedHost, int port, MessageListener listener) throws IOException;
/**
* Offer new objects to up to 8 random nodes.
*/
void offer(InventoryVector iv);
Property getNetworkStatus();

View File

@ -35,6 +35,25 @@ public class Property {
this.properties = properties;
}
public String getName() {
return name;
}
public Object getValue() {
return value;
}
public Property getProperty(String name) {
for (Property p : properties) {
if (name == null) {
if (p.name == null) return p;
} else {
if (name.equals(p.name)) return p;
}
}
return null;
}
@Override
public String toString() {
return toString("");