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,7 +39,7 @@ 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>
@ -51,7 +50,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
* <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,9 +164,40 @@ 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);
if (contact.getPubkey() == null) {
ctx.requestPubkey(contact); 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 {
void receive(Plaintext plaintext); void receive(Plaintext plaintext);

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,10 +128,14 @@ 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) {
if (signature == null) {
Encode.varInt(0, out);
} else {
Encode.varInt(signature.length, out); Encode.varInt(signature.length, out);
out.write(signature); out.write(signature);
} }
} }
}
@Override @Override
public void write(OutputStream out) throws IOException { public void write(OutputStream out) throws IOException {
@ -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

@ -14,8 +14,10 @@ CREATE TABLE Message (
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),
color INT NOT NULL DEFAULT X'FF000000',
ord BIGINT, 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);