Broadcasts. Receiving seems to work, but there still seems to be a problem with sending them.

This commit is contained in:
Christian Basler 2015-06-09 22:45:24 +02:00
parent f76864eebd
commit b4683bba68
26 changed files with 466 additions and 172 deletions

View File

@ -22,9 +22,6 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.networking.NetworkNode; import ch.dissem.bitmessage.networking.NetworkNode;
import ch.dissem.bitmessage.repository.*; import ch.dissem.bitmessage.repository.*;
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;
@ -72,6 +69,7 @@ public class Application {
System.out.println("available commands:"); System.out.println("available commands:");
System.out.println("i) identities"); System.out.println("i) identities");
System.out.println("c) contacts"); System.out.println("c) contacts");
System.out.println("s) subscriptions");
System.out.println("m) messages"); System.out.println("m) messages");
System.out.println("e) exit"); System.out.println("e) exit");
@ -85,6 +83,9 @@ public class Application {
case "c": case "c":
contacts(); contacts();
break; break;
case "s":
subscriptions();
break;
case "m": case "m":
messages(); messages();
break; break;
@ -181,7 +182,7 @@ public class Application {
command = nextCommand(); command = nextCommand();
switch (command) { switch (command) {
case "a": case "a":
addContact(); addContact(false);
contacts = ctx.addresses().getContacts(); contacts = ctx.addresses().getContacts();
break; break;
case "b": case "b":
@ -197,7 +198,7 @@ public class Application {
} while (!"b".equals(command)); } while (!"b".equals(command));
} }
private void addContact() { private void addContact(boolean isSubscription) {
System.out.println(); System.out.println();
System.out.println("Please enter the Bitmessage address you want to add"); System.out.println("Please enter the Bitmessage address you want to add");
try { try {
@ -207,12 +208,56 @@ public class Application {
if (alias.length() > 0) { if (alias.length() > 0) {
address.setAlias(alias); address.setAlias(alias);
} }
if (isSubscription) {
ctx.addSubscribtion(address);
}
ctx.addContact(address); ctx.addContact(address);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); System.out.println(e.getMessage());
} }
} }
private void subscriptions() {
String command;
List<BitmessageAddress> subscriptions = ctx.addresses().getSubscriptions();
do {
System.out.println();
int i = 0;
for (BitmessageAddress contact : subscriptions) {
i++;
System.out.print(i + ") ");
if (contact.getAlias() != null) {
System.out.println(contact.getAlias() + " (" + contact.getAddress() + ")");
} else {
System.out.println(contact.getAddress());
}
}
if (i == 0) {
System.out.println("You have no subscriptions yet.");
}
System.out.println();
System.out.println("a) add subscription");
System.out.println("b) back");
command = nextCommand();
switch (command) {
case "a":
addContact(true);
subscriptions = ctx.addresses().getSubscriptions();
break;
case "b":
return;
default:
try {
int index = Integer.parseInt(command) - 1;
address(subscriptions.get(index));
} catch (NumberFormatException e) {
System.out.println("Unknown command. Please try again.");
}
}
} while (!"b".equals(command));
}
private void address(BitmessageAddress address) { private void address(BitmessageAddress address) {
System.out.println(); System.out.println();
if (address.getAlias() != null) if (address.getAlias() != null)
@ -244,12 +289,16 @@ public class Application {
} }
System.out.println(); System.out.println();
System.out.println("c) compose message"); System.out.println("c) compose message");
System.out.println("s) compose broadcast");
System.out.println("b) back"); System.out.println("b) back");
command = scanner.nextLine().trim(); command = scanner.nextLine().trim();
switch (command) { switch (command) {
case "c": case "c":
compose(); compose(false);
break;
case "s":
compose(true);
break; break;
case "b": case "b":
return; return;
@ -272,7 +321,7 @@ 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("Labels: " + message.getLabels());
System.out.println(); System.out.println();
String command; String command;
do { do {
@ -294,10 +343,10 @@ public class Application {
} while (!"b".equalsIgnoreCase(command)); } while (!"b".equalsIgnoreCase(command));
} }
private void compose() { private void compose(boolean broadcast) {
System.out.println(); System.out.println();
BitmessageAddress from = selectAddress(true); BitmessageAddress from = selectAddress(true);
BitmessageAddress to = selectAddress(false); BitmessageAddress to = (broadcast ? null : selectAddress(false));
compose(from, to, null); compose(from, to, null);
} }
@ -352,9 +401,12 @@ public class Application {
} }
private void compose(BitmessageAddress from, BitmessageAddress to, String subject) { private void compose(BitmessageAddress from, BitmessageAddress to, String subject) {
boolean broadcast = (to == null);
System.out.println(); System.out.println();
System.out.println("From: " + from); System.out.println("From: " + from);
System.out.println("To: " + to); if (!broadcast) {
System.out.println("To: " + to);
}
if (subject != null) { if (subject != null) {
System.out.println("Subject: " + subject); System.out.println("Subject: " + subject);
} else { } else {
@ -368,7 +420,11 @@ public class Application {
line = nextCommand(); line = nextCommand();
message.append(line).append('\n'); message.append(line).append('\n');
} while (line.length() > 0 || !yesNo("Send message?")); } while (line.length() > 0 || !yesNo("Send message?"));
ctx.send(from, to, subject, message.toString()); if (broadcast) {
ctx.broadcast(from, subject, message.toString());
} else {
ctx.send(from, to, subject, message.toString());
}
} }
private boolean yesNo(String question) { private boolean yesNo(String question) {

View File

@ -16,71 +16,12 @@
package ch.dissem.bitmessage.demo; package ch.dissem.bitmessage.demo;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.networking.NetworkNode;
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.Base58;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Scanner;
/**
* Created by chris on 06.04.15.
*/
public class Main { public class Main {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
final BitmessageAddress address = new BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e"); // System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "TRACE");
// System.setProperty("org.slf4j.simpleLogger.logFile", "./trace.log");
new Application(); new Application();
//
//
// List<ObjectMessage> objects = new JdbcInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY);
// System.out.println("Address version: " + address.getVersion());
// System.out.println("Address stream: " + address.getStream());
// for (ObjectMessage o : objects) {
//// if (!o.isSignatureValid()) System.out.println("Invalid signature.");
//// System.out.println(o.getPayload().getSignature().length);
// V4Pubkey pubkey = (V4Pubkey) o.getPayload();
// if (Arrays.equals(address.getTag(), pubkey.getTag())) {
// System.out.println("Pubkey found!");
// try {
// System.out.println("IV: " + o.getInventoryVector());
// address.setPubkey(pubkey);
// } catch (Exception ignore) {
// System.out.println("But setPubkey failed? " + address.getRipe().length + "/" + pubkey.getRipe().length);
// System.out.println("Failed address: " + generateAddress(address.getStream(), address.getVersion(), pubkey.getRipe()));
// if (Arrays.equals(address.getRipe(), pubkey.getRipe())) {
// ignore.printStackTrace();
// }
// }
// }
// }
}
public static String generateAddress(long stream, long version, byte[] ripe) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
Encode.varInt(version, os);
Encode.varInt(stream, os);
os.write(ripe);
byte[] checksum = Security.doubleSha512(os.toByteArray());
os.write(checksum, 0, 4);
return "BM-" + Base58.encode(os.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
} }

View File

@ -19,10 +19,11 @@ package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.BitmessageAddress; 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.payload.*; import ch.dissem.bitmessage.entity.payload.*;
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.exception.DecryptionFailedException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.*; import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Security; import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime; import ch.dissem.bitmessage.utils.UnixTime;
@ -35,6 +36,8 @@ import java.util.Collection;
import java.util.TreeSet; import java.util.TreeSet;
import static ch.dissem.bitmessage.entity.Plaintext.Status.*; import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
import static ch.dissem.bitmessage.utils.UnixTime.DAY; import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/** /**
@ -59,6 +62,8 @@ public class BitmessageContext {
private final InternalContext ctx; private final InternalContext ctx;
private Listener listener;
private BitmessageContext(Builder builder) { private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder); ctx = new InternalContext(builder);
} }
@ -89,12 +94,34 @@ public class BitmessageContext {
// TODO // TODO
} }
public void broadcast(BitmessageAddress from, String subject, String message) {
// TODO: all this should happen in a separate thread
Plaintext msg = new Plaintext.Builder(BROADCAST)
.from(from)
.message(subject, message)
.build();
LOG.info("Sending message.");
msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg);
ctx.send(
from,
from,
Factory.getBroadcast(from, msg),
+2 * DAY,
0,
0
);
msg.setStatus(SENT);
ctx.getMessageRepository().save(msg);
}
public void send(BitmessageAddress from, BitmessageAddress to, String subject, String message) { public void send(BitmessageAddress from, BitmessageAddress to, String subject, String message) {
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 // TODO: all this should happen in a separate thread
Plaintext msg = new Plaintext.Builder() Plaintext msg = new Plaintext.Builder(MSG)
.from(from) .from(from)
.to(to) .to(to)
.message(subject, message) .message(subject, message)
@ -154,6 +181,7 @@ public class BitmessageContext {
} }
public void startup(Listener listener) { public void startup(Listener listener) {
this.listener = listener;
ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener)); ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener));
} }
@ -176,7 +204,7 @@ public class BitmessageContext {
if (address.getVersion() == 4) { if (address.getVersion() == 4) {
V4Pubkey v4Pubkey = (V4Pubkey) pubkey; V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) { if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) {
v4Pubkey.decrypt(address.getPubkeyDecryptionKey()); v4Pubkey.decrypt(address.getPublicDecryptionKey());
if (object.isSignatureValid(v4Pubkey)) { if (object.isSignatureValid(v4Pubkey)) {
address.setPubkey(v4Pubkey); address.setPubkey(v4Pubkey);
ctx.getAddressRepo().save(address); ctx.getAddressRepo().save(address);
@ -198,6 +226,25 @@ public class BitmessageContext {
} }
} }
public void addSubscribtion(BitmessageAddress address) {
address.setSubscribed(true);
ctx.getAddressRepo().save(address);
tryToFindBroadcastsForAddress(address);
}
private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) {
try {
Broadcast broadcast = (Broadcast) object.getPayload();
broadcast.decrypt(address);
listener.receive(broadcast.getPlaintext());
} catch (DecryptionFailedException ignore) {
} catch (Exception e) {
LOG.debug(e.getMessage(), e);
}
}
}
public interface Listener { public interface Listener {
void receive(Plaintext plaintext); void receive(Plaintext plaintext);
} }

View File

@ -82,7 +82,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
V4Pubkey v4Pubkey = (V4Pubkey) pubkey; V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
address = ctx.getAddressRepo().findContact(v4Pubkey.getTag()); address = ctx.getAddressRepo().findContact(v4Pubkey.getTag());
if (address != null) { if (address != null) {
v4Pubkey.decrypt(address.getPubkeyDecryptionKey()); v4Pubkey.decrypt(address.getPublicDecryptionKey());
} }
} else { } else {
address = ctx.getAddressRepo().findContact(pubkey.getRipe()); address = ctx.getAddressRepo().findContact(pubkey.getRipe());
@ -91,8 +91,8 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
address.setPubkey(pubkey); address.setPubkey(pubkey);
LOG.debug("Got pubkey for contact " + address); LOG.debug("Got pubkey for contact " + address);
List<Plaintext> messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address); List<Plaintext> messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
LOG.debug("Sending " + messages.size() + " messages for contact " + address);
for (Plaintext msg : messages) { for (Plaintext msg : messages) {
// TODO: send messages enqueued for this address
msg.setStatus(DOING_PROOF_OF_WORK); msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg); ctx.getMessageRepository().save(msg);
ctx.send( ctx.send(
@ -118,14 +118,18 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
try { try {
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
msg.getPlaintext().setTo(identity); msg.getPlaintext().setTo(identity);
object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey()); if (!object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey())) {
msg.getPlaintext().setStatus(RECEIVED); LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD)); } else {
ctx.getMessageRepository().save(msg.getPlaintext()); msg.getPlaintext().setStatus(RECEIVED);
listener.receive(msg.getPlaintext()); msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD));
msg.getPlaintext().setInventoryVector(object.getInventoryVector());
ctx.getMessageRepository().save(msg.getPlaintext());
listener.receive(msg.getPlaintext());
}
break; break;
} catch (DecryptionFailedException ignore) { } catch (DecryptionFailedException ignore) {
LOG.debug(ignore.getMessage(), ignore); LOG.trace(ignore.getMessage(), ignore);
} }
} }
} }
@ -135,9 +139,13 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
// V5Broadcast v5 = broadcast instanceof V5Broadcast ? (V5Broadcast) broadcast : null; // V5Broadcast v5 = broadcast instanceof V5Broadcast ? (V5Broadcast) broadcast : null;
for (BitmessageAddress subscription : ctx.getAddressRepo().getSubscriptions()) { for (BitmessageAddress subscription : ctx.getAddressRepo().getSubscriptions()) {
try { try {
broadcast.decrypt(subscription.getPubkeyDecryptionKey()); broadcast.decrypt(subscription.getPublicDecryptionKey());
object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey()); if (!object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey())) {
listener.receive(broadcast.getPlaintext()); LOG.warn("Broadcast with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
} else {
broadcast.getPlaintext().setInventoryVector(object.getInventoryVector());
listener.receive(broadcast.getPlaintext());
}
} catch (DecryptionFailedException ignore) { } catch (DecryptionFailedException ignore) {
} }
} }

View File

@ -16,9 +16,8 @@
package ch.dissem.bitmessage; package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.*;
import ch.dissem.bitmessage.entity.Encrypted; import ch.dissem.bitmessage.entity.payload.Broadcast;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.GetPubkey; import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.ObjectPayload; import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.ports.*; import ch.dissem.bitmessage.ports.*;
@ -141,6 +140,7 @@ public class InternalContext {
public void send(BitmessageAddress from, BitmessageAddress to, ObjectPayload payload, long timeToLive, long nonceTrialsPerByte, long extraBytes) { public void send(BitmessageAddress from, BitmessageAddress to, ObjectPayload payload, long timeToLive, long nonceTrialsPerByte, long extraBytes) {
try { try {
if (to == null) to = from;
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()
@ -151,10 +151,17 @@ public class InternalContext {
if (object.isSigned()) { if (object.isSigned()) {
object.sign(from.getPrivateKey()); object.sign(from.getPrivateKey());
} }
if (payload instanceof Encrypted) { if (payload instanceof Broadcast) {
((Broadcast) payload).encrypt();
} else if (payload instanceof Encrypted) {
object.encrypt(to.getPubkey()); object.encrypt(to.getPubkey());
} }
Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes); Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
if (payload instanceof PlaintextHolder) {
Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
plaintext.setInventoryVector(object.getInventoryVector());
messageRepository.save(plaintext);
}
inventory.storeObject(object); inventory.storeObject(object);
networkHandler.offer(object.getInventoryVector()); networkHandler.offer(object.getInventoryVector());
} catch (IOException e) { } catch (IOException e) {
@ -172,13 +179,13 @@ public class InternalContext {
.payload(identity.getPubkey()) .payload(identity.getPubkey())
.build(); .build();
response.sign(identity.getPrivateKey()); response.sign(identity.getPrivateKey());
response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).getEncoded(false)); response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes); Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
if (response.isSigned()) { if (response.isSigned()) {
response.sign(identity.getPrivateKey()); response.sign(identity.getPrivateKey());
} }
if (response instanceof Encrypted) { if (response instanceof Encrypted) {
response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).getEncoded(false)); response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
} }
inventory.storeObject(response); inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector()); networkHandler.offer(response.getInventoryVector());

View File

@ -42,7 +42,7 @@ public class BitmessageAddress {
/** /**
* Used for V4 address encryption. It's easier to just create it regardless of address version. * Used for V4 address encryption. It's easier to just create it regardless of address version.
*/ */
private final byte[] pubkeyDecryptionKey; private final byte[] publicDecryptionKey;
private String address; private String address;
@ -61,14 +61,20 @@ public class BitmessageAddress {
ByteArrayOutputStream os = new ByteArrayOutputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream();
Encode.varInt(version, os); Encode.varInt(version, os);
Encode.varInt(stream, os); Encode.varInt(stream, os);
// for the tag, the checksum has to be created with 0x00 padding if (version < 4) {
byte[] checksum = Security.doubleSha512(os.toByteArray(), ripe); byte[] checksum = Security.sha512(os.toByteArray(), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64); this.tag = null;
this.pubkeyDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
} else {
// for tag and decryption key, the checksum has to be created with 0x00 padding
byte[] checksum = Security.doubleSha512(os.toByteArray(), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64);
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
}
// but for the address and its checksum they need to be stripped // but for the address and its checksum they need to be stripped
int offset = Bytes.numberOfLeadingZeros(ripe); int offset = Bytes.numberOfLeadingZeros(ripe);
os.write(ripe, offset, ripe.length - offset); os.write(ripe, offset, ripe.length - offset);
checksum = Security.doubleSha512(os.toByteArray()); byte[] checksum = Security.doubleSha512(os.toByteArray());
os.write(checksum, 0, 4); os.write(checksum, 0, 4);
this.address = "BM-" + Base58.encode(os.toByteArray()); this.address = "BM-" + Base58.encode(os.toByteArray());
} catch (IOException e) { } catch (IOException e) {
@ -103,9 +109,15 @@ public class BitmessageAddress {
if (expectedChecksum[i] != checksum[i]) if (expectedChecksum[i] != checksum[i])
throw new IllegalArgumentException("Checksum of address failed"); throw new IllegalArgumentException("Checksum of address failed");
} }
checksum = Security.doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); if (version < 4) {
this.tag = Arrays.copyOfRange(checksum, 32, 64); checksum = Security.sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
this.pubkeyDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); this.tag = null;
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
} else {
checksum = Security.doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64);
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
}
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -145,8 +157,11 @@ public class BitmessageAddress {
this.pubkey = pubkey; this.pubkey = pubkey;
} }
public byte[] getPubkeyDecryptionKey() { /**
return pubkeyDecryptionKey; * Returns the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts.
*/
public byte[] getPublicDecryptionKey() {
return publicDecryptionKey;
} }
public PrivateKey getPrivateKey() { public PrivateKey getPrivateKey() {
@ -165,10 +180,6 @@ public class BitmessageAddress {
this.alias = alias; this.alias = alias;
} }
public void setSubscribed(boolean subscribed) {
this.subscribed = subscribed;
}
@Override @Override
public String toString() { public String toString() {
return alias != null ? alias : address; return alias != null ? alias : address;
@ -196,4 +207,12 @@ public class BitmessageAddress {
public int hashCode() { public int hashCode() {
return Arrays.hashCode(ripe); return Arrays.hashCode(ripe);
} }
public boolean isSubscribed() {
return subscribed;
}
public void setSubscribed(boolean subscribed) {
this.subscribed = subscribed;
}
} }

View File

@ -16,6 +16,7 @@
package ch.dissem.bitmessage.entity; package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
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;
@ -28,11 +29,13 @@ import java.util.*;
* The unencrypted message to be sent by 'msg' or 'broadcast'. * The unencrypted message to be sent by 'msg' or 'broadcast'.
*/ */
public class Plaintext implements Streamable { public class Plaintext implements Streamable {
private final Type type;
private final BitmessageAddress from; private final BitmessageAddress from;
private final long encoding; private final long encoding;
private final byte[] message; private final byte[] message;
private final byte[] ack; private final byte[] ack;
private Object id; private Object id;
private InventoryVector inventoryVector;
private BitmessageAddress to; private BitmessageAddress to;
private byte[] signature; private byte[] signature;
private Status status; private Status status;
@ -43,6 +46,8 @@ public class Plaintext implements Streamable {
private Plaintext(Builder builder) { private Plaintext(Builder builder) {
id = builder.id; id = builder.id;
inventoryVector = builder.inventoryVector;
type = builder.type;
from = builder.from; from = builder.from;
to = builder.to; to = builder.to;
encoding = builder.encoding; encoding = builder.encoding;
@ -55,14 +60,14 @@ public class Plaintext implements Streamable {
labels = builder.labels; labels = builder.labels;
} }
public static Plaintext read(InputStream in) throws IOException { public static Plaintext read(Type type, InputStream in) throws IOException {
return readWithoutSignature(in) return readWithoutSignature(type, in)
.signature(Decode.varBytes(in)) .signature(Decode.varBytes(in))
.build(); .build();
} }
public static Plaintext.Builder readWithoutSignature(InputStream in) throws IOException { public static Plaintext.Builder readWithoutSignature(Type type, InputStream in) throws IOException {
return new Builder() return new Builder(type)
.addressVersion(Decode.varInt(in)) .addressVersion(Decode.varInt(in))
.stream(Decode.varInt(in)) .stream(Decode.varInt(in))
.behaviorBitfield(Decode.int32(in)) .behaviorBitfield(Decode.int32(in))
@ -70,10 +75,22 @@ public class Plaintext implements Streamable {
.publicEncryptionKey(Decode.bytes(in, 64)) .publicEncryptionKey(Decode.bytes(in, 64))
.nonceTrialsPerByte(Decode.varInt(in)) .nonceTrialsPerByte(Decode.varInt(in))
.extraBytes(Decode.varInt(in)) .extraBytes(Decode.varInt(in))
.destinationRipe(Decode.bytes(in, 20)) .destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null)
.encoding(Decode.varInt(in)) .encoding(Decode.varInt(in))
.message(Decode.varBytes(in)) .message(Decode.varBytes(in))
.ack(Decode.varBytes(in)); .ack(type == Type.MSG ? Decode.varBytes(in) : null);
}
public InventoryVector getInventoryVector() {
return inventoryVector;
}
public void setInventoryVector(InventoryVector inventoryVector) {
this.inventoryVector = inventoryVector;
}
public Type getType() {
return type;
} }
public byte[] getMessage() { public byte[] getMessage() {
@ -121,12 +138,16 @@ public class Plaintext implements Streamable {
out.write(from.getPubkey().getEncryptionKey(), 1, 64); out.write(from.getPubkey().getEncryptionKey(), 1, 64);
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out); Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out);
Encode.varInt(from.getPubkey().getExtraBytes(), out); Encode.varInt(from.getPubkey().getExtraBytes(), out);
out.write(to.getRipe()); if (type == Type.MSG) {
out.write(to.getRipe());
}
Encode.varInt(encoding, out); Encode.varInt(encoding, out);
Encode.varInt(message.length, out); Encode.varInt(message.length, out);
out.write(message); out.write(message);
Encode.varInt(ack.length, out); if (type == Type.MSG) {
out.write(ack); Encode.varInt(ack.length, out);
out.write(ack);
}
if (includeSignature) { if (includeSignature) {
if (signature == null) { if (signature == null) {
Encode.varInt(0, out); Encode.varInt(0, out);
@ -249,8 +270,14 @@ public class Plaintext implements Streamable {
RECEIVED RECEIVED
} }
public enum Type {
MSG, BROADCAST
}
public static final class Builder { public static final class Builder {
private Object id; private Object id;
private InventoryVector inventoryVector;
private Type type;
private BitmessageAddress from; private BitmessageAddress from;
private BitmessageAddress to; private BitmessageAddress to;
private long addressVersion; private long addressVersion;
@ -270,7 +297,8 @@ public class Plaintext implements Streamable {
private Status status; private Status status;
private Set<Label> labels = new HashSet<>(); private Set<Label> labels = new HashSet<>();
public Builder() { public Builder(Type type) {
this.type = type;
} }
public Builder id(Object id) { public Builder id(Object id) {
@ -278,12 +306,19 @@ public class Plaintext implements Streamable {
return this; return this;
} }
public Builder IV(InventoryVector iv) {
this.inventoryVector = iv;
return this;
}
public Builder from(BitmessageAddress address) { public Builder from(BitmessageAddress address) {
from = address; from = address;
return this; return this;
} }
public Builder to(BitmessageAddress address) { public Builder to(BitmessageAddress address) {
if (type != Type.MSG && to != null)
throw new IllegalArgumentException("recipient address only allowed for msg");
to = address; to = address;
return this; return this;
} }
@ -324,6 +359,7 @@ public class Plaintext implements Streamable {
} }
private Builder destinationRipe(byte[] ripe) { private Builder destinationRipe(byte[] ripe) {
if (type != Type.MSG && ripe != null) throw new IllegalArgumentException("ripe only allowed for msg");
this.destinationRipe = ripe; this.destinationRipe = ripe;
return this; return this;
} }
@ -354,6 +390,7 @@ public class Plaintext implements Streamable {
} }
public Builder ack(byte[] ack) { public Builder ack(byte[] ack) {
if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ack only allowed for msg");
this.ack = ack; this.ack = ack;
return this; return this;
} }
@ -395,7 +432,7 @@ public class Plaintext implements Streamable {
behaviorBitfield behaviorBitfield
)); ));
} }
if (to == null) { if (to == null && type != Type.BROADCAST) {
to = new BitmessageAddress(0, 0, destinationRipe); to = new BitmessageAddress(0, 0, destinationRipe);
} }
return new Plaintext(this); return new Plaintext(this);

View File

@ -0,0 +1,21 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.entity;
public interface PlaintextHolder {
Plaintext getPlaintext();
}

View File

@ -16,17 +16,22 @@
package ch.dissem.bitmessage.entity.payload; package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Encrypted; import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.exception.DecryptionFailedException; import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.Security;
import java.io.IOException; import java.io.IOException;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
/** /**
* Users who are subscribed to the sending address will see the message appear in their inbox. * Users who are subscribed to the sending address will see the message appear in their inbox.
* Broadcasts are version 4 or 5. * Broadcasts are version 4 or 5.
*/ */
public abstract class Broadcast extends ObjectPayload implements Encrypted { public abstract class Broadcast extends ObjectPayload implements Encrypted, PlaintextHolder {
protected final long stream; protected final long stream;
protected CryptoBox encrypted; protected CryptoBox encrypted;
protected Plaintext plaintext; protected Plaintext plaintext;
@ -38,11 +43,16 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted {
this.plaintext = plaintext; this.plaintext = plaintext;
} }
public static long getVersion(BitmessageAddress address) {
return address.getVersion() < 4 ? 4 : 5;
}
@Override @Override
public long getStream() { public long getStream() {
return stream; return stream;
} }
@Override
public Plaintext getPlaintext() { public Plaintext getPlaintext() {
return plaintext; return plaintext;
} }
@ -52,9 +62,17 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted {
this.encrypted = new CryptoBox(plaintext, publicKey); this.encrypted = new CryptoBox(plaintext, publicKey);
} }
public void encrypt() throws IOException {
encrypt(Security.createPublicKey(plaintext.getFrom().getPublicDecryptionKey()).getEncoded(false));
}
@Override @Override
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
plaintext = Plaintext.read(encrypted.decrypt(privateKey)); plaintext = Plaintext.read(BROADCAST, encrypted.decrypt(privateKey));
}
public void decrypt(BitmessageAddress address) throws IOException, DecryptionFailedException {
decrypt(address.getPublicDecryptionKey());
} }
@Override @Override

View File

@ -18,16 +18,19 @@ package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.Encrypted; import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.exception.DecryptionFailedException; import ch.dissem.bitmessage.exception.DecryptionFailedException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
/** /**
* Used for person-to-person messages. * Used for person-to-person messages.
*/ */
public class Msg extends ObjectPayload implements Encrypted { public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
private long stream; private long stream;
private CryptoBox encrypted; private CryptoBox encrypted;
private Plaintext plaintext; private Plaintext plaintext;
@ -48,6 +51,7 @@ public class Msg extends ObjectPayload implements Encrypted {
return new Msg(stream, CryptoBox.read(in, length)); return new Msg(stream, CryptoBox.read(in, length));
} }
@Override
public Plaintext getPlaintext() { public Plaintext getPlaintext() {
return plaintext; return plaintext;
} }
@ -89,7 +93,7 @@ public class Msg extends ObjectPayload implements Encrypted {
@Override @Override
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
plaintext = Plaintext.read(encrypted.decrypt(privateKey)); plaintext = Plaintext.read(MSG, encrypted.decrypt(privateKey));
} }
@Override @Override

View File

@ -16,6 +16,9 @@
package ch.dissem.bitmessage.entity.payload; package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -25,12 +28,18 @@ 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 version, long stream, CryptoBox encrypted) { protected V4Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) {
super(version, stream, encrypted, null); super(version, stream, encrypted, plaintext);
}
public V4Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
super(4, senderAddress.getStream(), null, plaintext);
if (senderAddress.getVersion() >= 4)
throw new IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.getVersion());
} }
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(4, stream, CryptoBox.read(in, length)); return new V4Broadcast(4, stream, CryptoBox.read(in, length), null);
} }
@Override @Override

