Implemented sending messages (and fixed a few bugs on the way)

This closes issue #3
This commit is contained in:
Christian Basler 2015-05-29 13:17:00 +02:00
parent 3d618ffeb4
commit 274c16b748
29 changed files with 357 additions and 138 deletions

View File

@ -9,6 +9,9 @@ import ch.dissem.bitmessage.repository.JdbcAddressRepository;
import ch.dissem.bitmessage.repository.JdbcInventory; import ch.dissem.bitmessage.repository.JdbcInventory;
import ch.dissem.bitmessage.repository.JdbcMessageRepository; import ch.dissem.bitmessage.repository.JdbcMessageRepository;
import ch.dissem.bitmessage.repository.JdbcNodeRegistry; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -113,6 +116,7 @@ public class Application {
switch (command) { switch (command) {
case "a": case "a":
addIdentity(); addIdentity();
identities = ctx.addresses().getIdentities();
break; break;
case "b": case "b":
return; return;
@ -164,6 +168,7 @@ public class Application {
switch (command) { switch (command) {
case "a": case "a":
addContact(); addContact();
contacts = ctx.addresses().getContacts();
break; break;
case "b": case "b":
return; return;
@ -201,12 +206,18 @@ public class Application {
System.out.println(address.getAddress()); System.out.println(address.getAddress());
System.out.println("Stream: " + address.getStream()); System.out.println("Stream: " + address.getStream());
System.out.println("Version: " + address.getVersion()); 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() { private void messages() {
String command; String command;
List<Plaintext> messages = ctx.messages().findMessages(Plaintext.Status.NEW); List<Plaintext> messages = ctx.messages().findMessages(Plaintext.Status.RECEIVED);
do { do {
System.out.println(); System.out.println();
int i = 0; int i = 0;
@ -247,6 +258,8 @@ public class Application {
System.out.println(); System.out.println();
System.out.println(message.getText()); System.out.println(message.getText());
System.out.println(); System.out.println();
System.out.println("Labels: "+ message.getLabels());
System.out.println();
String command; String command;
do { do {
System.out.println("r) reply"); System.out.println("r) reply");
@ -269,14 +282,79 @@ public class Application {
private void compose() { private void compose() {
System.out.println(); System.out.println();
System.out.println("TODO"); BitmessageAddress from = selectAddress(true);
// TODO 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) { private void compose(BitmessageAddress from, BitmessageAddress to, String subject) {
System.out.println(); System.out.println();
System.out.println("TODO"); System.out.println("From: " + from);
// TODO 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) { private boolean yesNo(String question) {

View File

@ -20,9 +20,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.Plaintext.Encoding; import ch.dissem.bitmessage.entity.Plaintext.Encoding;
import ch.dissem.bitmessage.entity.payload.GetPubkey; import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.*; import ch.dissem.bitmessage.ports.*;
@ -32,6 +30,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.TreeSet; 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. * 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: * You'll need the Builder to create a BitmessageContext, and set the following properties:
* <ul> * <ul>
* <li>addressRepo</li> * <li>addressRepo</li>
* <li>inventory</li> * <li>inventory</li>
* <li>nodeRegistry</li> * <li>nodeRegistry</li>
* <li>networkHandler</li> * <li>networkHandler</li>
* <li>messageRepo</li> * <li>messageRepo</li>
* <li>streams</li> * <li>streams</li>
* </ul> * </ul>
* The default implementations in the different module builds can be used. * The default implementations in the different module builds can be used.
* <p> * <p/>
* The port defaults to 8444 (the default Bitmessage port) * The port defaults to 8444 (the default Bitmessage port)
*/ */
public class BitmessageContext { public class BitmessageContext {
@ -81,6 +80,7 @@ public class BitmessageContext {
features features
)); ));
ctx.getAddressRepo().save(identity); ctx.getAddressRepo().save(identity);
// TODO: this should happen in a separate thread
ctx.sendPubkey(identity, identity.getStream()); ctx.sendPubkey(identity, identity.getStream());
return identity; return identity;
} }
@ -93,6 +93,7 @@ public class BitmessageContext {
if (from.getPrivateKey() == null) { if (from.getPrivateKey() == null) {
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); 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() Plaintext msg = new Plaintext.Builder()
.from(from) .from(from)
.to(to) .to(to)
@ -100,10 +101,15 @@ public class BitmessageContext {
.message(subject, message) .message(subject, message)
.build(); .build();
if (to.getPubkey() == null) { if (to.getPubkey() == null) {
tryToFindMatchingPubkey(to);
}
if (to.getPubkey() == null) {
LOG.info("Public key is missing from recipient. Requesting.");
requestPubkey(from, to); requestPubkey(from, to);
msg.setStatus(PUBKEY_REQUESTED); msg.setStatus(PUBKEY_REQUESTED);
ctx.getMessageRepository().save(msg); ctx.getMessageRepository().save(msg);
} else { } else {
LOG.info("Sending message.");
msg.setStatus(DOING_PROOF_OF_WORK); msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg); ctx.getMessageRepository().save(msg);
ctx.send( 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 { try {
long expires = UnixTime.now(+timeToLive); long expires = UnixTime.now(+timeToLive);
LOG.info("Expires at " + expires); LOG.info("Expires at " + expires);
ObjectMessage object = new ObjectMessage.Builder() ObjectMessage object = new ObjectMessage.Builder()
.stream(stream) .stream(stream)
.version(version)
.expiresTime(expires) .expiresTime(expires)
.payload(payload) .payload(payload)
.build(); .build();
@ -159,8 +164,39 @@ public class BitmessageContext {
public void addContact(BitmessageAddress contact) { public void addContact(BitmessageAddress contact) {
ctx.getAddressRepo().save(contact); ctx.getAddressRepo().save(contact);
// TODO: search pubkey in inventory tryToFindMatchingPubkey(contact);
ctx.requestPubkey(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 { public interface Listener {

View File

@ -20,6 +20,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.*; import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.exception.DecryptionFailedException; import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.ports.NetworkHandler; import ch.dissem.bitmessage.ports.NetworkHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -28,9 +29,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import static ch.dissem.bitmessage.entity.Plaintext.Status.DOING_PROOF_OF_WORK; import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
import static ch.dissem.bitmessage.entity.Plaintext.Status.NEW;
import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT;
import static ch.dissem.bitmessage.utils.UnixTime.DAY; import static ch.dissem.bitmessage.utils.UnixTime.DAY;
class DefaultMessageListener implements NetworkHandler.MessageListener { class DefaultMessageListener implements NetworkHandler.MessageListener {
@ -107,6 +106,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
msg.setStatus(SENT); msg.setStatus(SENT);
ctx.getMessageRepository().save(msg); ctx.getMessageRepository().save(msg);
} }
ctx.getAddressRepo().save(address);
} }
} catch (DecryptionFailedException ignore) { } catch (DecryptionFailedException ignore) {
LOG.debug(ignore.getMessage(), ignore); LOG.debug(ignore.getMessage(), ignore);
@ -119,7 +119,8 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
msg.getPlaintext().setTo(identity); msg.getPlaintext().setTo(identity);
object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey()); 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()); ctx.getMessageRepository().save(msg.getPlaintext());
listener.receive(msg.getPlaintext()); listener.receive(msg.getPlaintext());
break; break;

View File

@ -145,17 +145,16 @@ public class InternalContext {
LOG.info("Expires at " + expires); LOG.info("Expires at " + expires);
ObjectMessage object = new ObjectMessage.Builder() ObjectMessage object = new ObjectMessage.Builder()
.stream(to.getStream()) .stream(to.getStream())
.version(to.getVersion())
.expiresTime(expires) .expiresTime(expires)
.payload(payload) .payload(payload)
.build(); .build();
Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
if (object.isSigned()) { if (object.isSigned()) {
object.sign(from.getPrivateKey()); object.sign(from.getPrivateKey());
} }
if (object instanceof Encrypted) { if (payload instanceof Encrypted) {
object.encrypt(to.getPubkey()); object.encrypt(to.getPubkey());
} }
Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
inventory.storeObject(object); inventory.storeObject(object);
networkHandler.offer(object.getInventoryVector()); networkHandler.offer(object.getInventoryVector());
} catch (IOException e) { } catch (IOException e) {
@ -169,7 +168,6 @@ public class InternalContext {
LOG.info("Expires at " + expires); LOG.info("Expires at " + expires);
ObjectMessage response = new ObjectMessage.Builder() ObjectMessage response = new ObjectMessage.Builder()
.stream(targetStream) .stream(targetStream)
.version(identity.getVersion())
.expiresTime(expires) .expiresTime(expires)
.payload(identity.getPubkey()) .payload(identity.getPubkey())
.build(); .build();
@ -196,7 +194,6 @@ public class InternalContext {
LOG.info("Expires at " + expires); LOG.info("Expires at " + expires);
ObjectMessage response = new ObjectMessage.Builder() ObjectMessage response = new ObjectMessage.Builder()
.stream(contact.getStream()) .stream(contact.getStream())
.version(contact.getVersion())
.expiresTime(expires) .expiresTime(expires)
.payload(new GetPubkey(contact)) .payload(new GetPubkey(contact))
.build(); .build();

View File

@ -50,7 +50,7 @@ public class ObjectMessage implements MessagePayload {
nonce = builder.nonce; nonce = builder.nonce;
expiresTime = builder.expiresTime; expiresTime = builder.expiresTime;
objectType = builder.objectType; objectType = builder.objectType;
version = builder.version; version = builder.payload.getVersion();
stream = builder.streamNumber; stream = builder.streamNumber;
payload = builder.payload; payload = builder.payload;
} }
@ -177,7 +177,6 @@ public class ObjectMessage implements MessagePayload {
private byte[] nonce; private byte[] nonce;
private long expiresTime; private long expiresTime;
private long objectType = -1; private long objectType = -1;
private long version = -1;
private long streamNumber; private long streamNumber;
private ObjectPayload payload; private ObjectPayload payload;
@ -204,11 +203,6 @@ public class ObjectMessage implements MessagePayload {
return this; return this;
} }
public Builder version(long version) {
this.version = version;
return this;
}
public Builder stream(long streamNumber) { public Builder stream(long streamNumber) {
this.streamNumber = streamNumber; this.streamNumber = streamNumber;
return this; return this;

View File

@ -16,7 +16,6 @@
package ch.dissem.bitmessage.entity; package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Decode; import ch.dissem.bitmessage.utils.Decode;
@ -129,8 +128,12 @@ public class Plaintext implements Streamable {
Encode.varInt(ack.length, out); Encode.varInt(ack.length, out);
out.write(ack); out.write(ack);
if (includeSignature) { if (includeSignature) {
Encode.varInt(signature.length, out); if (signature == null) {
out.write(signature); 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 { public enum Encoding {
IGNORE(0), TRIVIAL(1), SIMPLE(2); IGNORE(0), TRIVIAL(1), SIMPLE(2);
@ -203,15 +240,13 @@ public class Plaintext implements Streamable {
} }
public enum Status { public enum Status {
DRAFT,
// For sent messages // For sent messages
PUBKEY_REQUESTED, PUBKEY_REQUESTED,
DOING_PROOF_OF_WORK, DOING_PROOF_OF_WORK,
SENT, SENT,
SENT_ACKNOWLEDGED, SENT_ACKNOWLEDGED,
RECEIVED
// For received messages
NEW,
READ
} }
public static final class Builder { public static final class Builder {
@ -233,7 +268,7 @@ public class Plaintext implements Streamable {
private long sent; private long sent;
private long received; private long received;
private Status status; private Status status;
private Set<Label> labels = new TreeSet<>(); private Set<Label> labels = new HashSet<>();
public Builder() { public Builder() {
} }
@ -365,26 +400,4 @@ public class Plaintext implements Streamable {
return new Plaintext(this); 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);
}
} }

View File

@ -31,7 +31,8 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted {
protected CryptoBox encrypted; protected CryptoBox encrypted;
protected Plaintext plaintext; 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.stream = stream;
this.encrypted = encrypted; this.encrypted = encrypted;
this.plaintext = plaintext; this.plaintext = plaintext;

View File

@ -184,7 +184,7 @@ public class CryptoBox implements Streamable {
} }
public Builder curveType(int curveType) { 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; this.curveType = curveType;
return this; return this;
} }

View File

@ -31,13 +31,14 @@ public class GenericPayload extends ObjectPayload {
private long stream; private long stream;
private byte[] data; private byte[] data;
public GenericPayload(long stream, byte[] data) { public GenericPayload(long version, long stream, byte[] data) {
super(version);
this.stream = stream; this.stream = stream;
this.data = data; this.data = data;
} }
public static GenericPayload read(InputStream is, long stream, int length) throws IOException { public static GenericPayload read(long version, InputStream is, long stream, int length) throws IOException {
return new GenericPayload(stream, Decode.bytes(is, length)); return new GenericPayload(version, stream, Decode.bytes(is, length));
} }
@Override @Override

View File

@ -31,6 +31,7 @@ public class GetPubkey extends ObjectPayload {
private byte[] ripeTag; private byte[] ripeTag;
public GetPubkey(BitmessageAddress address) { public GetPubkey(BitmessageAddress address) {
super(address.getVersion());
this.stream = address.getStream(); this.stream = address.getStream();
if (address.getVersion() < 4) if (address.getVersion() < 4)
this.ripeTag = address.getRipe(); this.ripeTag = address.getRipe();
@ -38,13 +39,14 @@ public class GetPubkey extends ObjectPayload {
this.ripeTag = address.getTag(); 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.stream = stream;
this.ripeTag = ripeOrTag; this.ripeTag = ripeOrTag;
} }
public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException { 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));
} }
/** /**

View File

@ -33,11 +33,13 @@ public class Msg extends ObjectPayload implements Encrypted {
private Plaintext plaintext; private Plaintext plaintext;
private Msg(long stream, CryptoBox encrypted) { private Msg(long stream, CryptoBox encrypted) {
super(1);
this.stream = stream; this.stream = stream;
this.encrypted = encrypted; this.encrypted = encrypted;
} }
public Msg(Plaintext plaintext) { public Msg(Plaintext plaintext) {
super(1);
this.stream = plaintext.getStream(); this.stream = plaintext.getStream();
this.plaintext = plaintext; this.plaintext = plaintext;
} }

View File

@ -26,10 +26,21 @@ import java.io.OutputStream;
* The payload of an 'object' command. This is shared by the network. * The payload of an 'object' command. This is shared by the network.
*/ */
public abstract class ObjectPayload implements Streamable { public abstract class ObjectPayload implements Streamable {
private final long version;
protected ObjectPayload(long version) {
this.version = version;
}
public abstract ObjectType getType(); public abstract ObjectType getType();
public abstract long getStream(); public abstract long getStream();
public long getVersion() {
return version;
}
public boolean isSigned() { public boolean isSigned() {
return false; return false;
} }

View File

@ -29,12 +29,14 @@ import static ch.dissem.bitmessage.utils.Security.sha512;
public abstract class Pubkey extends ObjectPayload { public abstract class Pubkey extends ObjectPayload {
public final static long LATEST_VERSION = 4; public final static long LATEST_VERSION = 4;
protected Pubkey(long version) {
super(version);
}
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) { public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
return ripemd160(sha512(publicSigningKey, publicEncryptionKey)); return ripemd160(sha512(publicSigningKey, publicEncryptionKey));
} }
public abstract long getVersion();
public abstract byte[] getSigningKey(); public abstract byte[] getSigningKey();
public abstract byte[] getEncryptionKey(); public abstract byte[] getEncryptionKey();

View File

@ -32,10 +32,12 @@ public class V2Pubkey extends Pubkey {
protected byte[] publicSigningKey; // 64 Bytes protected byte[] publicSigningKey; // 64 Bytes
protected byte[] publicEncryptionKey; // 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; stream = builder.streamNumber;
behaviorBitfield = builder.behaviorBitfield; behaviorBitfield = builder.behaviorBitfield;
publicSigningKey = add0x04(builder.publicSigningKey); publicSigningKey = add0x04(builder.publicSigningKey);
@ -118,7 +120,7 @@ public class V2Pubkey extends Pubkey {
} }
public V2Pubkey build() { public V2Pubkey build() {
return new V2Pubkey(this); return new V2Pubkey(2, this);
} }
} }
} }

View File

@ -33,7 +33,8 @@ public class V3Pubkey extends V2Pubkey {
long extraBytes; long extraBytes;
byte[] signature; byte[] signature;
protected V3Pubkey(Builder builder) { protected V3Pubkey(long version, Builder builder) {
super(version);
stream = builder.streamNumber; stream = builder.streamNumber;
behaviorBitfield = builder.behaviorBitfield; behaviorBitfield = builder.behaviorBitfield;
publicSigningKey = add0x04(builder.publicSigningKey); publicSigningKey = add0x04(builder.publicSigningKey);
@ -95,6 +96,24 @@ public class V3Pubkey extends V2Pubkey {
this.signature = signature; 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 { public static class Builder {
private long streamNumber; private long streamNumber;
private int behaviorBitfield; private int behaviorBitfield;
@ -143,25 +162,7 @@ public class V3Pubkey extends V2Pubkey {
} }
public V3Pubkey build() { 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);
}
} }

View File

@ -25,12 +25,12 @@ import java.io.OutputStream;
* Broadcasts are version 4 or 5. * Broadcasts are version 4 or 5.
*/ */
public class V4Broadcast extends Broadcast { public class V4Broadcast extends Broadcast {
protected V4Broadcast(long stream, CryptoBox encrypted) { protected V4Broadcast(long version, long stream, CryptoBox encrypted) {
super(stream, encrypted, null); super(version, stream, encrypted, null);
} }
public static V4Broadcast read(InputStream in, long stream, int length) throws IOException { 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 @Override

View File

@ -39,12 +39,14 @@ public class V4Pubkey extends Pubkey implements Encrypted {
private V3Pubkey decrypted; private V3Pubkey decrypted;
private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) { private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) {
super(4);
this.stream = stream; this.stream = stream;
this.tag = tag; this.tag = tag;
this.encrypted = encrypted; this.encrypted = encrypted;
} }
public V4Pubkey(V3Pubkey decrypted) { public V4Pubkey(V3Pubkey decrypted) {
super(4);
this.decrypted = decrypted; this.decrypted = decrypted;
this.stream = decrypted.stream; this.stream = decrypted.stream;
this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe()); this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe());

View File

@ -29,7 +29,7 @@ public class V5Broadcast extends V4Broadcast {
private byte[] tag; private byte[] tag;
private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) { private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) {
super(stream, encrypted); super(5, stream, encrypted);
this.tag = tag; this.tag = tag;
} }

View File

@ -16,13 +16,17 @@
package ch.dissem.bitmessage.entity.valueobject; package ch.dissem.bitmessage.entity.valueobject;
import java.util.Objects;
public class Label { public class Label {
private Object id; private Object id;
private String label; private String label;
private Type type;
private int color; private int color;
public Label(String label, int color) { public Label(String label, Type type, int color) {
this.label = label; this.label = label;
this.type = type;
this.color = color; this.color = color;
} }
@ -45,4 +49,33 @@ public class Label {
public Object getId() { public Object getId() {
return id; 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
}
} }

View File

@ -124,7 +124,7 @@ public class Factory {
} }
// fallback: just store the message - we don't really care what it is // fallback: just store the message - we don't really care what it is
// LOG.info("Unexpected object type: " + objectType); // 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 { 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 { private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true); 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 { 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); return V5Broadcast.read(stream, streamNumber, length);
default: default:
LOG.debug("Encountered unknown broadcast version " + version); LOG.debug("Encountered unknown broadcast version " + version);
return GenericPayload.read(stream, streamNumber, length); return GenericPayload.read(version, stream, streamNumber, length);
} }
} }
} }

View File

@ -95,14 +95,13 @@ class V3MessageFactory {
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length); payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length);
} catch (IOException e) { } catch (IOException e) {
LOG.trace("Could not parse object payload - using generic payload instead", 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() return new ObjectMessage.Builder()
.nonce(nonce) .nonce(nonce)
.expiresTime(expiresTime) .expiresTime(expiresTime)
.objectType(objectType) .objectType(objectType)
.version(version)
.stream(stream) .stream(stream)
.payload(payload) .payload(payload)
.build(); .build();

View File

@ -24,7 +24,9 @@ import ch.dissem.bitmessage.entity.valueobject.Label;
import java.util.List; import java.util.List;
public interface MessageRepository { public interface MessageRepository {
List<String> getLabels(); List<Label> getLabels();
List<Label> getLabels(Label.Type... types);
List<Plaintext> findMessages(Label label); List<Plaintext> findMessages(Label label);

View File

@ -19,7 +19,8 @@ package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.payload.ObjectType; 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 class Strings {
public static StringBuilder join(byte[]... objects) { public static StringBuilder join(byte[]... objects) {
@ -49,6 +50,15 @@ public class Strings {
return streamList; 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) { public static StringBuilder join(Object... objects) {
StringBuilder streamList = new StringBuilder(); StringBuilder streamList = new StringBuilder();
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {

View File

@ -37,12 +37,12 @@ import static org.junit.Assert.assertTrue;
public class EncryptionTest { public class EncryptionTest {
@Test @Test
public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException { 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); PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey()); 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); assertEquals(before, after);
} }

View File

@ -48,7 +48,6 @@ public class SignatureTest {
ObjectMessage objectMessage = new ObjectMessage.Builder() ObjectMessage objectMessage = new ObjectMessage.Builder()
.objectType(ObjectType.PUBKEY) .objectType(ObjectType.PUBKEY)
.stream(1) .stream(1)
.version(1)
.payload(privateKey.getPubkey()) .payload(privateKey.getPubkey())
.build(); .build();
objectMessage.sign(privateKey); objectMessage.sign(privateKey);

View File

@ -73,7 +73,7 @@ public class SecurityTest {
.nonce(new byte[8]) .nonce(new byte[8])
.expiresTime(UnixTime.now(+2 * DAY)) // 5 minutes .expiresTime(UnixTime.now(+2 * DAY)) // 5 minutes
.objectType(0) .objectType(0)
.payload(GenericPayload.read(new ByteArrayInputStream(new byte[0]), 1, 0)) .payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
.build(); .build();
Security.checkProofOfWork(objectMessage, 1000, 1000); Security.checkProofOfWork(objectMessage, 1000, 1000);
} }
@ -84,7 +84,7 @@ public class SecurityTest {
.nonce(new byte[8]) .nonce(new byte[8])
.expiresTime(UnixTime.now(+2 * DAY)) .expiresTime(UnixTime.now(+2 * DAY))
.objectType(0) .objectType(0)
.payload(GenericPayload.read(new ByteArrayInputStream(new byte[0]), 1, 0)) .payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
.build(); .build();
Security.doProofOfWork(objectMessage, new MultiThreadedPOWEngine(), 1000, 1000); Security.doProofOfWork(objectMessage, new MultiThreadedPOWEngine(), 1000, 1000);
Security.checkProofOfWork(objectMessage, 1000, 1000); Security.checkProofOfWork(objectMessage, 1000, 1000);

View File

@ -120,6 +120,7 @@ public class Connection implements Runnable {
+ msg.getPayload().getCommand()); + msg.getPayload().getCommand());
} }
} }
if (socket.isClosed()) state = DISCONNECTED;
} catch (SocketTimeoutException ignore) { } catch (SocketTimeoutException ignore) {
if (state == ACTIVE) { if (state == ACTIVE) {
sendQueue(); sendQueue();
@ -160,7 +161,7 @@ public class Connection implements Runnable {
ctx.getInventory().storeObject(objectMessage); ctx.getInventory().storeObject(objectMessage);
} catch (InsufficientProofOfWorkException e) { } catch (InsufficientProofOfWorkException e) {
try { 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(); f.createNewFile();
objectMessage.write(new FileOutputStream(f)); objectMessage.write(new FileOutputStream(f));
} catch (IOException e1) { } catch (IOException e1) {

View File

@ -21,6 +21,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.Strings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -37,13 +38,41 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private InternalContext ctx; private InternalContext ctx;
@Override @Override
public List<String> getLabels() { public List<Label> getLabels() {
List<String> result = new LinkedList<>(); List<Label> result = new LinkedList<>();
try { try {
Statement stmt = getConnection().createStatement(); 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()) { 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) { } catch (SQLException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
@ -94,9 +123,9 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
List<Label> result = new ArrayList<>(); List<Label> result = new ArrayList<>();
try { try {
Statement stmt = getConnection().createStatement(); 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()) { while (rs.next()) {
result.add(new Label(rs.getString("label"), rs.getInt("color"))); result.add(getLabel(rs));
} }
} catch (SQLException e) { } catch (SQLException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);

View File

@ -1,21 +1,23 @@
CREATE TABLE Message ( CREATE TABLE Message (
id BIGINT AUTO_INCREMENT PRIMARY KEY, id BIGINT AUTO_INCREMENT PRIMARY KEY,
sender VARCHAR(40) NOT NULL, sender VARCHAR(40) NOT NULL,
recipient VARCHAR(40) NOT NULL, recipient VARCHAR(40) NOT NULL,
data BLOB NOT NULL, data BLOB NOT NULL,
sent BIGINT, sent BIGINT,
received BIGINT, received BIGINT,
status VARCHAR(20) NOT NULL, status VARCHAR(20) NOT NULL,
FOREIGN KEY (sender) REFERENCES Address (address), FOREIGN KEY (sender) REFERENCES Address (address),
FOREIGN KEY (recipient) REFERENCES Address (address) FOREIGN KEY (recipient) REFERENCES Address (address)
); );
CREATE TABLE Label ( CREATE TABLE Label (
id BIGINT AUTO_INCREMENT PRIMARY KEY, id BIGINT AUTO_INCREMENT PRIMARY KEY,
label VARCHAR(255) NOT NULL, label VARCHAR(255) NOT NULL,
color INT, type VARCHAR(20),
ord BIGINT, color INT NOT NULL DEFAULT X'FF000000',
ord BIGINT,
CONSTRAINT UC_label UNIQUE (label), CONSTRAINT UC_label UNIQUE (label),
CONSTRAINT UC_order UNIQUE (ord) CONSTRAINT UC_order UNIQUE (ord)
); );
@ -29,7 +31,8 @@ CREATE TABLE Message_Label (
FOREIGN KEY (label_id) REFERENCES Label (id) FOREIGN KEY (label_id) REFERENCES Label (id)
); );
INSERT INTO Label(label, ord) VALUES ('Inbox', 0); INSERT INTO Label(label, type, color, ord) VALUES ('Inbox', 'INBOX', X'FF0000FF', 0);
INSERT INTO Label(label, ord) VALUES ('Sent', 10); INSERT INTO Label(label, type, color, ord) VALUES ('Drafts', 'DRAFTS', X'FFFF9900', 10);
INSERT INTO Label(label, ord) VALUES ('Drafts', 20); INSERT INTO Label(label, type, color, ord) VALUES ('Sent', 'SENT', X'FFFFFF00', 20);
INSERT INTO Label(label, ord) VALUES ('Trash', 100); INSERT INTO Label(label, type, ord) VALUES ('Unread', 'UNREAD', 90);
INSERT INTO Label(label, type, ord) VALUES ('Trash', 'TRASH', 100);