A simple command line application (WIP), and a few tests. Unfotrunately, receiving messages doesn't seem to work yet.

This commit is contained in:
Christian Basler 2015-05-22 20:51:57 +02:00
parent 648afbbc75
commit 6b3b361aa3
36 changed files with 757 additions and 298 deletions

View File

@ -0,0 +1,291 @@
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.entity.payload.Pubkey;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Scanner;
/**
* A simple command line Bitmessage application
*/
public class Application {
private final static Logger LOG = LoggerFactory.getLogger(Application.class);
private final Scanner scanner;
private BitmessageContext ctx;
public Application() {
ctx = new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository())
.inventory(new JdbcInventory())
.nodeRegistry(new JdbcNodeRegistry())
.networkHandler(new NetworkNode())
.messageRepo(new JdbcMessageRepository())
.port(48444)
.streams(1)
.build();
ctx.startup(new BitmessageContext.Listener() {
@Override
public void receive(Plaintext plaintext) {
try {
System.out.println(new String(plaintext.getMessage(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
LOG.error(e.getMessage(), e);
}
}
});
scanner = new Scanner(System.in);
String command;
do {
System.out.println();
System.out.println("available commands:");
System.out.println("i) identities");
System.out.println("c) contacts");
System.out.println("m) messages");
System.out.println("e) Exit");
command = nextCommand();
try {
switch (command) {
case "i": {
identities();
break;
}
case "c":
contacts();
break;
case "m":
messages();
break;
case "e":
break;
default:
System.out.println("Unknown command. Please try again.");
}
} catch (Exception e) {
LOG.debug(e.getMessage());
}
} while (!"e".equals(command));
LOG.info("Shutting down client");
ctx.shutdown();
}
private String nextCommand() {
return scanner.nextLine().trim().toLowerCase();
}
private void identities() {
String command;
List<BitmessageAddress> identities = ctx.addresses().getIdentities();
do {
System.out.println();
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());
}
}
if (i == 0) {
System.out.println("You have no identities yet.");
}
System.out.println("a) create identity");
System.out.println("b) back");
command = nextCommand();
switch (command) {
case "a":
addIdentity();
break;
case "b":
return;
default:
try {
int index = Integer.parseInt(command) - 1;
address(identities.get(index));
} catch (NumberFormatException e) {
System.out.println("Unknown command. Please try again.");
}
}
} while (!"b".equals(command));
}
private void addIdentity() {
System.out.println();
BitmessageAddress identity = ctx.createIdentity(yesNo("would you like a shorter address? This will take some time to calculate."), Pubkey.Feature.DOES_ACK);
System.out.println("Please enter an alias for this identity, or an empty string for none");
String alias = nextCommand();
if (alias.length() > 0) {
identity.setAlias(alias);
}
ctx.addresses().save(identity);
}
private void contacts() {
String command;
List<BitmessageAddress> contacts = ctx.addresses().getContacts();
do {
System.out.println();
int i = 0;
for (BitmessageAddress contact : contacts) {
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 contacts yet.");
}
System.out.println();
System.out.println("a) add contact");
System.out.println("b) back");
command = nextCommand();
switch (command) {
case "a":
addContact();
break;
case "b":
return;
default:
try {
int index = Integer.parseInt(command) - 1;
address(contacts.get(index));
} catch (NumberFormatException e) {
System.out.println("Unknown command. Please try again.");
}
}
} while (!"b".equals(command));
}
private void addContact() {
System.out.println();
System.out.println("Please enter the Bitmessage address you want to add");
try {
BitmessageAddress address = new BitmessageAddress(scanner.nextLine().trim());
System.out.println("Please enter an alias for this address, or an empty string for none");
String alias = scanner.nextLine().trim();
if (alias.length() > 0) {
address.setAlias(alias);
}
ctx.addContact(address);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
private void address(BitmessageAddress address) {
System.out.println();
if (address.getAlias() != null)
System.out.println(address.getAlias());
System.out.println(address.getAddress());
System.out.println("Stream: " + address.getStream());
System.out.println("Version: " + address.getVersion());
}
private void messages() {
String command;
List<Plaintext> messages = ctx.messages().findMessages(Plaintext.Status.RECEIVED);
do {
System.out.println();
int i = 0;
for (Plaintext message : messages) {
i++;
System.out.print(i + ") From: " + message.getFrom() + "; Subject: " + message.getSubject());
}
if (i == 0) {
System.out.println("You have no messages.");
}
System.out.println();
System.out.println("c) compose message");
System.out.println("b) back");
command = scanner.nextLine().trim();
switch (command) {
case "c":
compose();
break;
case "b":
return;
default:
try {
int index = Integer.parseInt(command);
show(messages.get(index));
} catch (NumberFormatException | IndexOutOfBoundsException e) {
System.out.println("Unknown command. Please try again.");
}
}
} while (!"b".equalsIgnoreCase(command));
}
private void show(Plaintext message) {
System.out.println();
System.out.println("From: " + message.getFrom());
System.out.println("To: " + message.getTo());
System.out.println("Subject: " + message.getSubject());
System.out.println();
System.out.println(message.getText());
System.out.println();
String command;
do {
System.out.printf("r) reply");
System.out.println("d) delete");
System.out.printf("b) back");
command = nextCommand();
switch (command) {
case "r":
compose(message.getTo(), message.getFrom(), "RE: " + message.getSubject());
break;
case "d":
ctx.messages().remove(message);
case "b":
return;
default:
System.out.println("Unknown command. Please try again.");
}
} while (!"b".equalsIgnoreCase(command));
}
private void compose() {
System.out.println();
System.out.println("TODO");
// TODO
}
private void compose(BitmessageAddress from, BitmessageAddress to, String subject) {
System.out.println();
System.out.println("TODO");
// TODO
}
private boolean yesNo(String question) {
String answer;
do {
System.out.println(question + " (y/n)");
answer = scanner.nextLine();
if ("y".equalsIgnoreCase(answer)) return true;
if ("n".equalsIgnoreCase(answer)) return false;
} while (true);
}
}

View File

@ -18,11 +18,12 @@ 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.networking.NetworkNode;
import ch.dissem.bitmessage.utils.Base58;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
@ -31,48 +32,18 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Scanner;
/**
* Created by chris on 06.04.15.
*/
public class Main {
private final static Logger LOG = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) throws IOException {
final BitmessageAddress address = new BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e");
BitmessageContext ctx = new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository())
.inventory(new JdbcInventory())
.nodeRegistry(new JdbcNodeRegistry())
.networkHandler(new NetworkNode())
.messageRepo(new JdbcMessageRepository())
.port(48444)
.streams(1)
.build();
// ctx.startup(new BitmessageContext.Listener() {
// @Override
// public void receive(Plaintext plaintext) {
// // TODO
// try {
// System.out.println(new String(plaintext.getMessage(), "UTF-8"));
// } catch (UnsupportedEncodingException e) {
// LOG.error(e.getMessage(), e);
// }
// }
// });
// Scanner scanner = new Scanner(System.in);
//// System.out.println("Press Enter to request pubkey for address " + address);
//// scanner.nextLine();
//// ctx.send(1, address.getVersion(), new GetPubkey(address), 3000, 1000, 1000);
//
// System.out.println("Press Enter to exit");
// scanner.nextLine();
// LOG.info("Shutting down client");
// ctx.shutdown();
new Application();
//
//
// List<ObjectMessage> objects = new JdbcInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY);

View File

@ -17,16 +17,17 @@
package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Encrypted;
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.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
@ -34,10 +35,12 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
import static ch.dissem.bitmessage.entity.payload.ObjectType.GET_PUBKEY;
import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG;
import static ch.dissem.bitmessage.entity.payload.ObjectType.PUBKEY;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
@ -53,8 +56,12 @@ public class BitmessageContext {
ctx = new InternalContext(builder);
}
public List<BitmessageAddress> getIdentities() {
return ctx.getAddressRepo().getIdentities();
public AddressRepository addresses() {
return ctx.getAddressRepo();
}
public MessageRepository messages() {
return ctx.getMessageRepository();
}
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
@ -66,6 +73,7 @@ public class BitmessageContext {
features
));
ctx.getAddressRepo().save(identity);
ctx.sendPubkey(identity, identity.getStream());
return identity;
}
@ -134,13 +142,23 @@ public class BitmessageContext {
}
public void startup(Listener listener) {
ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener));
MessageListener messageListener = new DefaultMessageListener(ctx, listener);
for (ObjectMessage object : ctx.getInventory().getObjects(0, 0, PUBKEY, MSG)) {
messageListener.receive(object);
}
ctx.getNetworkHandler().start(messageListener);
}
public void shutdown() {
ctx.getNetworkHandler().stop();
}
public void addContact(BitmessageAddress contact) {
ctx.getAddressRepo().save(contact);
// TODO: search pubkey in inventory
ctx.requestPubkey(contact);
}
public interface Listener {
void receive(Plaintext plaintext);
}

