Implemented sending messages (and fixed a few bugs on the way)
This closes issue #3
This commit is contained in:
parent
3d618ffeb4
commit
274c16b748
@ -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<Plaintext> 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) {
|
||||
|
@ -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,7 +39,7 @@ 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>
|
||||
@ -51,7 +50,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
* <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,9 +164,40 @@ public class BitmessageContext {
|
||||
|
||||
public void addContact(BitmessageAddress contact) {
|
||||
ctx.getAddressRepo().save(contact);
|
||||
// TODO: search pubkey in inventory
|
||||
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 {
|
||||
void receive(Plaintext plaintext);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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,10 +128,14 @@ public class Plaintext implements Streamable {
|
||||
Encode.varInt(ack.length, out);
|
||||
out.write(ack);
|
||||
if (includeSignature) {
|
||||
if (signature == null) {
|
||||
Encode.varInt(0, out);
|
||||
} else {
|
||||
Encode.varInt(signature.length, out);
|
||||
out.write(signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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++) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -14,8 +14,10 @@ CREATE TABLE Message (
|
||||
CREATE TABLE Label (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
label VARCHAR(255) NOT NULL,
|
||||
color INT,
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user