From 274c16b7486e9b824f474384bf0549b0631dabde Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 29 May 2015 13:17:00 +0200 Subject: [PATCH] Implemented sending messages (and fixed a few bugs on the way) This closes issue #3 --- .../dissem/bitmessage/demo/Application.java | 90 +++++++++++++++++-- .../dissem/bitmessage/BitmessageContext.java | 66 ++++++++++---- .../bitmessage/DefaultMessageListener.java | 9 +- .../ch/dissem/bitmessage/InternalContext.java | 7 +- .../bitmessage/entity/ObjectMessage.java | 8 +- .../dissem/bitmessage/entity/Plaintext.java | 73 ++++++++------- .../bitmessage/entity/payload/Broadcast.java | 3 +- .../bitmessage/entity/payload/CryptoBox.java | 2 +- .../entity/payload/GenericPayload.java | 7 +- .../bitmessage/entity/payload/GetPubkey.java | 6 +- .../dissem/bitmessage/entity/payload/Msg.java | 2 + .../entity/payload/ObjectPayload.java | 11 +++ .../bitmessage/entity/payload/Pubkey.java | 6 +- .../bitmessage/entity/payload/V2Pubkey.java | 8 +- .../bitmessage/entity/payload/V3Pubkey.java | 41 ++++----- .../entity/payload/V4Broadcast.java | 6 +- .../bitmessage/entity/payload/V4Pubkey.java | 2 + .../entity/payload/V5Broadcast.java | 2 +- .../bitmessage/entity/valueobject/Label.java | 35 +++++++- .../ch/dissem/bitmessage/factory/Factory.java | 6 +- .../bitmessage/factory/V3MessageFactory.java | 3 +- .../bitmessage/ports/MessageRepository.java | 4 +- .../ch/dissem/bitmessage/utils/Strings.java | 12 ++- .../ch/dissem/bitmessage/EncryptionTest.java | 4 +- .../ch/dissem/bitmessage/SignatureTest.java | 1 - .../dissem/bitmessage/utils/SecurityTest.java | 4 +- .../bitmessage/networking/Connection.java | 3 +- .../repository/JdbcMessageRepository.java | 41 +++++++-- .../migration/V1.3__Create_message_table.sql | 33 +++---- 29 files changed, 357 insertions(+), 138 deletions(-) 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 05b6df2..3faa77f 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java @@ -9,6 +9,9 @@ import ch.dissem.bitmessage.repository.JdbcAddressRepository; import ch.dissem.bitmessage.repository.JdbcInventory; import ch.dissem.bitmessage.repository.JdbcMessageRepository; import ch.dissem.bitmessage.repository.JdbcNodeRegistry; +import ch.dissem.bitmessage.utils.Strings; +import org.flywaydb.core.internal.util.logging.slf4j.Slf4jLog; +import org.flywaydb.core.internal.util.logging.slf4j.Slf4jLogCreator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -113,6 +116,7 @@ public class Application { switch (command) { case "a": addIdentity(); + identities = ctx.addresses().getIdentities(); break; case "b": return; @@ -164,6 +168,7 @@ public class Application { switch (command) { case "a": addContact(); + contacts = ctx.addresses().getContacts(); break; case "b": return; @@ -201,12 +206,18 @@ public class Application { System.out.println(address.getAddress()); System.out.println("Stream: " + address.getStream()); System.out.println("Version: " + address.getVersion()); - + if (address.getPrivateKey() == null) { + if (address.getPubkey() != null) { + System.out.println("Public key available"); + } else { + System.out.println("Public key still missing"); + } + } } private void messages() { String command; - List messages = ctx.messages().findMessages(Plaintext.Status.NEW); + List<Plaintext> messages = ctx.messages().findMessages(Plaintext.Status.RECEIVED); do { System.out.println(); int i = 0; @@ -247,6 +258,8 @@ public class Application { System.out.println(); System.out.println(message.getText()); System.out.println(); + System.out.println("Labels: "+ message.getLabels()); + System.out.println(); String command; do { System.out.println("r) reply"); @@ -269,14 +282,79 @@ public class Application { private void compose() { System.out.println(); - System.out.println("TODO"); - // TODO + BitmessageAddress from = selectAddress(true); + BitmessageAddress to = selectAddress(false); + + compose(from, to, null); + } + + private BitmessageAddress selectAddress(boolean id) { + List<BitmessageAddress> identities = (id ? ctx.addresses().getIdentities() : ctx.addresses().getContacts()); + while (identities.size() == 0) { + addIdentity(); + identities = ctx.addresses().getIdentities(); + } + if (identities.size() == 1) { + return identities.get(0); + } + + String command; + do { + System.out.println(); + if (id) { + System.out.println("From:"); + } else { + System.out.println("To:"); + } + + int i = 0; + for (BitmessageAddress identity : identities) { + i++; + System.out.print(i + ") "); + if (identity.getAlias() != null) { + System.out.println(identity.getAlias() + " (" + identity.getAddress() + ")"); + } else { + System.out.println(identity.getAddress()); + } + } + System.out.println("b) back"); + + command = nextCommand(); + switch (command) { + case "b": + return null; + default: + try { + int index = Integer.parseInt(command) - 1; + if (identities.get(index) != null) { + return identities.get(index); + } + } catch (NumberFormatException e) { + System.out.println("Unknown command. Please try again."); + } + } + } while (!"b".equals(command)); + return null; } private void compose(BitmessageAddress from, BitmessageAddress to, String subject) { System.out.println(); - System.out.println("TODO"); - // TODO + System.out.println("From: " + from); + System.out.println("To: " + to); + if (subject != null) { + System.out.println("Subject: " + subject); + } else { + System.out.print("Subject: "); + subject = nextCommand(); + } + System.out.println("Message:"); + StringBuilder message = new StringBuilder(); + String line; + do { + line = nextCommand(); + message.append(line).append('\n'); + } while (line.length() > 0 || !yesNo("Send message?")); + ctx.send(from, to, subject, message.toString()); } private boolean yesNo(String question) { diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index 7cf9d7b..3ced983 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -20,9 +20,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext.Encoding; -import ch.dissem.bitmessage.entity.payload.GetPubkey; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.payload.ObjectPayload; +import ch.dissem.bitmessage.entity.payload.*; import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.ports.*; @@ -32,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.Arrays; import java.util.Collection; import java.util.TreeSet; @@ -40,18 +39,18 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY; /** * Use this class if you want to create a Bitmessage client. - * <p> + * <p/> * You'll need the Builder to create a BitmessageContext, and set the following properties: * <ul> - * <li>addressRepo</li> - * <li>inventory</li> - * <li>nodeRegistry</li> - * <li>networkHandler</li> - * <li>messageRepo</li> - * <li>streams</li> + * <li>addressRepo</li> + * <li>inventory</li> + * <li>nodeRegistry</li> + * <li>networkHandler</li> + * <li>messageRepo</li> + * <li>streams</li> * </ul> * The default implementations in the different module builds can be used. - * <p> + * <p/> * The port defaults to 8444 (the default Bitmessage port) */ public class BitmessageContext { @@ -81,6 +80,7 @@ public class BitmessageContext { features )); ctx.getAddressRepo().save(identity); + // TODO: this should happen in a separate thread ctx.sendPubkey(identity, identity.getStream()); return identity; } @@ -93,6 +93,7 @@ public class BitmessageContext { if (from.getPrivateKey() == null) { throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); } + // TODO: all this should happen in a separate thread Plaintext msg = new Plaintext.Builder() .from(from) .to(to) @@ -100,10 +101,15 @@ public class BitmessageContext { .message(subject, message) .build(); if (to.getPubkey() == null) { + tryToFindMatchingPubkey(to); + } + if (to.getPubkey() == null) { + LOG.info("Public key is missing from recipient. Requesting."); requestPubkey(from, to); msg.setStatus(PUBKEY_REQUESTED); ctx.getMessageRepository().save(msg); } else { + LOG.info("Sending message."); msg.setStatus(DOING_PROOF_OF_WORK); ctx.getMessageRepository().save(msg); ctx.send( @@ -130,13 +136,12 @@ public class BitmessageContext { ); } - private void send(long stream, long version, ObjectPayload payload, long timeToLive) { + private void send(long stream, ObjectPayload payload, long timeToLive) { try { long expires = UnixTime.now(+timeToLive); LOG.info("Expires at " + expires); ObjectMessage object = new ObjectMessage.Builder() .stream(stream) - .version(version) .expiresTime(expires) .payload(payload) .build(); @@ -159,8 +164,39 @@ public class BitmessageContext { public void addContact(BitmessageAddress contact) { ctx.getAddressRepo().save(contact); - // TODO: search pubkey in inventory - ctx.requestPubkey(contact); + tryToFindMatchingPubkey(contact); + if (contact.getPubkey() == null) { + ctx.requestPubkey(contact); + } + } + + private void tryToFindMatchingPubkey(BitmessageAddress address) { + for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) { + try { + Pubkey pubkey = (Pubkey) object.getPayload(); + if (address.getVersion() == 4) { + V4Pubkey v4Pubkey = (V4Pubkey) pubkey; + if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) { + v4Pubkey.decrypt(address.getPubkeyDecryptionKey()); + if (object.isSignatureValid(v4Pubkey)) { + address.setPubkey(v4Pubkey); + ctx.getAddressRepo().save(address); + break; + } else { + LOG.debug("Found pubkey for " + address + " but signature is invalid"); + } + } + } else { + if (Arrays.equals(pubkey.getRipe(), address.getRipe())) { + address.setPubkey(pubkey); + ctx.getAddressRepo().save(address); + break; + } + } + } catch (Exception e) { + LOG.debug(e.getMessage(), e); + } + } } public interface Listener { diff --git a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java index deeb8e4..6c7175d 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java +++ b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java @@ -20,6 +20,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.payload.*; +import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.exception.DecryptionFailedException; import ch.dissem.bitmessage.ports.NetworkHandler; import org.slf4j.Logger; @@ -28,9 +29,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.List; -import static ch.dissem.bitmessage.entity.Plaintext.Status.DOING_PROOF_OF_WORK; -import static ch.dissem.bitmessage.entity.Plaintext.Status.NEW; -import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT; +import static ch.dissem.bitmessage.entity.Plaintext.Status.*; import static ch.dissem.bitmessage.utils.UnixTime.DAY; class DefaultMessageListener implements NetworkHandler.MessageListener { @@ -107,6 +106,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener { msg.setStatus(SENT); ctx.getMessageRepository().save(msg); } + ctx.getAddressRepo().save(address); } } catch (DecryptionFailedException ignore) { LOG.debug(ignore.getMessage(), ignore); @@ -119,7 +119,8 @@ class DefaultMessageListener implements NetworkHandler.MessageListener { msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); msg.getPlaintext().setTo(identity); object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey()); - msg.getPlaintext().setStatus(NEW); + msg.getPlaintext().setStatus(RECEIVED); + msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD)); ctx.getMessageRepository().save(msg.getPlaintext()); listener.receive(msg.getPlaintext()); break; diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java index 1ebf86d..94cf1f3 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java @@ -145,17 +145,16 @@ public class InternalContext { LOG.info("Expires at " + expires); ObjectMessage object = new ObjectMessage.Builder() .stream(to.getStream()) - .version(to.getVersion()) .expiresTime(expires) .payload(payload) .build(); - Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes); if (object.isSigned()) { object.sign(from.getPrivateKey()); } - if (object instanceof Encrypted) { + if (payload instanceof Encrypted) { object.encrypt(to.getPubkey()); } + Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes); inventory.storeObject(object); networkHandler.offer(object.getInventoryVector()); } catch (IOException e) { @@ -169,7 +168,6 @@ public class InternalContext { LOG.info("Expires at " + expires); ObjectMessage response = new ObjectMessage.Builder() .stream(targetStream) - .version(identity.getVersion()) .expiresTime(expires) .payload(identity.getPubkey()) .build(); @@ -196,7 +194,6 @@ public class InternalContext { LOG.info("Expires at " + expires); ObjectMessage response = new ObjectMessage.Builder() .stream(contact.getStream()) - .version(contact.getVersion()) .expiresTime(expires) .payload(new GetPubkey(contact)) .build(); 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 4ccfcff..14bd43f 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java @@ -50,7 +50,7 @@ public class ObjectMessage implements MessagePayload { nonce = builder.nonce; expiresTime = builder.expiresTime; objectType = builder.objectType; - version = builder.version; + version = builder.payload.getVersion(); stream = builder.streamNumber; payload = builder.payload; } @@ -177,7 +177,6 @@ public class ObjectMessage implements MessagePayload { private byte[] nonce; private long expiresTime; private long objectType = -1; - private long version = -1; private long streamNumber; private ObjectPayload payload; @@ -204,11 +203,6 @@ public class ObjectMessage implements MessagePayload { return this; } - public Builder version(long version) { - this.version = version; - return this; - } - public Builder stream(long streamNumber) { this.streamNumber = streamNumber; return this; 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 84e5bca..8027531 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java @@ -16,7 +16,6 @@ package ch.dissem.bitmessage.entity; -import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.utils.Decode; @@ -129,8 +128,12 @@ public class Plaintext implements Streamable { Encode.varInt(ack.length, out); out.write(ack); if (includeSignature) { - Encode.varInt(signature.length, out); - out.write(signature); + if (signature == null) { + Encode.varInt(0, out); + } else { + Encode.varInt(signature.length, out); + out.write(signature); + } } } @@ -188,6 +191,40 @@ public class Plaintext implements Streamable { } } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Plaintext plaintext = (Plaintext) o; + return Objects.equals(encoding, plaintext.encoding) && + Objects.equals(from, plaintext.from) && + Arrays.equals(message, plaintext.message) && + Arrays.equals(ack, plaintext.ack) && + Arrays.equals(to.getRipe(), plaintext.to.getRipe()) && + Arrays.equals(signature, plaintext.signature) && + Objects.equals(status, plaintext.status) && + Objects.equals(sent, plaintext.sent) && + Objects.equals(received, plaintext.received) && + Objects.equals(labels, plaintext.labels); + } + + @Override + public int hashCode() { + return Objects.hash(from, encoding, message, ack, to, signature, status, sent, received, labels); + } + + public void addLabels(Label... labels) { + if (labels != null) { + Collections.addAll(this.labels, labels); + } + } + + public void addLabels(Collection<Label> labels) { + if (labels != null) { + this.labels.addAll(labels); + } + } + public enum Encoding { IGNORE(0), TRIVIAL(1), SIMPLE(2); @@ -203,15 +240,13 @@ public class Plaintext implements Streamable { } public enum Status { + DRAFT, // For sent messages PUBKEY_REQUESTED, DOING_PROOF_OF_WORK, SENT, SENT_ACKNOWLEDGED, - - // For received messages - NEW, - READ + RECEIVED } public static final class Builder { @@ -233,7 +268,7 @@ public class Plaintext implements Streamable { private long sent; private long received; private Status status; - private Set<Label> labels = new TreeSet<>(); + private Set<Label> labels = new HashSet<>(); public Builder() { } @@ -365,26 +400,4 @@ public class Plaintext implements Streamable { return new Plaintext(this); } } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Plaintext plaintext = (Plaintext) o; - return Objects.equals(encoding, plaintext.encoding) && - Objects.equals(from, plaintext.from) && - Arrays.equals(message, plaintext.message) && - Arrays.equals(ack, plaintext.ack) && - Arrays.equals(to.getRipe(), plaintext.to.getRipe()) && - Arrays.equals(signature, plaintext.signature) && - Objects.equals(status, plaintext.status) && - Objects.equals(sent, plaintext.sent) && - Objects.equals(received, plaintext.received) && - Objects.equals(labels, plaintext.labels); - } - - @Override - public int hashCode() { - return Objects.hash(from, encoding, message, ack, to, signature, status, sent, received, labels); - } } 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 0cbd0e4..327a107 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 @@ -31,7 +31,8 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted { protected CryptoBox encrypted; protected Plaintext plaintext; - protected Broadcast(long stream, CryptoBox encrypted, Plaintext plaintext) { + protected Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) { + super(version); this.stream = stream; this.encrypted = encrypted; this.plaintext = plaintext; 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 abc9a6c..8db2238 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 @@ -184,7 +184,7 @@ public class CryptoBox implements Streamable { } public Builder curveType(int curveType) { - if (curveType != 0x2CA) LOG.error("Unexpected curve type " + curveType); + if (curveType != 0x2CA) LOG.debug("Unexpected curve type " + curveType); this.curveType = curveType; return this; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java index 64151d0..7051814 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java @@ -31,13 +31,14 @@ public class GenericPayload extends ObjectPayload { private long stream; private byte[] data; - public GenericPayload(long stream, byte[] data) { + public GenericPayload(long version, long stream, byte[] data) { + super(version); this.stream = stream; this.data = data; } - public static GenericPayload read(InputStream is, long stream, int length) throws IOException { - return new GenericPayload(stream, Decode.bytes(is, length)); + public static GenericPayload read(long version, InputStream is, long stream, int length) throws IOException { + return new GenericPayload(version, stream, Decode.bytes(is, length)); } @Override diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java index a6900c7..e23ab11 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java @@ -31,6 +31,7 @@ public class GetPubkey extends ObjectPayload { private byte[] ripeTag; public GetPubkey(BitmessageAddress address) { + super(address.getVersion()); this.stream = address.getStream(); if (address.getVersion() < 4) this.ripeTag = address.getRipe(); @@ -38,13 +39,14 @@ public class GetPubkey extends ObjectPayload { this.ripeTag = address.getTag(); } - private GetPubkey(long stream, long version, byte[] ripeOrTag) { + private GetPubkey(long version, long stream, byte[] ripeOrTag) { + super(version); this.stream = stream; this.ripeTag = ripeOrTag; } public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException { - return new GetPubkey(stream, version, Decode.bytes(is, length)); + return new GetPubkey(version, stream, Decode.bytes(is, length)); } /** diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java index f9e7c99..22175df 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java @@ -33,11 +33,13 @@ public class Msg extends ObjectPayload implements Encrypted { private Plaintext plaintext; private Msg(long stream, CryptoBox encrypted) { + super(1); this.stream = stream; this.encrypted = encrypted; } public Msg(Plaintext plaintext) { + super(1); this.stream = plaintext.getStream(); this.plaintext = plaintext; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java index 1667fb7..39c28ff 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java @@ -26,10 +26,21 @@ import java.io.OutputStream; * The payload of an 'object' command. This is shared by the network. */ public abstract class ObjectPayload implements Streamable { + private final long version; + + protected ObjectPayload(long version) { + this.version = version; + } + + public abstract ObjectType getType(); public abstract long getStream(); + public long getVersion() { + return version; + } + public boolean isSigned() { return false; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java index 3438ea3..2bdcbb6 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java @@ -29,12 +29,14 @@ import static ch.dissem.bitmessage.utils.Security.sha512; public abstract class Pubkey extends ObjectPayload { public final static long LATEST_VERSION = 4; + protected Pubkey(long version) { + super(version); + } + public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) { return ripemd160(sha512(publicSigningKey, publicEncryptionKey)); } - public abstract long getVersion(); - public abstract byte[] getSigningKey(); public abstract byte[] getEncryptionKey(); diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java index ef144a1..da16929 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java @@ -32,10 +32,12 @@ public class V2Pubkey extends Pubkey { protected byte[] publicSigningKey; // 64 Bytes protected byte[] publicEncryptionKey; // 64 Bytes - protected V2Pubkey() { + protected V2Pubkey(long version) { + super(version); } - private V2Pubkey(Builder builder) { + private V2Pubkey(long version, Builder builder) { + super(version); stream = builder.streamNumber; behaviorBitfield = builder.behaviorBitfield; publicSigningKey = add0x04(builder.publicSigningKey); @@ -118,7 +120,7 @@ public class V2Pubkey extends Pubkey { } public V2Pubkey build() { - return new V2Pubkey(this); + return new V2Pubkey(2, this); } } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java index f36e2d6..bf91afe 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java @@ -33,7 +33,8 @@ public class V3Pubkey extends V2Pubkey { long extraBytes; byte[] signature; - protected V3Pubkey(Builder builder) { + protected V3Pubkey(long version, Builder builder) { + super(version); stream = builder.streamNumber; behaviorBitfield = builder.behaviorBitfield; publicSigningKey = add0x04(builder.publicSigningKey); @@ -95,6 +96,24 @@ public class V3Pubkey extends V2Pubkey { this.signature = signature; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + V3Pubkey pubkey = (V3Pubkey) o; + return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) && + Objects.equals(extraBytes, pubkey.extraBytes) && + stream == pubkey.stream && + behaviorBitfield == pubkey.behaviorBitfield && + Arrays.equals(publicSigningKey, pubkey.publicSigningKey) && + Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey); + } + + @Override + public int hashCode() { + return Objects.hash(nonceTrialsPerByte, extraBytes); + } + public static class Builder { private long streamNumber; private int behaviorBitfield; @@ -143,25 +162,7 @@ public class V3Pubkey extends V2Pubkey { } public V3Pubkey build() { - return new V3Pubkey(this); + return new V3Pubkey(3, this); } } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - V3Pubkey pubkey = (V3Pubkey) o; - return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) && - Objects.equals(extraBytes, pubkey.extraBytes) && - stream == pubkey.stream && - behaviorBitfield == pubkey.behaviorBitfield && - Arrays.equals(publicSigningKey, pubkey.publicSigningKey) && - Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey); - } - - @Override - public int hashCode() { - return Objects.hash(nonceTrialsPerByte, extraBytes); - } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java index ea4021d..a2b6b23 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java @@ -25,12 +25,12 @@ import java.io.OutputStream; * Broadcasts are version 4 or 5. */ public class V4Broadcast extends Broadcast { - protected V4Broadcast(long stream, CryptoBox encrypted) { - super(stream, encrypted, null); + protected V4Broadcast(long version, long stream, CryptoBox encrypted) { + super(version, stream, encrypted, null); } public static V4Broadcast read(InputStream in, long stream, int length) throws IOException { - return new V4Broadcast(stream, CryptoBox.read(in, length)); + return new V4Broadcast(4, stream, CryptoBox.read(in, length)); } @Override diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java index e6ec264..048e7a6 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java @@ -39,12 +39,14 @@ public class V4Pubkey extends Pubkey implements Encrypted { private V3Pubkey decrypted; private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) { + super(4); this.stream = stream; this.tag = tag; this.encrypted = encrypted; } public V4Pubkey(V3Pubkey decrypted) { + super(4); this.decrypted = decrypted; this.stream = decrypted.stream; this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe()); diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java index 7910e1e..65e4d1a 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java @@ -29,7 +29,7 @@ public class V5Broadcast extends V4Broadcast { private byte[] tag; private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) { - super(stream, encrypted); + super(5, stream, encrypted); this.tag = tag; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java index 2a4f637..1f186c0 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java @@ -16,13 +16,17 @@ package ch.dissem.bitmessage.entity.valueobject; +import java.util.Objects; + public class Label { private Object id; private String label; + private Type type; private int color; - public Label(String label, int color) { + public Label(String label, Type type, int color) { this.label = label; + this.type = type; this.color = color; } @@ -45,4 +49,33 @@ public class Label { public Object getId() { return id; } + + public Type getType() { + return type; + } + + public void setId(Object id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Label label1 = (Label) o; + return Objects.equals(label, label1.label); + } + + @Override + public int hashCode() { + return Objects.hash(label); + } + + public enum Type { + INBOX, + DRAFTS, + SENT, + UNREAD, + TRASH + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java index bdf7170..5595602 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java @@ -124,7 +124,7 @@ public class Factory { } // fallback: just store the message - we don't really care what it is // LOG.info("Unexpected object type: " + objectType); - return GenericPayload.read(stream, streamNumber, length); + return GenericPayload.read(version, stream, streamNumber, length); } private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { @@ -146,7 +146,7 @@ public class Factory { private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true); - return pubkey != null ? pubkey : GenericPayload.read(stream, streamNumber, length); + return pubkey != null ? pubkey : GenericPayload.read(version, stream, streamNumber, length); } private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException { @@ -161,7 +161,7 @@ public class Factory { return V5Broadcast.read(stream, streamNumber, length); default: LOG.debug("Encountered unknown broadcast version " + version); - return GenericPayload.read(stream, streamNumber, length); + return GenericPayload.read(version, stream, streamNumber, length); } } } 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 c720caa..467afe7 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java +++ b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java @@ -95,14 +95,13 @@ class V3MessageFactory { payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length); } catch (IOException e) { LOG.trace("Could not parse object payload - using generic payload instead", e); - payload = new GenericPayload(stream, data); + payload = new GenericPayload(version, stream, data); } return new ObjectMessage.Builder() .nonce(nonce) .expiresTime(expiresTime) .objectType(objectType) - .version(version) .stream(stream) .payload(payload) .build(); diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java index c1a3cdf..b698a97 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java @@ -24,7 +24,9 @@ import ch.dissem.bitmessage.entity.valueobject.Label; import java.util.List; public interface MessageRepository { - List<String> getLabels(); + List<Label> getLabels(); + + List<Label> getLabels(Label.Type... types); List<Plaintext> findMessages(Label label); diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java index 51f89cc..0d2788c 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java @@ -19,7 +19,8 @@ package ch.dissem.bitmessage.utils; import ch.dissem.bitmessage.entity.payload.ObjectType; /** - * Created by chris on 13.04.15. + * Some utilities to handle strings. + * TODO: Probably this should be split in a GUI related and an SQL related utility class. */ public class Strings { public static StringBuilder join(byte[]... objects) { @@ -49,6 +50,15 @@ public class Strings { return streamList; } + public static StringBuilder join(Enum... types) { + StringBuilder streamList = new StringBuilder(); + for (int i = 0; i < types.length; i++) { + if (i > 0) streamList.append(", "); + streamList.append('\'').append(types[i].name()).append('\''); + } + return streamList; + } + public static StringBuilder join(Object... objects) { StringBuilder streamList = new StringBuilder(); for (int i = 0; i < objects.length; i++) { diff --git a/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java b/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java index ecaf2b1..f0f8ddb 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java @@ -37,12 +37,12 @@ import static org.junit.Assert.assertTrue; public class EncryptionTest { @Test public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException { - GenericPayload before = new GenericPayload(1, Security.randomBytes(100)); + GenericPayload before = new GenericPayload(0, 1, Security.randomBytes(100)); PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey()); - GenericPayload after = GenericPayload.read(cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 1, 100); + GenericPayload after = GenericPayload.read(0, cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 1, 100); assertEquals(before, after); } diff --git a/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java b/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java index 4f733fc..dc2eb85 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java @@ -48,7 +48,6 @@ public class SignatureTest { ObjectMessage objectMessage = new ObjectMessage.Builder() .objectType(ObjectType.PUBKEY) .stream(1) - .version(1) .payload(privateKey.getPubkey()) .build(); objectMessage.sign(privateKey); diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java index 029fe05..9b97eb1 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java @@ -73,7 +73,7 @@ public class SecurityTest { .nonce(new byte[8]) .expiresTime(UnixTime.now(+2 * DAY)) // 5 minutes .objectType(0) - .payload(GenericPayload.read(new ByteArrayInputStream(new byte[0]), 1, 0)) + .payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0)) .build(); Security.checkProofOfWork(objectMessage, 1000, 1000); } @@ -84,7 +84,7 @@ public class SecurityTest { .nonce(new byte[8]) .expiresTime(UnixTime.now(+2 * DAY)) .objectType(0) - .payload(GenericPayload.read(new ByteArrayInputStream(new byte[0]), 1, 0)) + .payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0)) .build(); Security.doProofOfWork(objectMessage, new MultiThreadedPOWEngine(), 1000, 1000); Security.checkProofOfWork(objectMessage, 1000, 1000); 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 2f5bbb9..13dafc2 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java @@ -120,6 +120,7 @@ public class Connection implements Runnable { + msg.getPayload().getCommand()); } } + if (socket.isClosed()) state = DISCONNECTED; } catch (SocketTimeoutException ignore) { if (state == ACTIVE) { sendQueue(); @@ -160,7 +161,7 @@ public class Connection implements Runnable { ctx.getInventory().storeObject(objectMessage); } catch (InsufficientProofOfWorkException e) { try { - File f = new File(System.getProperty("user.home")+"/jabit.error/" + objectMessage.getInventoryVector() + ".inv"); + File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv"); f.createNewFile(); objectMessage.write(new FileOutputStream(f)); } catch (IOException e1) { diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java index 1777d2c..0911ad6 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java @@ -21,6 +21,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.ports.MessageRepository; +import ch.dissem.bitmessage.utils.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,13 +38,41 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito private InternalContext ctx; @Override - public List<String> getLabels() { - List<String> result = new LinkedList<>(); + public List<Label> getLabels() { + List<Label> result = new LinkedList<>(); try { Statement stmt = getConnection().createStatement(); - ResultSet rs = stmt.executeQuery("SELECT label FROM Label ORDER BY ord"); + ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label ORDER BY ord"); while (rs.next()) { - result.add(rs.getString("label")); + result.add(getLabel(rs)); + } + } catch (SQLException e) { + LOG.error(e.getMessage(), e); + } + return result; + } + + private Label getLabel(ResultSet rs) throws SQLException { + String typeName = rs.getString("type"); + Label.Type type = null; + if (typeName != null) { + type = Label.Type.valueOf(typeName); + } + Label label = new Label(rs.getString("label"), type, rs.getInt("color")); + label.setId(rs.getLong("id")); + + return label; + } + + @Override + public List<Label> getLabels(Label.Type... types) { + List<Label> result = new LinkedList<>(); + try { + Statement stmt = getConnection().createStatement(); + ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE type IN (" + Strings.join(types) + + ") ORDER BY ord"); + while (rs.next()) { + result.add(getLabel(rs)); } } catch (SQLException e) { LOG.error(e.getMessage(), e); @@ -94,9 +123,9 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito List<Label> result = new ArrayList<>(); try { Statement stmt = getConnection().createStatement(); - ResultSet rs = stmt.executeQuery("SELECT label, color FROM Label WHERE id IN (SELECT label_id FROM Message_Label WHERE message_id=" + messageId + ")"); + ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE id IN (SELECT label_id FROM Message_Label WHERE message_id=" + messageId + ")"); while (rs.next()) { - result.add(new Label(rs.getString("label"), rs.getInt("color"))); + result.add(getLabel(rs)); } } catch (SQLException e) { LOG.error(e.getMessage(), e); diff --git a/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql b/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql index 80c4cad..19a0185 100644 --- a/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql +++ b/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql @@ -1,21 +1,23 @@ CREATE TABLE Message ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - sender VARCHAR(40) NOT NULL, - recipient VARCHAR(40) NOT NULL, - data BLOB NOT NULL, - sent BIGINT, - received BIGINT, - status VARCHAR(20) NOT NULL, + id BIGINT AUTO_INCREMENT PRIMARY KEY, + sender VARCHAR(40) NOT NULL, + recipient VARCHAR(40) NOT NULL, + data BLOB NOT NULL, + sent BIGINT, + received BIGINT, + status VARCHAR(20) NOT NULL, FOREIGN KEY (sender) REFERENCES Address (address), FOREIGN KEY (recipient) REFERENCES Address (address) ); CREATE TABLE Label ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - label VARCHAR(255) NOT NULL, - color INT, - ord BIGINT, + id BIGINT AUTO_INCREMENT PRIMARY KEY, + label VARCHAR(255) NOT NULL, + type VARCHAR(20), + color INT NOT NULL DEFAULT X'FF000000', + ord BIGINT, + CONSTRAINT UC_label UNIQUE (label), CONSTRAINT UC_order UNIQUE (ord) ); @@ -29,7 +31,8 @@ CREATE TABLE Message_Label ( FOREIGN KEY (label_id) REFERENCES Label (id) ); -INSERT INTO Label(label, ord) VALUES ('Inbox', 0); -INSERT INTO Label(label, ord) VALUES ('Sent', 10); -INSERT INTO Label(label, ord) VALUES ('Drafts', 20); -INSERT INTO Label(label, ord) VALUES ('Trash', 100); +INSERT INTO Label(label, type, color, ord) VALUES ('Inbox', 'INBOX', X'FF0000FF', 0); +INSERT INTO Label(label, type, color, ord) VALUES ('Drafts', 'DRAFTS', X'FFFF9900', 10); +INSERT INTO Label(label, type, color, ord) VALUES ('Sent', 'SENT', X'FFFFFF00', 20); +INSERT INTO Label(label, type, ord) VALUES ('Unread', 'UNREAD', 90); +INSERT INTO Label(label, type, ord) VALUES ('Trash', 'TRASH', 100);