View File

@ -16,6 +16,8 @@
package ch.dissem.bitmessage.entity.payload; package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.utils.Decode; import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException; import java.io.IOException;
@ -29,10 +31,17 @@ 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(5, stream, encrypted); super(5, stream, encrypted, null);
this.tag = tag; this.tag = tag;
} }
public V5Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
super(5, senderAddress.getStream(), null, plaintext);
if (senderAddress.getVersion() < 4)
throw new IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.getVersion());
this.tag = senderAddress.getTag();
}
public static V5Broadcast read(InputStream is, long stream, int length) throws IOException { public static V5Broadcast read(InputStream is, long stream, int length) throws IOException {
return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32)); return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32));
} }

View File

@ -19,6 +19,7 @@ package ch.dissem.bitmessage.factory;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.NetworkMessage; import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.*; import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -164,4 +165,12 @@ public class Factory {
return GenericPayload.read(version, stream, streamNumber, length); return GenericPayload.read(version, stream, streamNumber, length);
} }
} }
public static ObjectPayload getBroadcast(BitmessageAddress sendingAddress, Plaintext plaintext) {
if (sendingAddress.getVersion() < 4) {
return new V4Broadcast(sendingAddress, plaintext);
} else {
return new V5Broadcast(sendingAddress, plaintext);
}
}
} }