View File

@ -17,13 +17,10 @@
package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.ports.NetworkHandler;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -47,6 +44,8 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
@Override
public void receive(ObjectMessage object) {
ObjectPayload payload = object.getPayload();
if (payload.getType() == null) return;
switch (payload.getType()) {
case GET_PUBKEY: {
receive(object, (GetPubkey) payload);
@ -70,28 +69,8 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
BitmessageAddress identity = ctx.getAddressRepo().findIdentity(getPubkey.getRipeTag());
if (identity != null && identity.getPrivateKey() != null) {
try {
long expires = UnixTime.now(+28 * DAY);
LOG.info("Expires at " + expires);
ObjectMessage response = new ObjectMessage.Builder()
.stream(object.getStream())
.version(identity.getVersion())
.expiresTime(expires)
.payload(identity.getPubkey())
.build();
Security.doProofOfWork(response, ctx.getProofOfWorkEngine(),
ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
if (response.isSigned()) {
response.sign(identity.getPrivateKey());
}
if (response instanceof Encrypted) {
response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).getEncoded(false));
}
ctx.getInventory().storeObject(response);
ctx.getNetworkHandler().offer(response.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
LOG.debug("Got pubkey request for identity " + identity);
ctx.sendPubkey(identity, object.getStream());
}
}
@ -109,8 +88,9 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
if (address != null) {
address.setPubkey(pubkey);
LOG.debug("Got pubkey for contact " + address);
List<Plaintext> messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
for (Plaintext msg:messages){
for (Plaintext msg : messages) {
// TODO: send messages enqueued for this address
msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg);
@ -140,7 +120,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
ctx.getMessageRepository().save(msg.getPlaintext());
listener.receive(msg.getPlaintext());
break;
} catch (IOException ignore) {
} catch (IOException | RuntimeException ignore) {
}
}
}

View File

@ -19,6 +19,7 @@ package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Security;
@ -29,6 +30,8 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.TreeSet;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
* The internal context should normally only be used for port implementations. If you need it in your client
* implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
@ -160,6 +163,51 @@ public class InternalContext {
}
}
public void sendPubkey(BitmessageAddress identity, long targetStream) {
try {
long expires = UnixTime.now(+28 * DAY);
LOG.info("Expires at " + expires);
ObjectMessage response = new ObjectMessage.Builder()
.stream(targetStream)
.version(identity.getVersion())
.expiresTime(expires)
.payload(identity.getPubkey())
.build();
response.sign(identity.getPrivateKey());
response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).getEncoded(false));
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
if (response.isSigned()) {
response.sign(identity.getPrivateKey());
}
if (response instanceof Encrypted) {
response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).getEncoded(false));
}
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
// TODO: save that the pubkey was just sent, and on which stream!
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void requestPubkey(BitmessageAddress contact) {
try {
long expires = UnixTime.now(+2 * DAY);
LOG.info("Expires at " + expires);
ObjectMessage response = new ObjectMessage.Builder()
.stream(contact.getStream())
.version(contact.getVersion())
.expiresTime(expires)
.payload(new GetPubkey(contact))
.build();
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public interface ContextHolder {
void setContext(InternalContext context);
}

View File

@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import static ch.dissem.bitmessage.utils.Decode.bytes;
import static ch.dissem.bitmessage.utils.Decode.varInt;
@ -180,4 +181,19 @@ public class BitmessageAddress {
public byte[] getTag() {
return tag;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BitmessageAddress address = (BitmessageAddress) o;
return Objects.equals(version, address.version) &&
Objects.equals(stream, address.stream) &&
Arrays.equals(ripe, address.ripe);
}
@Override
public int hashCode() {
return Arrays.hashCode(ripe);
}
}

View File

@ -60,15 +60,15 @@ public class NetworkMessage implements Streamable {
}
@Override
public void write(OutputStream stream) throws IOException {
public void write(OutputStream out) throws IOException {
// magic
Encode.int32(MAGIC, stream);
Encode.int32(MAGIC, out);
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
String command = payload.getCommand().name().toLowerCase();
stream.write(command.getBytes("ASCII"));
out.write(command.getBytes("ASCII"));
for (int i = command.length(); i < 12; i++) {
stream.write('\0');
out.write('\0');
}
ByteArrayOutputStream payloadStream = new ByteArrayOutputStream();
@ -78,16 +78,16 @@ public class NetworkMessage implements Streamable {
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
// larger than this.
Encode.int32(payloadBytes.length, stream);
Encode.int32(payloadBytes.length, out);
// checksum
try {
stream.write(getChecksum(payloadBytes));
out.write(getChecksum(payloadBytes));
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
// message payload
stream.write(payloadBytes);
out.write(payloadBytes);
}
}

View File

@ -22,10 +22,7 @@ import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.io.*;
import java.util.*;
/**
@ -121,8 +118,8 @@ public class Plaintext implements Streamable {
Encode.varInt(from.getVersion(), out);
Encode.varInt(from.getStream(), out);
Encode.int32(from.getPubkey().getBehaviorBitfield(), out);
out.write(from.getPubkey().getSigningKey());
out.write(from.getPubkey().getEncryptionKey());
out.write(from.getPubkey().getSigningKey(), 1, 64);
out.write(from.getPubkey().getEncryptionKey(), 1, 64);
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out);
Encode.varInt(from.getPubkey().getExtraBytes(), out);
out.write(to.getRipe());
@ -167,6 +164,30 @@ public class Plaintext implements Streamable {
this.status = status;
}
public String getSubject() {
Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8");
String firstLine = s.nextLine();
if (encoding == 2) {
return firstLine.substring("Subject:".length()).trim();
} else if (firstLine.length() > 50) {
return firstLine.substring(0, 50).trim() + "...";
} else {
return firstLine;
}
}
public String getText() {
try {
String text = new String(message, "UTF-8");
if (encoding == 2) {
return text.substring(text.indexOf("\nBody:") + 6);
}
return text;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public enum Encoding {
IGNORE(0), TRIVIAL(1), SIMPLE(2);
@ -176,23 +197,21 @@ public class Plaintext implements Streamable {
this.code = code;
}
public static Encoding fromCode(long code) {
for (Encoding e : values()) {
if (e.getCode() == code) return e;
}
return null;
}
public long getCode() {
return code;
}
}
public enum Status {
// For sent messages
PUBKEY_REQUESTED,
DOING_PROOF_OF_WORK,
SENT,
ACKNOWLEDGED
SENT_ACKNOWLEDGED,
// For received messages
RECEIVED,
READ
}
public static final class Builder {
@ -340,7 +359,7 @@ public class Plaintext implements Streamable {
publicEncryptionKey,
nonceTrialsPerByte,
extraBytes,
Pubkey.Feature.features(behaviorBitfield)
behaviorBitfield
));
}
if (to == null) {
@ -349,4 +368,26 @@ 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);
}
}

View File

@ -28,16 +28,17 @@ import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
/**
* Created by chris on 09.04.15.
*/
public class CryptoBox implements Streamable {
private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class);
private final byte[] initializationVector;
private final int curveType;
private final ECPoint R;
@ -182,7 +183,7 @@ public class CryptoBox implements Streamable {
}
public Builder curveType(int curveType) {
if (curveType != 0x2CA) System.out.println("Unexpected curve type " + curveType);
if (curveType != 0x2CA) LOG.debug("Unexpected curve type " + curveType);
this.curveType = curveType;
return this;
}

View File

@ -16,6 +16,8 @@
package ch.dissem.bitmessage.entity.payload;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import static ch.dissem.bitmessage.utils.Security.ripemd160;
@ -51,6 +53,10 @@ public abstract class Pubkey extends ObjectPayload {
return 0;
}
public void writeUnencrypted(OutputStream out) throws IOException {
write(out);
}
protected byte[] add0x04(byte[] key) {
if (key.length == 65) return key;
byte[] result = new byte[65];

View File

@ -22,6 +22,8 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Objects;
/**
* A version 3 public key.
@ -100,7 +102,7 @@ public class V3Pubkey extends V2Pubkey {
private byte[] publicEncryptionKey;
private long nonceTrialsPerByte;
private long extraBytes;
private byte[] signature;
private byte[] signature = new byte[0];
public Builder() {
}
@ -144,4 +146,22 @@ public class V3Pubkey extends V2Pubkey {
return new V3Pubkey(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
V3Pubkey pubkey = (V3Pubkey) o;
return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) &&
Objects.equals(extraBytes, pubkey.extraBytes) &&
stream == pubkey.stream &&
behaviorBitfield == pubkey.behaviorBitfield &&
Arrays.equals(publicSigningKey, pubkey.publicSigningKey) &&
Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey);
}
@Override
public int hashCode() {
return Objects.hash(nonceTrialsPerByte, extraBytes);
}
}

View File

@ -23,6 +23,7 @@ import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
/**
* A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is
@ -43,15 +44,18 @@ public class V4Pubkey extends Pubkey implements Encrypted {
}
public V4Pubkey(V3Pubkey decrypted) {
this.decrypted = decrypted;
this.stream = decrypted.stream;
this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe());
this.decrypted = decrypted;
}
public static V4Pubkey read(InputStream in, long stream, int length) throws IOException {
return new V4Pubkey(stream,
Decode.bytes(in, 32),
CryptoBox.read(in, length - 32));
public static V4Pubkey read(InputStream in, long stream, int length, boolean encrypted) throws IOException {
if (encrypted)
return new V4Pubkey(stream,
Decode.bytes(in, 32),
CryptoBox.read(in, length - 32));
else
return new V4Pubkey(V3Pubkey.read(in, stream));
}
@Override
@ -76,6 +80,11 @@ public class V4Pubkey extends Pubkey implements Encrypted {
encrypted.write(stream);
}
@Override
public void writeUnencrypted(OutputStream out) throws IOException {
decrypted.write(out);
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
out.write(tag);
@ -141,4 +150,25 @@ public class V4Pubkey extends Pubkey implements Encrypted {
public long getExtraBytes() {
return decrypted.getExtraBytes();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
V4Pubkey v4Pubkey = (V4Pubkey) o;
if (stream != v4Pubkey.stream) return false;
if (!Arrays.equals(tag, v4Pubkey.tag)) return false;
return !(decrypted != null ? !decrypted.equals(v4Pubkey.decrypted) : v4Pubkey.decrypted != null);
}
@Override
public int hashCode() {
int result = (int) (stream ^ (stream >>> 32));
result = 31 * result + Arrays.hashCode(tag);
result = 31 * result + (decrypted != null ? decrypted.hashCode() : 0);
return result;
}
}

View File

@ -18,6 +18,8 @@ package ch.dissem.bitmessage.entity.valueobject;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Decode;
@ -76,7 +78,7 @@ public class PrivateKey implements Streamable {
int version = (int) Decode.varInt(is);
long stream = Decode.varInt(is);
int len = (int) Decode.varInt(is);
Pubkey pubkey = Factory.readPubkey(version, stream, is, len);
Pubkey pubkey = Factory.readPubkey(version, stream, is, len, false);
len = (int) Decode.varInt(is);
byte[] signingKey = Decode.bytes(is, len);
len = (int) Decode.varInt(is);
@ -97,16 +99,16 @@ public class PrivateKey implements Streamable {
}
@Override
public void write(OutputStream os) throws IOException {
Encode.varInt(pubkey.getVersion(), os);
Encode.varInt(pubkey.getStream(), os);
public void write(OutputStream out) throws IOException {
Encode.varInt(pubkey.getVersion(), out);
Encode.varInt(pubkey.getStream(), out);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pubkey.write(baos);
Encode.varInt(baos.size(), os);
os.write(baos.toByteArray());
Encode.varInt(privateSigningKey.length, os);
os.write(privateSigningKey);
Encode.varInt(privateEncryptionKey.length, os);
os.write(privateEncryptionKey);
pubkey.writeUnencrypted(baos);
Encode.varInt(baos.size(), out);
out.write(baos.toByteArray());
Encode.varInt(privateSigningKey.length, out);
out.write(privateSigningKey);
Encode.varInt(privateEncryptionKey.length, out);
out.write(privateEncryptionKey);
}
}

View File

@ -56,6 +56,12 @@ public class Factory {
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes,
Pubkey.Feature.bitfield(features));
}
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
long nonceTrialsPerByte, long extraBytes, int behaviourBitfield) {
if (publicSigningKey.length != 64 && publicSigningKey.length != 65)
throw new IllegalArgumentException("64 bytes signing key expected, but it was "
+ publicSigningKey.length + " bytes long.");
@ -69,14 +75,14 @@ public class Factory {
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(Pubkey.Feature.bitfield(features))
.behaviorBitfield(behaviourBitfield)
.build();
case 3:
return new V3Pubkey.Builder()
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(Pubkey.Feature.bitfield(features))
.behaviorBitfield(behaviourBitfield)
.nonceTrialsPerByte(nonceTrialsPerByte)
.extraBytes(extraBytes)
.build();
@ -86,7 +92,7 @@ public class Factory {
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(Pubkey.Feature.bitfield(features))
.behaviorBitfield(behaviourBitfield)
.nonceTrialsPerByte(nonceTrialsPerByte)
.extraBytes(extraBytes)
.build()
@ -125,21 +131,21 @@ public class Factory {
return GetPubkey.read(stream, streamNumber, length, version);
}
public static Pubkey readPubkey(long version, long stream, InputStream is, int length) throws IOException {
public static Pubkey readPubkey(long version, long stream, InputStream is, int length, boolean encrypted) throws IOException {
switch ((int) version) {
case 2:
return V2Pubkey.read(is, stream);
case 3:
return V3Pubkey.read(is, stream);
case 4:
return V4Pubkey.read(is, stream, length);
return V4Pubkey.read(is, stream, length, encrypted);
}
LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object");
return null;
}
private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
Pubkey pubkey = readPubkey(version, streamNumber, stream, length);
Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true);
return pubkey != null ? pubkey : GenericPayload.read(stream, streamNumber, length);
}

View File

@ -32,7 +32,7 @@ public interface Inventory {
ObjectMessage getObject(InventoryVector vector);
List<ObjectMessage> getObjects(long stream, long version, ObjectType type);
List<ObjectMessage> getObjects(long stream, long version, ObjectType... types);
void storeObject(ObjectMessage object);

View File

@ -17,7 +17,7 @@
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import org.bouncycastle.asn1.x9.X9ECParameters;
@ -35,6 +35,7 @@ import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.PrivateKey;
import java.security.spec.KeySpec;
import java.util.Arrays;

View File

@ -16,6 +16,8 @@
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.payload.ObjectType;
/**
* Created by chris on 13.04.15.
*/
@ -38,6 +40,15 @@ public class Strings {
return streamList;
}
public static StringBuilder join(ObjectType... types) {
StringBuilder streamList = new StringBuilder();
for (int i = 0; i < types.length; i++) {
if (i > 0) streamList.append(", ");
streamList.append(types[i].getNumber());
}
return streamList;
}
public static StringBuilder join(Object... objects) {
StringBuilder streamList = new StringBuilder();
for (int i = 0; i < objects.length; i++) {

View File

@ -16,21 +16,23 @@
package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.CryptoBox;
import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.Security;
import org.junit.Ignore;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
import java.io.IOException;
import java.security.KeyPair;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Created by chris on 10.05.15.
*/
public class EncryptionTest {
@Test
public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException {
@ -43,4 +45,19 @@ public class EncryptionTest {
assertEquals(before, after);
}
@Test
public void ensureMessageCanBeDecrypted() throws IOException {
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
BitmessageAddress identity = new BitmessageAddress(privateKey);
assertEquals("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8", identity.getAddress());
ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload");
Msg msg = (Msg) object.getPayload();
msg.decrypt(privateKey.getPrivateEncryptionKey());
Plaintext plaintext = msg.getPlaintext();
assertNotNull(plaintext);
assertEquals("Test", plaintext.getSubject());
assertEquals("Hallo, das ist ein Test von der v4-Adresse", plaintext.getText());
}
}

View File

@ -18,15 +18,19 @@ package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
import java.io.IOException;
import java.util.Date;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
public class SignatureTest {
@Test
@ -39,7 +43,6 @@ public class SignatureTest {
@Test
public void ensureSigningWorks() throws IOException {
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
BitmessageAddress address = new BitmessageAddress(privateKey);
ObjectMessage objectMessage = new ObjectMessage.Builder()
.objectType(ObjectType.PUBKEY)
@ -51,4 +54,25 @@ public class SignatureTest {
assertTrue(objectMessage.isSignatureValid(privateKey.getPubkey()));
}
@Test
public void ensureMessageIsProperlySigned() throws IOException {
BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8");
ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload");
Msg msg = (Msg) object.getPayload();
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
Plaintext plaintext = msg.getPlaintext();
assertEquals(0, object.getExpiresTime());
assertEquals(loadPubkey(), plaintext.getFrom().getPubkey());
assertNotNull(plaintext);
assertTrue(object.isSignatureValid(plaintext.getFrom().getPubkey()));
}
private V4Pubkey loadPubkey() throws IOException {
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
object.decrypt(address.getPubkeyDecryptionKey());
return (V4Pubkey) object.getPayload();
}
}

View File

@ -17,7 +17,6 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.*;

View File

@ -75,6 +75,23 @@ public class SerializationTest {
doTest("V1MsgStrangeData.payload", 1, GenericPayload.class);
}
@Test
public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws IOException {
Plaintext p1 = new Plaintext.Builder()
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.to(TestUtils.loadContact())
.encoding(Plaintext.Encoding.SIMPLE)
.message("Subject", "Message")
.ack("ack".getBytes())
.signature(new byte[0])
.build();
ByteArrayOutputStream out = new ByteArrayOutputStream();
p1.write(out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
Plaintext p2 = Plaintext.read(in);
assertEquals(p1, p2);
}
private void doTest(String resourceName, int version, Class<?> expectedPayloadType) throws IOException {
byte[] data = TestUtils.getBytes(resourceName);
InputStream in = new ByteArrayInputStream(data);

View File

@ -25,9 +25,8 @@ import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyPairGenerator;
import java.util.Calendar;
import java.util.GregorianCalendar;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
import static org.junit.Assert.assertArrayEquals;
/**
@ -72,7 +71,8 @@ public class SecurityTest {
public void testProofOfWorkFails() throws IOException {
ObjectMessage objectMessage = new ObjectMessage.Builder()
.nonce(new byte[8])
.expiresTime(UnixTime.now() + 300) // 5 minutes
.expiresTime(UnixTime.now(+2 * DAY)) // 5 minutes
.objectType(0)
.payload(GenericPayload.read(new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
Security.checkProofOfWork(objectMessage, 1000, 1000);
@ -80,11 +80,10 @@ public class SecurityTest {
@Test
public void testDoProofOfWork() throws IOException {
Calendar expires = new GregorianCalendar();
expires.add(1, Calendar.HOUR);
ObjectMessage objectMessage = new ObjectMessage.Builder()
.nonce(new byte[8])
.expiresTime(expires.getTimeInMillis() / 1000)
.expiresTime(UnixTime.now(+2 * DAY))
.objectType(0)
.payload(GenericPayload.read(new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
Security.doProofOfWork(objectMessage, new MultiThreadedPOWEngine(), 1000, 1000);

View File

@ -16,7 +16,10 @@
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.factory.Factory;
import java.io.ByteArrayInputStream;
@ -24,6 +27,8 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.Assert.assertEquals;
/**
* If there's ever a need for this in production code, it should be rewritten to be more efficient.
*/
@ -51,4 +56,23 @@ public class TestUtils {
}
return out.toByteArray();
}
public static InputStream getResource(String resourceName) {
return TestUtils.class.getClassLoader().getResourceAsStream(resourceName);
}
public static BitmessageAddress loadIdentity(String address) throws IOException {
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey"));
BitmessageAddress identity = new BitmessageAddress(privateKey);
assertEquals(address, identity.getAddress());
return identity;
}
public static BitmessageAddress loadContact() throws IOException {
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
object.decrypt(address.getPubkeyDecryptionKey());
address.setPubkey((V4Pubkey) object.getPayload());
return address;
}
}

View File

@ -18,12 +18,16 @@ package ch.dissem.bitmessage.repository;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.AddressRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Arrays;
@ -62,7 +66,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
@Override
public List<BitmessageAddress> getIdentities() {
return find("private_signing_key IS NOT NULL");
return find("private_key IS NOT NULL");
}
@Override
@ -72,7 +76,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
@Override
public List<BitmessageAddress> getContacts() {
return find("private_signing_key IS NULL");
return find("private_key IS NULL");
}
private List<BitmessageAddress> find(String where) {
@ -91,7 +95,10 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
Blob publicKeyBlob = rs.getBlob("public_key");
if (publicKeyBlob != null) {
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(),
publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length());
publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length(), false);
if (address.getVersion() == 4) {
pubkey = new V4Pubkey((V3Pubkey) pubkey);
}
address.setPubkey(pubkey);
}
}
@ -111,7 +118,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
Statement stmt = getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Address WHERE address='" + address.getAddress() + "'");
rs.next();
return rs.getInt(0) > 0;
return rs.getInt(1) > 0;
} catch (SQLException e) {
LOG.error(e.getMessage(), e);
}
@ -136,21 +143,32 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
"UPDATE Address SET address=?, alias=?, public_key=?, private_key=?");
ps.setString(1, address.getAddress());
ps.setString(2, address.getAlias());
writeBlob(ps, 3, address.getPubkey());
writePubkey(ps, 3, address.getPubkey());
writeBlob(ps, 4, address.getPrivateKey());
ps.executeUpdate();
}
private void insert(BitmessageAddress address) throws IOException, SQLException {
PreparedStatement ps = getConnection().prepareStatement(
"INSERT INTO Address (address, alias, public_key, private_key) VALUES (?, ?, ?, ?, ?)");
"INSERT INTO Address (address, alias, public_key, private_key) VALUES (?, ?, ?, ?)");
ps.setString(1, address.getAddress());
ps.setString(2, address.getAlias());
writeBlob(ps, 3, address.getPubkey());
writePubkey(ps, 3, address.getPubkey());
writeBlob(ps, 4, address.getPrivateKey());
ps.executeUpdate();
}
protected void writePubkey(PreparedStatement ps, int parameterIndex, Pubkey data) throws SQLException, IOException {
if (data != null) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
data.writeUnencrypted(out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ps.setBlob(parameterIndex, in);
} else {
ps.setBlob(parameterIndex, (Blob) null);
}
}
@Override
public void remove(BitmessageAddress address) {
try {

View File

@ -24,10 +24,7 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.*;
/**
* Helper class that does Flyway migration, provides JDBC connections and some helper methods.
@ -47,10 +44,14 @@ abstract class JdbcHelper {
}
protected void writeBlob(PreparedStatement ps, int parameterIndex, Streamable data) throws SQLException, IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
data.write(os);
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
ps.setBlob(parameterIndex, is);
if (data != null) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
data.write(os);
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
ps.setBlob(parameterIndex, is);
} else {
ps.setBlob(parameterIndex, (Blob) null);
}
}
protected Connection getConnection() {

View File

@ -52,10 +52,23 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
}
return result;
}
private List<InventoryVector> getFullInventory(long... streams) {
List<InventoryVector> result = new LinkedList<>();
try {
Statement stmt = getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT hash FROM Inventory WHERE stream IN (" + join(streams) + ")");
while (rs.next()) {
result.add(new InventoryVector(rs.getBytes("hash")));
}
} catch (SQLException e) {
LOG.error(e.getMessage(), e);
}
return result;
}
@Override
public List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams) {
offer.removeAll(getInventory(streams));
offer.removeAll(getFullInventory(streams));
return offer;
}
@ -78,17 +91,17 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
}
@Override
public List<ObjectMessage> getObjects(long stream, long version, ObjectType type) {
public List<ObjectMessage> getObjects(long stream, long version, ObjectType... types) {
try {
StringBuilder query = new StringBuilder("SELECT data, version FROM Inventory WHERE 1=1");
if (stream >= 0) {
if (stream > 0) {
query.append(" AND stream = ").append(stream);
}
if (version >= 0) {
if (version > 0) {
query.append(" AND version = ").append(version);
}
if (type != null) {
query.append(" AND type = ").append(type.getNumber());
if (types.length > 0) {
query.append(" AND type IN (").append(join(types)).append(")");
}
Statement stmt = getConnection().createStatement();
ResultSet rs = stmt.executeQuery(query.toString());

View File

@ -41,7 +41,7 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
List<String> result = new LinkedList<>();
try {
Statement stmt = getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT label FROM Label ORDER BY order");
ResultSet rs = stmt.executeQuery("SELECT label FROM Label ORDER BY ord");
while (rs.next()) {
result.add(rs.getString("label"));
}
@ -58,7 +58,7 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
@Override
public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) {
return find("status='" + status.name() + "' AND to='" + recipient.getAddress() + "'");
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'");
}
@Override
@ -70,14 +70,14 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
List<Plaintext> result = new LinkedList<>();
try {
Statement stmt = getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT \"id\", \"from\", \"to\", \"data\", \"sent\", \"received\", \"status\" FROM Message WHERE " + where);
ResultSet rs = stmt.executeQuery("SELECT id, sender, recipient, data, sent, received, status FROM Message WHERE " + where);
while (rs.next()) {
Blob data = rs.getBlob("data");
Plaintext.Builder builder = Plaintext.readWithoutSignature(data.getBinaryStream());
long id = rs.getLong("id");
builder.id(id);
builder.from(ctx.getAddressRepo().getAddress(rs.getString("from")));
builder.to(ctx.getAddressRepo().getAddress(rs.getString("to")));
builder.from(ctx.getAddressRepo().getAddress(rs.getString("sender")));
builder.to(ctx.getAddressRepo().getAddress(rs.getString("recipient")));
builder.sent(rs.getLong("sent"));
builder.received(rs.getLong("received"));
builder.status(Plaintext.Status.valueOf(rs.getString("status")));
@ -94,7 +94,7 @@ 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 label, 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")));
}
@ -151,7 +151,7 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private void insert(Connection connection, Plaintext message) throws SQLException, IOException {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO Message (\"from\", \"to\", \"data\", \"sent\", \"received\", \"status\") VALUES (?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
"INSERT INTO Message (sender, recipient, data, sent, received, status) VALUES (?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
ps.setString(1, message.getFrom().getAddress());
ps.setString(2, message.getTo().getAddress());
writeBlob(ps, 3, message);
@ -168,7 +168,7 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito
private void update(Connection connection, Plaintext message) throws SQLException, IOException {
PreparedStatement ps = connection.prepareStatement(
"UPDATE Message SET \"sent\"=?, \"received\"=?, \"status\"=?");
"UPDATE Message SET sent=?, received=?, status=?");
ps.setLong(1, message.getSent());
ps.setLong(2, message.getReceived());
ps.setString(3, message.getStatus().name());

View File

@ -38,7 +38,7 @@ public class JdbcNodeRegistry extends JdbcHelper implements NodeRegistry {
List<NetworkAddress> result = new LinkedList<>();
try {
Statement stmt = getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM Node WHERE Stream IN (" + join(streams) + ")");
ResultSet rs = stmt.executeQuery("SELECT * FROM Node WHERE stream IN (" + join(streams) + ")");
while (rs.next()) {
// result.add(new NetworkAddress.Builder()
// .ipv6(rs.getBytes("ip"))

View File

@ -1,61 +0,0 @@
/*
* 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.repository;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.ports.Inventory;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.util.LinkedList;
import java.util.List;
/**
* Created by chris on 06.04.15.
*/
public class SimpleInventory implements Inventory {
@Override
public List<InventoryVector> getInventory(long... streams) {
return new LinkedList<>();
}
@Override
public List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams) {
return offer;
}
@Override
public ObjectMessage getObject(InventoryVector vector) {
throw new NotImplementedException();
}
@Override
public List<ObjectMessage> getObjects(long stream, long version, ObjectType type) {
return new LinkedList<>();
}
@Override
public void storeObject(ObjectMessage object) {
throw new NotImplementedException();
}
@Override
public void cleanup() {
throw new NotImplementedException();
}
}

View File

@ -1,37 +0,0 @@
/*
* 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.repository;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.ports.NodeRegistry;
import java.util.Collections;
import java.util.List;
/**
* Created by chris on 06.04.15.
*/
public class SimpleNodeRegistry implements NodeRegistry {
@Override
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
return Collections.singletonList(new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(8444).build());
}
@Override
public void offerAddresses(List<NetworkAddress> addresses) {
}
}

View File

@ -1,9 +1,9 @@
CREATE TABLE Node (
"ip" BINARY(16) NOT NULL,
"port" INT NOT NULL,
"stream" BIGINT NOT NULL,
"services" BIGINT NOT NULL,
"time" BIGINT NOT NULL,
ip BINARY(16) NOT NULL,
port INT NOT NULL,
stream BIGINT NOT NULL,
services BIGINT NOT NULL,
time BIGINT NOT NULL,
PRIMARY KEY ("ip", "port", "stream")
PRIMARY KEY (ip, port, stream)
);

View File

@ -1,8 +1,8 @@
CREATE TABLE Inventory (
"hash" BINARY(32) NOT NULL PRIMARY KEY,
"stream" BIGINT NOT NULL,
"expires" BIGINT NOT NULL,
"data" BLOB NOT NULL,
"type" BIGINT NOT NULL,
"version" BIGINT NOT NULL
hash BINARY(32) NOT NULL PRIMARY KEY,
stream BIGINT NOT NULL,
expires BIGINT NOT NULL,
data BLOB NOT NULL,
type BIGINT NOT NULL,
version BIGINT NOT NULL
);

View File

@ -1,7 +1,7 @@
CREATE TABLE Address (
"address" VARCHAR(40) NOT NULL PRIMARY KEY,
"alias" VARCHAR(255),
"public_key" BLOB,
"private_key" BLOB,
"subscribed" BIT DEFAULT '0'
address VARCHAR(40) NOT NULL PRIMARY KEY,
alias VARCHAR(255),
public_key BLOB,
private_key BLOB,
subscribed BIT DEFAULT '0'
);

View File

@ -1,32 +1,35 @@
CREATE TABLE Message (
"id" BIGINT AUTO_INCREMENT PRIMARY KEY,
"from" VARCHAR(40) NOT NULL,
"to" VARCHAR(40) NOT NULL,
"data" BLOB NOT NULL,
"sent" BIGINT,
"received" BIGINT,
"status" VARCHAR(20) NOT NULL
id BIGINT AUTO_INCREMENT PRIMARY KEY,
sender VARCHAR(40) NOT NULL,
recipient VARCHAR(40) NOT NULL,
data BLOB NOT NULL,
sent BIGINT,
received BIGINT,
status VARCHAR(20) NOT NULL,
FOREIGN KEY (sender) REFERENCES Address (address),
FOREIGN KEY (recipient) REFERENCES Address (address)
);
CREATE TABLE Label (
"id" BIGINT AUTO_INCREMENT PRIMARY KEY,
"label" VARCHAR(255) NOT NULL,
"color" INT,
"order" BIGINT,
CONSTRAINT UC_label UNIQUE ("label"),
CONSTRAINT UC_order UNIQUE ("order")
id BIGINT AUTO_INCREMENT PRIMARY KEY,
label VARCHAR(255) NOT NULL,
color INT,
ord BIGINT,
CONSTRAINT UC_label UNIQUE (label),
CONSTRAINT UC_order UNIQUE (ord)
);
CREATE TABLE Message_Label (
"message_id" BIGINT NOT NULL,
"label_id" BIGINT NOT NULL,
message_id BIGINT NOT NULL,
label_id BIGINT NOT NULL,
PRIMARY KEY ("message_id", "label_id"),
FOREIGN KEY ("message_id") REFERENCES Message ("id"),
FOREIGN KEY ("label_id") REFERENCES Label ("id")
PRIMARY KEY (message_id, label_id),
FOREIGN KEY (message_id) REFERENCES Message (id),
FOREIGN KEY (label_id) REFERENCES Label (id)
);
INSERT INTO Label("label", "order") VALUES ('Inbox', 0);
INSERT INTO Label("label", "order") VALUES ('Sent', 10);
INSERT INTO Label("label", "order") VALUES ('Drafts', 20);
INSERT INTO Label("label", "order") VALUES ('Trash', 100);
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);