View File

@ -0,0 +1,39 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.ObjectMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class DebugUtils {
private final static Logger LOG = LoggerFactory.getLogger(DebugUtils.class);
public static void saveToFile(ObjectMessage objectMessage) {
try {
File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv");
f.createNewFile();
objectMessage.write(new FileOutputStream(f));
} catch (IOException e) {
LOG.debug(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.V4Broadcast;
import ch.dissem.bitmessage.entity.payload.V5Broadcast;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class DecryptionTest {
@Test
public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V4Broadcast.payload");
V4Broadcast broadcast = (V4Broadcast) objectMessage.getPayload();
broadcast.decrypt(new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"));
assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject());
}
@Test
public void ensureV5BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V5Broadcast.payload");
V5Broadcast broadcast = (V5Broadcast) objectMessage.getPayload();
broadcast.decrypt(new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"));
assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject());
}
}

View File

@ -84,7 +84,7 @@ public class BitmessageAddressTest {
public void testV4PubkeyImport() throws IOException, DecryptionFailedException { public void testV4PubkeyImport() throws IOException, DecryptionFailedException {
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload"); ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
object.decrypt(address.getPubkeyDecryptionKey()); object.decrypt(address.getPublicDecryptionKey());
V4Pubkey pubkey = (V4Pubkey) object.getPayload(); V4Pubkey pubkey = (V4Pubkey) object.getPayload();
assertTrue(object.isSignatureValid(pubkey)); assertTrue(object.isSignatureValid(pubkey));
address.setPubkey(pubkey); address.setPubkey(pubkey);

View File

@ -27,6 +27,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -78,7 +79,7 @@ public class SerializationTest {
@Test @Test
public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws IOException, DecryptionFailedException { public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws IOException, DecryptionFailedException {
Plaintext p1 = new Plaintext.Builder() Plaintext p1 = new Plaintext.Builder(MSG)
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.to(TestUtils.loadContact()) .to(TestUtils.loadContact())
.message("Subject", "Message") .message("Subject", "Message")
@ -88,7 +89,7 @@ public class SerializationTest {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
p1.write(out); p1.write(out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
Plaintext p2 = Plaintext.read(in); Plaintext p2 = Plaintext.read(MSG, in);
assertEquals(p1, p2); assertEquals(p1, p2);
} }

View File

@ -72,7 +72,7 @@ public class TestUtils {
public static BitmessageAddress loadContact() throws IOException, DecryptionFailedException { public static BitmessageAddress loadContact() throws IOException, DecryptionFailedException {
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload"); ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
object.decrypt(address.getPubkeyDecryptionKey()); object.decrypt(address.getPublicDecryptionKey());
address.setPubkey((V4Pubkey) object.getPayload()); address.setPubkey((V4Pubkey) object.getPayload());
return address; return address;
} }

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip

View File

@ -24,6 +24,7 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener; import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener;
import ch.dissem.bitmessage.utils.DebugUtils;
import ch.dissem.bitmessage.utils.Security; import ch.dissem.bitmessage.utils.Security;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -160,15 +161,10 @@ public class Connection implements Runnable {
listener.receive(objectMessage); listener.receive(objectMessage);
ctx.getInventory().storeObject(objectMessage); ctx.getInventory().storeObject(objectMessage);
} catch (InsufficientProofOfWorkException e) { } catch (InsufficientProofOfWorkException e) {
try { // DebugUtils.saveToFile(objectMessage);
File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv");
f.createNewFile();
objectMessage.write(new FileOutputStream(f));
} catch (IOException e1) {
e1.printStackTrace();
}
} catch (IOException e) { } catch (IOException e) {
LOG.error("Stream " + objectMessage.getStream() + ", object type " + objectMessage.getType() + ": " + e.getMessage(), e); LOG.error("Stream " + objectMessage.getStream() + ", object type " + objectMessage.getType() + ": " + e.getMessage(), e);
DebugUtils.saveToFile(objectMessage);
} }
break; break;
case ADDR: case ADDR:

View File

@ -140,24 +140,27 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
} }
private void update(BitmessageAddress address) throws IOException, SQLException { private void update(BitmessageAddress address) throws IOException, SQLException {
try(Connection connection = config.getConnection()){ try (Connection connection = config.getConnection()) {
PreparedStatement ps = connection.prepareStatement( PreparedStatement ps = connection.prepareStatement(
"UPDATE Address SET alias=?, public_key=?, private_key=? WHERE address=?"); "UPDATE Address SET alias=?, public_key=?, private_key=?, subscribed=? WHERE address=?");
ps.setString(1, address.getAlias()); ps.setString(1, address.getAlias());
writePubkey(ps, 2, address.getPubkey()); writePubkey(ps, 2, address.getPubkey());
writeBlob(ps, 3, address.getPrivateKey()); writeBlob(ps, 3, address.getPrivateKey());
ps.setString(4, address.getAddress()); ps.setBoolean(4, address.isSubscribed());
ps.executeUpdate(); ps.setString(5, address.getAddress());
}} ps.executeUpdate();
}
}
private void insert(BitmessageAddress address) throws IOException, SQLException { private void insert(BitmessageAddress address) throws IOException, SQLException {
try (Connection connection = config.getConnection()) { try (Connection connection = config.getConnection()) {
PreparedStatement ps = connection.prepareStatement( PreparedStatement ps = connection.prepareStatement(
"INSERT INTO Address (address, alias, public_key, private_key) VALUES (?, ?, ?, ?)"); "INSERT INTO Address (address, alias, public_key, private_key, subscribed) VALUES (?, ?, ?, ?, ?)");
ps.setString(1, address.getAddress()); ps.setString(1, address.getAddress());
ps.setString(2, address.getAlias()); ps.setString(2, address.getAlias());
writePubkey(ps, 3, address.getPubkey()); writePubkey(ps, 3, address.getPubkey());
writeBlob(ps, 4, address.getPrivateKey()); writeBlob(ps, 4, address.getPrivateKey());
ps.setBoolean(5, address.isSubscribed());
ps.executeUpdate(); ps.executeUpdate();
} }
} }

View File

@ -19,6 +19,7 @@ package ch.dissem.bitmessage.repository;
import ch.dissem.bitmessage.InternalContext; import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
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 org.slf4j.Logger; import org.slf4j.Logger;
@ -102,12 +103,15 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
List<Plaintext> result = new LinkedList<>(); List<Plaintext> result = new LinkedList<>();
try (Connection connection = config.getConnection()) { try (Connection connection = config.getConnection()) {
Statement stmt = connection.createStatement(); Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, sender, recipient, data, sent, received, status FROM Message WHERE " + where); ResultSet rs = stmt.executeQuery("SELECT id, iv, type, sender, recipient, data, sent, received, status FROM Message WHERE " + where);
while (rs.next()) { while (rs.next()) {
byte[] iv = rs.getBytes("iv");
Blob data = rs.getBlob("data"); Blob data = rs.getBlob("data");
Plaintext.Builder builder = Plaintext.readWithoutSignature(data.getBinaryStream()); Plaintext.Type type = Plaintext.Type.valueOf(rs.getString("type"));
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, data.getBinaryStream());
long id = rs.getLong("id"); long id = rs.getLong("id");
builder.id(id); builder.id(id);
builder.IV(new InventoryVector(iv));
builder.from(ctx.getAddressRepo().getAddress(rs.getString("sender"))); builder.from(ctx.getAddressRepo().getAddress(rs.getString("sender")));
builder.to(ctx.getAddressRepo().getAddress(rs.getString("recipient"))); builder.to(ctx.getAddressRepo().getAddress(rs.getString("recipient")));
builder.sent(rs.getLong("sent")); builder.sent(rs.getLong("sent"));
@ -155,14 +159,14 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
// save message // save message
if (message.getId() == null) { if (message.getId() == null) {
insert(connection, message); insert(connection, message);
// remove existing labels
Statement stmt = connection.createStatement();
stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=" + message.getId());
} else { } else {
update(connection, message); update(connection, message);
} }
// remove existing labels
Statement stmt = connection.createStatement();
stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=" + message.getId());
// save labels // save labels
PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Label VALUES (" + message.getId() + ", ?)"); PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Label VALUES (" + message.getId() + ", ?)");
for (Label label : message.getLabels()) { for (Label label : message.getLabels()) {
@ -186,16 +190,15 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private void insert(Connection connection, Plaintext message) throws SQLException, IOException { private void insert(Connection connection, Plaintext message) throws SQLException, IOException {
PreparedStatement ps = connection.prepareStatement( PreparedStatement ps = connection.prepareStatement(
"INSERT INTO Message (sender, recipient, data, sent, received, status) VALUES (?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); "INSERT INTO Message (iv, type, sender, recipient, data, sent, received, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
ps.setString(1, message.getFrom().getAddress()); ps.setBytes(1, message.getInventoryVector() != null ? message.getInventoryVector().getHash() : null);
ps.setString(2, message.getTo().getAddress()); ps.setString(2, message.getType().name());
writeBlob(ps, 3, message); ps.setString(3, message.getFrom().getAddress());
ps.setLong(4, message.getSent()); ps.setString(4, message.getTo() != null ? message.getTo().getAddress() : null);
ps.setLong(5, message.getReceived()); writeBlob(ps, 5, message);
if (message.getStatus() != null) ps.setLong(6, message.getSent());
ps.setString(6, message.getStatus().name()); ps.setLong(7, message.getReceived());
else ps.setString(8, message.getStatus() != null ? message.getStatus().name() : null);
ps.setString(6, null);
ps.executeUpdate(); ps.executeUpdate();
@ -207,11 +210,12 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private void update(Connection connection, Plaintext message) throws SQLException, IOException { private void update(Connection connection, Plaintext message) throws SQLException, IOException {
PreparedStatement ps = connection.prepareStatement( PreparedStatement ps = connection.prepareStatement(
"UPDATE Message SET sent=?, received=?, status=? WHERE id=?"); "UPDATE Message SET iv=?, sent=?, received=?, status=? WHERE id=?");
ps.setLong(1, message.getSent()); ps.setBytes(1, message.getInventoryVector() != null ? message.getInventoryVector().getHash() : null);
ps.setLong(2, message.getReceived()); ps.setLong(2, message.getSent());
ps.setString(3, message.getStatus() != null ? message.getStatus().name() : null); ps.setLong(3, message.getReceived());
ps.setLong(4, (Long) message.getId()); ps.setString(4, message.getStatus() != null ? message.getStatus().name() : null);
ps.setLong(5, (Long) message.getId());
ps.executeUpdate(); ps.executeUpdate();
} }

View File

@ -1,7 +1,9 @@
CREATE TABLE Message ( CREATE TABLE Message (
id BIGINT AUTO_INCREMENT PRIMARY KEY, id BIGINT AUTO_INCREMENT PRIMARY KEY,
iv BINARY(32) UNIQUE,
type VARCHAR(20) NOT NULL,
sender VARCHAR(40) NOT NULL, sender VARCHAR(40) NOT NULL,
recipient VARCHAR(40) NOT NULL, recipient VARCHAR(40),
data BLOB NOT NULL, data BLOB NOT NULL,
sent BIGINT, sent BIGINT,
received BIGINT, received BIGINT,

View File

@ -20,16 +20,19 @@ import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.InternalContext; import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.Security;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@ -116,7 +119,8 @@ public class JdbcMessageRepositoryTest {
@Test @Test
public void testSave() throws Exception { public void testSave() throws Exception {
Plaintext message = new Plaintext.Builder() Plaintext message = new Plaintext.Builder(MSG)
.IV(new InventoryVector(Security.randomBytes(32)))
.from(identity) .from(identity)
.to(contactA) .to(contactA)
.message("Subject", "Message") .message("Subject", "Message")
@ -132,6 +136,19 @@ public class JdbcMessageRepositoryTest {
List<Plaintext> messages = repo.findMessages(Plaintext.Status.DOING_PROOF_OF_WORK); List<Plaintext> messages = repo.findMessages(Plaintext.Status.DOING_PROOF_OF_WORK);
assertEquals(1, messages.size()); assertEquals(1, messages.size());
assertNotNull(messages.get(0).getInventoryVector());
}
@Test
public void testUpdate() throws Exception {
List<Plaintext> messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
Plaintext message = messages.get(0);
message.setInventoryVector(new InventoryVector(Security.randomBytes(32)));
repo.save(message);
messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
assertEquals(1, messages.size());
assertNotNull(messages.get(0).getInventoryVector());
} }
@Test @Test
@ -143,7 +160,7 @@ public class JdbcMessageRepositoryTest {
} }
private void addMessage(BitmessageAddress from, BitmessageAddress to, Plaintext.Status status, Label... labels) { private void addMessage(BitmessageAddress from, BitmessageAddress to, Plaintext.Status status, Label... labels) {
Plaintext message = new Plaintext.Builder() Plaintext message = new Plaintext.Builder(MSG)
.from(from) .from(from)
.to(to) .to(to)
.message("Subject", "Message") .message("Subject", "Message")