diff --git a/demo/build.gradle b/demo/build.gradle index 7adbed2..4662642 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -10,7 +10,7 @@ repositories { dependencies { compile project(':domain') compile project(':networking') - compile project(':inventory') + compile project(':repositories') compile 'org.slf4j:slf4j-simple:1.7.12' testCompile 'junit:junit:4.11' } \ No newline at end of file diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index f34771f..4fc9e9d 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -18,13 +18,11 @@ package ch.dissem.bitmessage.demo; import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.*; -import ch.dissem.bitmessage.inventory.JdbcAddressRepository; -import ch.dissem.bitmessage.inventory.JdbcInventory; -import ch.dissem.bitmessage.inventory.JdbcNodeRegistry; +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.ports.NetworkHandler; import ch.dissem.bitmessage.utils.Base58; import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.Security; @@ -33,9 +31,6 @@ import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.Scanner; /** * Created by chris on 06.04.15. @@ -51,61 +46,56 @@ public class Main { .inventory(new JdbcInventory()) .nodeRegistry(new JdbcNodeRegistry()) .networkHandler(new NetworkNode()) + .messageRepo(new JdbcMessageRepository()) .port(48444) .streams(1) .build(); - ctx.getNetworkHandler().start(new NetworkHandler.MessageListener() { - @Override - public void receive(ObjectPayload payload) { -// LOG.info("message received: " + payload); -// System.out.print('.'); - if (payload instanceof V4Pubkey) { - V4Pubkey pubkey = (V4Pubkey) payload; - if (Arrays.equals(address.getTag(), pubkey.getTag())) { - System.out.println("Pubkey found!"); - try { - address.setPubkey(pubkey); - } catch (Exception ignore) { - System.err.println("Received pubkey we didn't request."); - } - } - } - } - }); +// 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 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(); -// 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.getNetworkHandler().stop(); - - - List 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(); - } - } - } - } +// LOG.info("Shutting down client"); +// ctx.shutdown(); +// +// +// List 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) { diff --git a/docs/seminar-blx.bib b/docs/seminar-blx.bib new file mode 100644 index 0000000..f1ead3e --- /dev/null +++ b/docs/seminar-blx.bib @@ -0,0 +1,11 @@ +@Comment{$ biblatex control file $} +@Comment{$ biblatex version 1.7 $} +Do not modify this file! + +This is an auxiliary file used by the 'biblatex' package. +This file may safely be deleted. It will be recreated as +required. + +@Control{biblatex-control, + options = {1.7:0:0:1:0:0:1:1:0:0:0:0:1:1:3:1:79:+}, +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index 0d45e22..c408b59 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -16,8 +16,16 @@ 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.Pubkey.Feature; +import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.ports.*; import ch.dissem.bitmessage.utils.Security; import ch.dissem.bitmessage.utils.UnixTime; @@ -26,115 +34,126 @@ 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.utils.UnixTime.DAY; + /** * Created by chris on 05.04.15. */ public class BitmessageContext { public static final int CURRENT_VERSION = 3; private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class); - private final Inventory inventory; - private final NodeRegistry nodeRegistry; - private final NetworkHandler networkHandler; - private final AddressRepository addressRepo; - private final ProofOfWorkEngine proofOfWorkEngine; - private final TreeSet streams; - - private final int port; - - private long networkNonceTrialsPerByte = 1000; - private long networkExtraBytes = 1000; + private final InternalContext ctx; private BitmessageContext(Builder builder) { - port = builder.port; - inventory = builder.inventory; - nodeRegistry = builder.nodeRegistry; - networkHandler = builder.networkHandler; - addressRepo = builder.addressRepo; - proofOfWorkEngine = builder.proofOfWorkEngine; - streams = builder.streams; - - init(inventory, nodeRegistry, networkHandler, addressRepo, proofOfWorkEngine); + ctx = new InternalContext(builder); } - private void init(Object... objects) { - for (Object o : objects) { - if (o instanceof ContextHolder) { - ((ContextHolder) o).setContext(this); - } + public List getIdentities() { + return ctx.getAddressRepo().getIdentities(); + } + + public BitmessageAddress createIdentity(boolean shorter, Feature... features) { + BitmessageAddress identity = new BitmessageAddress(new PrivateKey( + shorter, + ctx.getStreams()[0], + ctx.getNetworkNonceTrialsPerByte(), + ctx.getNetworkExtraBytes(), + features + )); + ctx.getAddressRepo().save(identity); + return identity; + } + + public void addDistributedMailingList(String address, String alias) { + // TODO + } + + public void send(BitmessageAddress from, BitmessageAddress to, String subject, String message) { + if (from.getPrivateKey() == null) { + throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); } - } - - public void send(long stream, long version, ObjectPayload payload, long timeToLive, long nonceTrialsPerByte, long extraBytes) throws IOException { - long expires = UnixTime.now(+timeToLive); - LOG.info("Expires at " + expires); - ObjectMessage object = new ObjectMessage.Builder() - .stream(stream) - .version(version) - .expiresTime(expires) - .payload(payload) + Plaintext msg = new Plaintext.Builder() + .from(from) + .to(to) + .encoding(Encoding.SIMPLE) + .message(subject, message) .build(); - Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes); - inventory.storeObject(object); - networkHandler.offer(object.getInventoryVector()); - } - - public Inventory getInventory() { - return inventory; - } - - public NodeRegistry getAddressRepository() { - return nodeRegistry; - } - - public NetworkHandler getNetworkHandler() { - return networkHandler; - } - - public int getPort() { - return port; - } - - public long[] getStreams() { - long[] result = new long[streams.size()]; - int i = 0; - for (long stream : streams) { - result[i++] = stream; + if (to.getPubkey() == null) { + requestPubkey(from, to); + msg.setStatus(PUBKEY_REQUESTED); + ctx.getMessageRepository().save(msg); + } else { + msg.setStatus(DOING_PROOF_OF_WORK); + ctx.getMessageRepository().save(msg); + ctx.send( + from, + to, + new Msg(msg), + +2 * DAY, + ctx.getNonceTrialsPerByte(to), + ctx.getExtraBytes(to) + ); + msg.setStatus(SENT); + ctx.getMessageRepository().save(msg); } - return result; } - public void addStream(long stream) { - streams.add(stream); + private void requestPubkey(BitmessageAddress requestingIdentity, BitmessageAddress address) { + ctx.send( + requestingIdentity, + address, + new GetPubkey(address), + +28 * DAY, + ctx.getNetworkNonceTrialsPerByte(), + ctx.getNetworkExtraBytes() + ); } - public void removeStream(long stream) { - streams.remove(stream); + private void send(long stream, long version, ObjectPayload payload, long timeToLive) { + try { + long expires = UnixTime.now(+timeToLive); + LOG.info("Expires at " + expires); + ObjectMessage object = new ObjectMessage.Builder() + .stream(stream) + .version(version) + .expiresTime(expires) + .payload(payload) + .build(); + Security.doProofOfWork(object, ctx.getProofOfWorkEngine(), + ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes()); + ctx.getInventory().storeObject(object); + ctx.getNetworkHandler().offer(object.getInventoryVector()); + } catch (IOException e) { + throw new RuntimeException(e); + } } - public long getNetworkNonceTrialsPerByte() { - return networkNonceTrialsPerByte; + public void startup(Listener listener) { + ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener)); } - public long getNetworkExtraBytes() { - return networkExtraBytes; + public void shutdown() { + ctx.getNetworkHandler().stop(); } - - public interface ContextHolder { - void setContext(BitmessageContext context); + public interface Listener { + void receive(Plaintext plaintext); } public static final class Builder { - private int port = 8444; - private Inventory inventory; - private NodeRegistry nodeRegistry; - private NetworkHandler networkHandler; - private AddressRepository addressRepo; - private ProofOfWorkEngine proofOfWorkEngine; - private TreeSet streams; + int port = 8444; + Inventory inventory; + NodeRegistry nodeRegistry; + NetworkHandler networkHandler; + AddressRepository addressRepo; + MessageRepository messageRepo; + ProofOfWorkEngine proofOfWorkEngine; + TreeSet streams; public Builder() { } @@ -164,6 +183,11 @@ public class BitmessageContext { return this; } + public Builder messageRepo(MessageRepository messageRepo) { + this.messageRepo = messageRepo; + return this; + } + public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) { this.proofOfWorkEngine = proofOfWorkEngine; return this; @@ -187,6 +211,7 @@ public class BitmessageContext { nonNull("nodeRegistry", nodeRegistry); nonNull("networkHandler", networkHandler); nonNull("addressRepo", addressRepo); + nonNull("messageRepo", messageRepo); if (streams == null) { streams(1); } @@ -200,4 +225,5 @@ public class BitmessageContext { if (o == null) throw new IllegalStateException(name + " must not be null"); } } + } diff --git a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java new file mode 100644 index 0000000..dad500b --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java @@ -0,0 +1,160 @@ +/* + * 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.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; + +import java.io.IOException; +import java.util.List; + +import static ch.dissem.bitmessage.entity.Plaintext.Status.DOING_PROOF_OF_WORK; +import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT; +import static ch.dissem.bitmessage.utils.UnixTime.DAY; + +class DefaultMessageListener implements NetworkHandler.MessageListener { + private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class); + private final InternalContext ctx; + private final BitmessageContext.Listener listener; + + public DefaultMessageListener(InternalContext context, BitmessageContext.Listener listener) { + this.ctx = context; + this.listener = listener; + } + + @Override + public void receive(ObjectMessage object) { + ObjectPayload payload = object.getPayload(); + switch (payload.getType()) { + case GET_PUBKEY: { + receive(object, (GetPubkey) payload); + break; + } + case PUBKEY: { + receive(object, (Pubkey) payload); + break; + } + case MSG: { + receive(object, (Msg) payload); + break; + } + case BROADCAST: { + receive(object, (Broadcast) payload); + break; + } + } + } + + 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); + } + } + } + + protected void receive(ObjectMessage object, Pubkey pubkey) { + BitmessageAddress address; + try { + if (pubkey instanceof V4Pubkey) { + V4Pubkey v4Pubkey = (V4Pubkey) pubkey; + address = ctx.getAddressRepo().findContact(v4Pubkey.getTag()); + if (address != null) { + v4Pubkey.decrypt(address.getPubkeyDecryptionKey()); + } + } else { + address = ctx.getAddressRepo().findContact(pubkey.getRipe()); + } + if (address != null) { + address.setPubkey(pubkey); + List messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address); + for (Plaintext msg:messages){ + // TODO: send messages enqueued for this address + msg.setStatus(DOING_PROOF_OF_WORK); + ctx.getMessageRepository().save(msg); + ctx.send( + msg.getFrom(), + msg.getTo(), + new Msg(msg), + +2 * DAY, + ctx.getNonceTrialsPerByte(msg.getTo()), + ctx.getExtraBytes(msg.getTo()) + ); + msg.setStatus(SENT); + ctx.getMessageRepository().save(msg); + } + } + } catch (IllegalArgumentException | IOException e) { + LOG.debug(e.getMessage(), e); + } + } + + protected void receive(ObjectMessage object, Msg msg) { + for (BitmessageAddress identity : ctx.getAddressRepo().getIdentities()) { + try { + msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); + msg.getPlaintext().setTo(identity); + object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey()); + ctx.getMessageRepository().save(msg.getPlaintext()); + listener.receive(msg.getPlaintext()); + break; + } catch (IOException ignore) { + } + } + } + + protected void receive(ObjectMessage object, Broadcast broadcast) { + // TODO this should work fine as-is, but checking the tag might be more efficient +// V5Broadcast v5 = broadcast instanceof V5Broadcast ? (V5Broadcast) broadcast : null; + for (BitmessageAddress subscription : ctx.getAddressRepo().getSubscriptions()) { + try { + broadcast.decrypt(subscription.getPubkeyDecryptionKey()); + object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey()); + listener.receive(broadcast.getPlaintext()); + } catch (IOException ignore) { + } + } + } +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java new file mode 100644 index 0000000..4d51423 --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java @@ -0,0 +1,166 @@ +/* + * 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.Encrypted; +import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.payload.ObjectPayload; +import ch.dissem.bitmessage.ports.*; +import ch.dissem.bitmessage.utils.Security; +import ch.dissem.bitmessage.utils.UnixTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.TreeSet; + +/** + * 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 + * get extended. + * <p/> + * On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply. + */ +public class InternalContext { + private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class); + + private final Inventory inventory; + private final NodeRegistry nodeRegistry; + private final NetworkHandler networkHandler; + private final AddressRepository addressRepository; + private final MessageRepository messageRepository; + private final ProofOfWorkEngine proofOfWorkEngine; + + private final TreeSet<Long> streams; + private final int port; + private long networkNonceTrialsPerByte = 1000; + private long networkExtraBytes = 1000; + + public InternalContext(BitmessageContext.Builder builder) { + this.inventory = builder.inventory; + this.nodeRegistry = builder.nodeRegistry; + this.networkHandler = builder.networkHandler; + this.addressRepository = builder.addressRepo; + this.messageRepository = builder.messageRepo; + this.proofOfWorkEngine = builder.proofOfWorkEngine; + + port = builder.port; + streams = builder.streams; + + init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine); + } + + private void init(Object... objects) { + for (Object o : objects) { + if (o instanceof ContextHolder) { + ((ContextHolder) o).setContext(this); + } + } + } + + public Inventory getInventory() { + return inventory; + } + + public NodeRegistry getNodeRegistry() { + return nodeRegistry; + } + + public NetworkHandler getNetworkHandler() { + return networkHandler; + } + + public AddressRepository getAddressRepo() { + return addressRepository; + } + + public MessageRepository getMessageRepository() { + return messageRepository; + } + + public ProofOfWorkEngine getProofOfWorkEngine() { + return proofOfWorkEngine; + } + + public long[] getStreams() { + long[] result = new long[streams.size()]; + int i = 0; + for (long stream : streams) { + result[i++] = stream; + } + return result; + } + + public void addStream(long stream) { + streams.add(stream); + } + + public void removeStream(long stream) { + streams.remove(stream); + } + + public int getPort() { + return port; + } + + public long getNetworkNonceTrialsPerByte() { + return networkNonceTrialsPerByte; + } + + public long getNonceTrialsPerByte(BitmessageAddress address) { + long nonceTrialsPerByte = address.getPubkey().getNonceTrialsPerByte(); + return networkNonceTrialsPerByte > nonceTrialsPerByte ? networkNonceTrialsPerByte : nonceTrialsPerByte; + } + + public long getNetworkExtraBytes() { + return networkExtraBytes; + } + + public long getExtraBytes(BitmessageAddress address) { + long extraBytes = address.getPubkey().getExtraBytes(); + return networkExtraBytes > extraBytes ? networkExtraBytes : extraBytes; + } + + public void send(BitmessageAddress from, BitmessageAddress to, ObjectPayload payload, long timeToLive, long nonceTrialsPerByte, long extraBytes) { + try { + long expires = UnixTime.now(+timeToLive); + LOG.info("Expires at " + expires); + ObjectMessage object = new ObjectMessage.Builder() + .stream(to.getStream()) + .version(to.getVersion()) + .expiresTime(expires) + .payload(payload) + .build(); + Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes); + if (object.isSigned()) { + object.sign(from.getPrivateKey()); + } + if (object instanceof Encrypted) { + object.encrypt(to.getPubkey()); + } + inventory.storeObject(object); + networkHandler.offer(object.getInventoryVector()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public interface ContextHolder { + void setContext(InternalContext context); + } +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java index 7538926..83303b7 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java @@ -49,8 +49,9 @@ public class BitmessageAddress { private Pubkey pubkey; private String alias; + private boolean subscribed; - private BitmessageAddress(long version, long stream, byte[] ripe) { + BitmessageAddress(long version, long stream, byte[] ripe) { try { this.version = version; this.stream = stream; @@ -74,7 +75,7 @@ public class BitmessageAddress { } } - private BitmessageAddress(Pubkey publicKey) { + BitmessageAddress(Pubkey publicKey) { this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe()); this.pubkey = publicKey; } @@ -163,6 +164,10 @@ public class BitmessageAddress { this.alias = alias; } + public void setSubscribed(boolean subscribed) { + this.subscribed = subscribed; + } + @Override public String toString() { return alias != null ? alias : address; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java index a861bce..1e375a3 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java @@ -19,7 +19,7 @@ package ch.dissem.bitmessage.entity; import java.io.IOException; /** - * Created by chris on 12.05.15. + * Used for objects that have encrypted content */ public interface Encrypted { void encrypt(byte[] publicKey) throws IOException; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java index 88c35bc..73ac107 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java @@ -128,15 +128,19 @@ public class ObjectMessage implements MessagePayload { } } - public void encrypt(byte[] publicEncryptionKey) throws IOException{ - if (payload instanceof Encrypted){ + public void encrypt(byte[] publicEncryptionKey) throws IOException { + if (payload instanceof Encrypted) { ((Encrypted) payload).encrypt(publicEncryptionKey); } } - public void encrypt(Pubkey publicKey) throws IOException{ - if (payload instanceof Encrypted){ - ((Encrypted) payload).encrypt(publicKey.getEncryptionKey()); + public void encrypt(Pubkey publicKey) { + try { + if (payload instanceof Encrypted) { + ((Encrypted) payload).encrypt(publicKey.getEncryptionKey()); + } + } catch (IOException e) { + throw new RuntimeException(e); } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java new file mode 100644 index 0000000..b8b8f52 --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java @@ -0,0 +1,352 @@ +/* + * 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; + +import ch.dissem.bitmessage.entity.payload.Pubkey; +import ch.dissem.bitmessage.entity.valueobject.Label; +import ch.dissem.bitmessage.factory.Factory; +import ch.dissem.bitmessage.utils.Decode; +import ch.dissem.bitmessage.utils.Encode; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.*; + +/** + * The unencrypted message to be sent by 'msg' or 'broadcast'. + */ +public class Plaintext implements Streamable { + private final BitmessageAddress from; + private final long encoding; + private final byte[] message; + private final byte[] ack; + private Object id; + private BitmessageAddress to; + private byte[] signature; + private Status status; + private Long sent; + private Long received; + + private Set<Label> labels; + + private Plaintext(Builder builder) { + id = builder.id; + from = builder.from; + to = builder.to; + encoding = builder.encoding; + message = builder.message; + ack = builder.ack; + signature = builder.signature; + status = builder.status; + sent = builder.sent; + received = builder.received; + labels = builder.labels; + } + + public static Plaintext read(InputStream in) throws IOException { + return readWithoutSignature(in) + .signature(Decode.varBytes(in)) + .build(); + } + + public static Plaintext.Builder readWithoutSignature(InputStream in) throws IOException { + return new Builder() + .addressVersion(Decode.varInt(in)) + .stream(Decode.varInt(in)) + .behaviorBitfield(Decode.int32(in)) + .publicSigningKey(Decode.bytes(in, 64)) + .publicEncryptionKey(Decode.bytes(in, 64)) + .nonceTrialsPerByte(Decode.varInt(in)) + .extraBytes(Decode.varInt(in)) + .destinationRipe(Decode.bytes(in, 20)) + .encoding(Decode.varInt(in)) + .message(Decode.varBytes(in)) + .ack(Decode.varBytes(in)); + } + + public byte[] getMessage() { + return message; + } + + public BitmessageAddress getFrom() { + return from; + } + + public BitmessageAddress getTo() { + return to; + } + + public void setTo(BitmessageAddress to) { + if (this.to.getVersion() != 0) + throw new RuntimeException("Correct address already set"); + if (Arrays.equals(this.to.getRipe(), to.getRipe())) { + throw new RuntimeException("RIPEs don't match"); + } + this.to = to; + } + + public Set<Label> getLabels() { + return labels; + } + + public long getStream() { + return from.getStream(); + } + + public byte[] getSignature() { + return signature; + } + + public void setSignature(byte[] signature) { + this.signature = signature; + } + + public void write(OutputStream out, boolean includeSignature) throws IOException { + 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()); + Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out); + Encode.varInt(from.getPubkey().getExtraBytes(), out); + out.write(to.getRipe()); + Encode.varInt(encoding, out); + Encode.varInt(message.length, out); + out.write(message); + Encode.varInt(ack.length, out); + out.write(ack); + if (includeSignature) { + Encode.varInt(signature.length, out); + out.write(signature); + } + } + + @Override + public void write(OutputStream out) throws IOException { + write(out, true); + } + + public Object getId() { + return id; + } + + public void setId(long id) { + if (this.id != null) throw new IllegalStateException("ID already set"); + this.id = id; + } + + public Long getSent() { + return sent; + } + + public Long getReceived() { + return received; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public enum Encoding { + IGNORE(0), TRIVIAL(1), SIMPLE(2); + + long code; + + Encoding(long code) { + 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 { + PUBKEY_REQUESTED, + DOING_PROOF_OF_WORK, + SENT, + ACKNOWLEDGED + } + + public static final class Builder { + private Object id; + private BitmessageAddress from; + private BitmessageAddress to; + private long addressVersion; + private long stream; + private int behaviorBitfield; + private byte[] publicSigningKey; + private byte[] publicEncryptionKey; + private long nonceTrialsPerByte; + private long extraBytes; + private byte[] destinationRipe; + private long encoding; + private byte[] message = new byte[0]; + private byte[] ack = new byte[0]; + private byte[] signature; + private long sent; + private long received; + private Status status; + private Set<Label> labels = new TreeSet<>(); + + public Builder() { + } + + public Builder id(Object id) { + this.id = id; + return this; + } + + public Builder from(BitmessageAddress address) { + from = address; + return this; + } + + public Builder to(BitmessageAddress address) { + to = address; + return this; + } + + private Builder addressVersion(long addressVersion) { + this.addressVersion = addressVersion; + return this; + } + + private Builder stream(long stream) { + this.stream = stream; + return this; + } + + private Builder behaviorBitfield(int behaviorBitfield) { + this.behaviorBitfield = behaviorBitfield; + return this; + } + + private Builder publicSigningKey(byte[] publicSigningKey) { + this.publicSigningKey = publicSigningKey; + return this; + } + + private Builder publicEncryptionKey(byte[] publicEncryptionKey) { + this.publicEncryptionKey = publicEncryptionKey; + return this; + } + + private Builder nonceTrialsPerByte(long nonceTrialsPerByte) { + this.nonceTrialsPerByte = nonceTrialsPerByte; + return this; + } + + private Builder extraBytes(long extraBytes) { + this.extraBytes = extraBytes; + return this; + } + + private Builder destinationRipe(byte[] ripe) { + this.destinationRipe = ripe; + return this; + } + + public Builder encoding(Encoding encoding) { + this.encoding = encoding.getCode(); + return this; + } + + private Builder encoding(long encoding) { + this.encoding = encoding; + return this; + } + + public Builder message(String subject, String message) { + try { + this.message = ("Subject:" + subject + '\n' + "Body:" + message).getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + return this; + } + + public Builder message(byte[] message) { + this.message = message; + return this; + } + + public Builder ack(byte[] ack) { + this.ack = ack; + return this; + } + + public Builder signature(byte[] signature) { + this.signature = signature; + return this; + } + + public Builder sent(long sent) { + this.sent = sent; + return this; + } + + public Builder received(long received) { + this.received = received; + return this; + } + + public Builder status(Status status) { + this.status = status; + return this; + } + + public Builder labels(Collection<Label> labels) { + this.labels.addAll(labels); + return this; + } + + public Plaintext build() { + if (id == null) { + id = UUID.randomUUID(); + } + if (from == null) { + from = new BitmessageAddress(Factory.createPubkey( + addressVersion, + stream, + publicSigningKey, + publicEncryptionKey, + nonceTrialsPerByte, + extraBytes, + Pubkey.Feature.features(behaviorBitfield) + )); + } + if (to == null) { + to = new BitmessageAddress(0, 0, destinationRipe); + } + return new Plaintext(this); + } + } +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java index 1630caf..0551ba5 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java @@ -16,10 +16,47 @@ package ch.dissem.bitmessage.entity.payload; +import ch.dissem.bitmessage.entity.Encrypted; +import ch.dissem.bitmessage.entity.Plaintext; + +import java.io.IOException; + /** * Users who are subscribed to the sending address will see the message appear in their inbox. * Broadcasts are version 4 or 5. */ -public abstract class Broadcast extends ObjectPayload { - public abstract byte[] getEncrypted(); +public abstract class Broadcast extends ObjectPayload implements Encrypted { + protected final long stream; + protected CryptoBox encrypted; + protected Plaintext plaintext; + + protected Broadcast(long stream, CryptoBox encrypted, Plaintext plaintext) { + this.stream = stream; + this.encrypted = encrypted; + this.plaintext = plaintext; + } + + @Override + public long getStream() { + return stream; + } + + public Plaintext getPlaintext() { + return plaintext; + } + + @Override + public void encrypt(byte[] publicKey) throws IOException { + this.encrypted = new CryptoBox(plaintext, publicKey); + } + + @Override + public void decrypt(byte[] privateKey) throws IOException { + plaintext = Plaintext.read(encrypted.decrypt(privateKey)); + } + + @Override + public boolean isDecrypted() { + return plaintext != null; + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java index b044bce..a6900c7 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java @@ -17,9 +17,7 @@ package ch.dissem.bitmessage.entity.payload; import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.utils.Bytes; import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Security; import java.io.IOException; import java.io.InputStream; @@ -30,30 +28,33 @@ import java.io.OutputStream; */ public class GetPubkey extends ObjectPayload { private long stream; - private byte[] ripe; - private byte[] tag; + private byte[] ripeTag; public GetPubkey(BitmessageAddress address) { this.stream = address.getStream(); if (address.getVersion() < 4) - this.ripe = address.getRipe(); + this.ripeTag = address.getRipe(); else - this.tag = address.getTag(); + this.ripeTag = address.getTag(); } private GetPubkey(long stream, long version, byte[] ripeOrTag) { this.stream = stream; - if (version < 4) { - ripe = ripeOrTag; - } else { - tag = ripeOrTag; - } + this.ripeTag = ripeOrTag; } public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException { return new GetPubkey(stream, version, Decode.bytes(is, length)); } + /** + * Returns an array of bytes that represent either the ripe, or the tag of an address, depending on the + * address version. + */ + public byte[] getRipeTag() { + return ripeTag; + } + @Override public ObjectType getType() { return ObjectType.GET_PUBKEY; @@ -66,10 +67,6 @@ public class GetPubkey extends ObjectPayload { @Override public void write(OutputStream stream) throws IOException { - if (tag != null) { - stream.write(tag); - } else { - stream.write(ripe); - } + stream.write(ripeTag); } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java index 413d99b..b94cb2b 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java @@ -16,6 +16,9 @@ package ch.dissem.bitmessage.entity.payload; +import ch.dissem.bitmessage.entity.Encrypted; +import ch.dissem.bitmessage.entity.Plaintext; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -23,26 +26,29 @@ import java.io.OutputStream; /** * Used for person-to-person messages. */ -public class Msg extends ObjectPayload { +public class Msg extends ObjectPayload implements Encrypted { private long stream; private CryptoBox encrypted; - private UnencryptedMessage decrypted; + private Plaintext plaintext; private Msg(long stream, CryptoBox encrypted) { this.stream = stream; this.encrypted = encrypted; } - public Msg(UnencryptedMessage unencrypted, Pubkey publicKey) { - this.stream = unencrypted.getStream(); - this.decrypted = unencrypted; - this.encrypted = new CryptoBox(unencrypted, publicKey.getEncryptionKey()); + public Msg(Plaintext plaintext) { + this.stream = plaintext.getStream(); + this.plaintext = plaintext; } public static Msg read(InputStream in, long stream, int length) throws IOException { return new Msg(stream, CryptoBox.read(in, length)); } + public Plaintext getPlaintext() { + return plaintext; + } + @Override public ObjectType getType() { return ObjectType.MSG; @@ -55,26 +61,37 @@ public class Msg extends ObjectPayload { @Override public boolean isSigned() { - return decrypted != null; + return true; } @Override public void writeBytesToSign(OutputStream out) throws IOException { - decrypted.write(out, false); + plaintext.write(out, false); } @Override public byte[] getSignature() { - return decrypted.getSignature(); + return plaintext.getSignature(); } @Override public void setSignature(byte[] signature) { - decrypted.setSignature(signature); + plaintext.setSignature(signature); } + @Override + public void encrypt(byte[] publicKey) throws IOException { + this.encrypted = new CryptoBox(plaintext, publicKey); + } + + @Override public void decrypt(byte[] privateKey) throws IOException { - decrypted = UnencryptedMessage.read(encrypted.decrypt(privateKey)); + plaintext = Plaintext.read(encrypted.decrypt(privateKey)); + } + + @Override + public boolean isDecrypted() { + return plaintext != null; } @Override diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java index 7225acb..0e1736c 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java @@ -37,10 +37,20 @@ public abstract class Pubkey extends ObjectPayload { public abstract byte[] getEncryptionKey(); + public abstract int getBehaviorBitfield(); + public byte[] getRipe() { return ripemd160(sha512(getSigningKey(), getEncryptionKey())); } + public long getNonceTrialsPerByte() { + return 0; + } + + public long getExtraBytes() { + return 0; + } + protected byte[] add0x04(byte[] key) { if (key.length == 65) return key; byte[] result = new byte[65]; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/UnencryptedMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/UnencryptedMessage.java deleted file mode 100644 index ea13dd8..0000000 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/UnencryptedMessage.java +++ /dev/null @@ -1,173 +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.entity.payload; - -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * The unencrypted message to be sent by 'msg' or 'broadcast'. - */ -public class UnencryptedMessage implements Streamable { - private final long addressVersion; - private final long stream; - private final int behaviorBitfield; - private final byte[] publicSigningKey; - private final byte[] publicEncryptionKey; - private final long nonceTrialsPerByte; - private final long extraBytes; - private final long encoding; - private final byte[] message; - private byte[] signature; - - private UnencryptedMessage(Builder builder) { - addressVersion = builder.addressVersion; - stream = builder.stream; - behaviorBitfield = builder.behaviorBitfield; - publicSigningKey = builder.publicSigningKey; - publicEncryptionKey = builder.publicEncryptionKey; - nonceTrialsPerByte = builder.nonceTrialsPerByte; - extraBytes = builder.extraBytes; - encoding = builder.encoding; - message = builder.message; - signature = builder.signature; - } - - public static UnencryptedMessage read(InputStream is) throws IOException { - return new Builder() - .addressVersion(Decode.varInt(is)) - .stream(Decode.varInt(is)) - .behaviorBitfield(Decode.int32(is)) - .publicSigningKey(Decode.bytes(is, 64)) - .publicEncryptionKey(Decode.bytes(is, 64)) - .nonceTrialsPerByte(Decode.varInt(is)) - .extraBytes(Decode.varInt(is)) - .encoding(Decode.varInt(is)) - .message(Decode.varBytes(is)) - .signature(Decode.varBytes(is)) - .build(); - } - - public long getStream() { - return stream; - } - - public byte[] getSignature() { - return signature; - } - - public void setSignature(byte[] signature) { - this.signature = signature; - } - - public void write(OutputStream out, boolean includeSignature) throws IOException { - Encode.varInt(addressVersion, out); - Encode.varInt(stream, out); - Encode.int32(behaviorBitfield, out); - out.write(publicSigningKey); - out.write(publicEncryptionKey); - Encode.varInt(nonceTrialsPerByte, out); - Encode.varInt(extraBytes, out); - Encode.varInt(encoding, out); - Encode.varInt(message.length, out); - out.write(message); - if (includeSignature) { - Encode.varInt(signature.length, out); - out.write(signature); - } - } - - @Override - public void write(OutputStream out) throws IOException { - write(out, true); - } - - public static final class Builder { - private long addressVersion; - private long stream; - private int behaviorBitfield; - private byte[] publicSigningKey; - private byte[] publicEncryptionKey; - private long nonceTrialsPerByte; - private long extraBytes; - private long encoding; - private byte[] message; - private byte[] signature; - - public Builder() { - } - - public Builder addressVersion(long addressVersion) { - this.addressVersion = addressVersion; - return this; - } - - public Builder stream(long stream) { - this.stream = stream; - return this; - } - - public Builder behaviorBitfield(int behaviorBitfield) { - this.behaviorBitfield = behaviorBitfield; - return this; - } - - public Builder publicSigningKey(byte[] publicSigningKey) { - this.publicSigningKey = publicSigningKey; - return this; - } - - public Builder publicEncryptionKey(byte[] publicEncryptionKey) { - this.publicEncryptionKey = publicEncryptionKey; - return this; - } - - public Builder nonceTrialsPerByte(long nonceTrialsPerByte) { - this.nonceTrialsPerByte = nonceTrialsPerByte; - return this; - } - - public Builder extraBytes(long extraBytes) { - this.extraBytes = extraBytes; - return this; - } - - public Builder encoding(long encoding) { - this.encoding = encoding; - return this; - } - - public Builder message(byte[] message) { - this.message = message; - return this; - } - - public Builder signature(byte[] signature) { - this.signature = signature; - return this; - } - - public UnencryptedMessage build() { - return new UnencryptedMessage(this); - } - } -} diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java index f99c04d..ef144a1 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java @@ -76,6 +76,11 @@ public class V2Pubkey extends Pubkey { return publicEncryptionKey; } + @Override + public int getBehaviorBitfield() { + return behaviorBitfield; + } + @Override public void write(OutputStream os) throws IOException { Encode.int32(behaviorBitfield, os); diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java index 10f320f..1a12ed6 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java @@ -18,7 +18,6 @@ package ch.dissem.bitmessage.entity.payload; import ch.dissem.bitmessage.utils.Decode; import ch.dissem.bitmessage.utils.Encode; -import ch.dissem.bitmessage.utils.Security; import java.io.IOException; import java.io.InputStream; @@ -66,6 +65,14 @@ public class V3Pubkey extends V2Pubkey { return 3; } + public long getNonceTrialsPerByte() { + return nonceTrialsPerByte; + } + + public long getExtraBytes() { + return extraBytes; + } + public boolean isSigned() { return true; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java index e529b38..ea4021d 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java @@ -16,8 +16,6 @@ package ch.dissem.bitmessage.entity.payload; -import ch.dissem.bitmessage.utils.Decode; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -27,17 +25,12 @@ import java.io.OutputStream; * Broadcasts are version 4 or 5. */ public class V4Broadcast extends Broadcast { - private long stream; - private byte[] encrypted; - private UnencryptedMessage unencrypted; - - protected V4Broadcast(long stream, byte[] encrypted) { - this.stream = stream; - this.encrypted = encrypted; + protected V4Broadcast(long stream, CryptoBox encrypted) { + super(stream, encrypted, null); } - public static V4Broadcast read(InputStream is, long stream, int length) throws IOException { - return new V4Broadcast(stream, Decode.bytes(is, length)); + public static V4Broadcast read(InputStream in, long stream, int length) throws IOException { + return new V4Broadcast(stream, CryptoBox.read(in, length)); } @Override @@ -45,32 +38,23 @@ public class V4Broadcast extends Broadcast { return ObjectType.BROADCAST; } - @Override - public long getStream() { - return stream; - } - - public byte[] getEncrypted() { - return encrypted; - } - @Override public void writeBytesToSign(OutputStream out) throws IOException { - unencrypted.write(out, false); + plaintext.write(out, false); } @Override public byte[] getSignature() { - return unencrypted.getSignature(); + return plaintext.getSignature(); } @Override public void setSignature(byte[] signature) { - unencrypted.setSignature(signature); + plaintext.setSignature(signature); } @Override - public void write(OutputStream stream) throws IOException { - stream.write(getEncrypted()); + public void write(OutputStream out) throws IOException { + encrypted.write(out); } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java index cf4a230..e3ba31b 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java @@ -111,6 +111,11 @@ public class V4Pubkey extends Pubkey implements Encrypted { return decrypted.getEncryptionKey(); } + @Override + public int getBehaviorBitfield() { + return decrypted.getBehaviorBitfield(); + } + @Override public byte[] getSignature() { if (decrypted != null) @@ -128,4 +133,12 @@ public class V4Pubkey extends Pubkey implements Encrypted { public boolean isSigned() { return true; } + + public long getNonceTrialsPerByte() { + return decrypted.getNonceTrialsPerByte(); + } + + public long getExtraBytes() { + return decrypted.getExtraBytes(); + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java index ed78309..7910e1e 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java @@ -28,13 +28,13 @@ import java.io.OutputStream; public class V5Broadcast extends V4Broadcast { private byte[] tag; - private V5Broadcast(long stream, byte[] tag, byte[] encrypted) { + private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) { super(stream, encrypted); this.tag = tag; } public static V5Broadcast read(InputStream is, long stream, int length) throws IOException { - return new V5Broadcast(stream, Decode.bytes(is, 32), Decode.bytes(is, length - 32)); + return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32)); } public byte[] getTag() { @@ -42,8 +42,8 @@ public class V5Broadcast extends V4Broadcast { } @Override - public void write(OutputStream stream) throws IOException { - stream.write(tag); - super.write(stream); + public void write(OutputStream out) throws IOException { + out.write(tag); + super.write(out); } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java new file mode 100644 index 0000000..2a4f637 --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java @@ -0,0 +1,48 @@ +/* + * 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.valueobject; + +public class Label { + private Object id; + private String label; + private int color; + + public Label(String label, int color) { + this.label = label; + this.color = color; + } + + /** + * RGBA representation for the color. + */ + public int getColor() { + return color; + } + + public void setColor(int color) { + this.color = color; + } + + @Override + public String toString() { + return label; + } + + public Object getId() { + return id; + } +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java b/domain/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java index 90edb4b..cb01028 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java @@ -20,21 +20,25 @@ import ch.dissem.bitmessage.entity.BitmessageAddress; import java.util.List; -/** - * Created by chris on 23.04.15. - */ public interface AddressRepository { + BitmessageAddress findContact(byte[] ripeOrTag); + + BitmessageAddress findIdentity(byte[] ripeOrTag); + /** * Returns all Bitmessage addresses that belong to this user, i.e. have a private key. */ - List<BitmessageAddress> findIdentities(); + List<BitmessageAddress> getIdentities(); + List<BitmessageAddress> getSubscriptions(); /** * Returns all Bitmessage addresses that have no private key. */ - List<BitmessageAddress> findContacts(); + List<BitmessageAddress> getContacts(); void save(BitmessageAddress address); void remove(BitmessageAddress address); + + BitmessageAddress getAddress(String address); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java new file mode 100644 index 0000000..c1a3cdf --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java @@ -0,0 +1,38 @@ +/* + * 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.ports; + +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.Plaintext.Status; +import ch.dissem.bitmessage.entity.valueobject.Label; + +import java.util.List; + +public interface MessageRepository { + List<String> getLabels(); + + List<Plaintext> findMessages(Label label); + + List<Plaintext> findMessages(Status status); + + List<Plaintext> findMessages(Status status, BitmessageAddress recipient); + + void save(Plaintext message); + + void remove(Plaintext message); +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java b/domain/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java index 98bdceb..9814c3a 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java @@ -28,7 +28,7 @@ import java.util.List; import static ch.dissem.bitmessage.utils.Bytes.inc; /** - * Created by chris on 14.04.15. + * A POW engine using all available CPU cores. */ public class MultiThreadedPOWEngine implements ProofOfWorkEngine { private static Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class); diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java index 620b57d..5266755 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.ports; import ch.dissem.bitmessage.BitmessageContext; +import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.payload.ObjectPayload; import ch.dissem.bitmessage.entity.valueobject.InventoryVector; @@ -31,6 +32,6 @@ public interface NetworkHandler { void offer(InventoryVector iv); interface MessageListener { - void receive(ObjectPayload payload); + void receive(ObjectMessage object); } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java index d32f899..f5413f4 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java @@ -89,16 +89,20 @@ public class Security { return result; } - public static void doProofOfWork(ObjectMessage object, ProofOfWorkEngine worker, long nonceTrialsPerByte, long extraBytes) throws IOException { - if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000; - if (extraBytes < 1000) extraBytes = 1000; + public static void doProofOfWork(ObjectMessage object, ProofOfWorkEngine worker, long nonceTrialsPerByte, long extraBytes) { + try { + if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000; + if (extraBytes < 1000) extraBytes = 1000; - byte[] initialHash = getInitialHash(object); + byte[] initialHash = getInitialHash(object); - byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); + byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); - byte[] nonce = worker.calculateNonce(initialHash, target); - object.setNonce(nonce); + byte[] nonce = worker.calculateNonce(initialHash, target); + object.setNonce(nonce); + } catch (IOException e) { + throw new RuntimeException(e); + } } /** diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java b/domain/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java index da5d585..d57c637 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java @@ -20,6 +20,8 @@ package ch.dissem.bitmessage.utils; * Created by chris on 18.04.15. */ public class UnixTime { + public static final long DAY = 60 * 60 * 24; + /** * Returns the time in second based Unix time ({@link System#currentTimeMillis()}/1000) */ diff --git a/inventory/src/main/resources/db/migration/V1.0__Create_node_table.sql b/inventory/src/main/resources/db/migration/V1.0__Create_node_table.sql deleted file mode 100644 index 2472634..0000000 --- a/inventory/src/main/resources/db/migration/V1.0__Create_node_table.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE Node ( - 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) -); \ No newline at end of file diff --git a/inventory/src/main/resources/db/migration/V1.1__Create_inventory_table.sql b/inventory/src/main/resources/db/migration/V1.1__Create_inventory_table.sql deleted file mode 100644 index 83653bf..0000000 --- a/inventory/src/main/resources/db/migration/V1.1__Create_inventory_table.sql +++ /dev/null @@ -1,8 +0,0 @@ -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 -); \ No newline at end of file diff --git a/inventory/src/main/resources/db/migration/V1.2__Create_address_table.sql b/inventory/src/main/resources/db/migration/V1.2__Create_address_table.sql deleted file mode 100644 index bd1cb70..0000000 --- a/inventory/src/main/resources/db/migration/V1.2__Create_address_table.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE Address ( - address VARCHAR(40) NOT NULL PRIMARY KEY, - alias VARCHAR(255), - public_key BLOB, - private_key BLOB -); \ No newline at end of file diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java index ee8c4c6..7bc356c 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.networking; import ch.dissem.bitmessage.BitmessageContext; +import ch.dissem.bitmessage.InternalContext; import ch.dissem.bitmessage.entity.*; import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; @@ -43,7 +44,7 @@ import static ch.dissem.bitmessage.networking.Connection.State.*; public class Connection implements Runnable { private final static Logger LOG = LoggerFactory.getLogger(Connection.class); - private BitmessageContext ctx; + private InternalContext ctx; private State state; private Socket socket; @@ -59,7 +60,7 @@ public class Connection implements Runnable { private Queue<MessagePayload> sendingQueue = new ConcurrentLinkedDeque<>(); - public Connection(BitmessageContext context, State state, Socket socket, MessageListener listener) throws IOException { + public Connection(InternalContext context, State state, Socket socket, MessageListener listener) throws IOException { this.ctx = context; this.state = state; this.socket = socket; @@ -161,12 +162,12 @@ public class Connection implements Runnable { LOG.debug(e.getMessage(), e); } // It's probably pointless, but let the listener decide if we accept the message for the client. - listener.receive(objectMessage.getPayload()); + listener.receive(objectMessage); break; case ADDR: Addr addr = (Addr) messagePayload; LOG.debug("Received " + addr.getAddresses().size() + " addresses."); - ctx.getAddressRepository().offerAddresses(addr.getAddresses()); + ctx.getNodeRegistry().offerAddresses(addr.getAddresses()); break; case VERACK: case VERSION: @@ -175,7 +176,7 @@ public class Connection implements Runnable { } private void sendAddresses() { - List<NetworkAddress> addresses = ctx.getAddressRepository().getKnownAddresses(1000, streams); + List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses(1000, streams); sendingQueue.offer(new Addr.Builder().addresses(addresses).build()); } diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java b/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java index 67389bc..b379a2c 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java @@ -16,8 +16,8 @@ package ch.dissem.bitmessage.networking; -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.BitmessageContext.ContextHolder; +import ch.dissem.bitmessage.InternalContext; +import ch.dissem.bitmessage.InternalContext.ContextHolder; import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.ports.NetworkHandler; @@ -42,7 +42,7 @@ public class NetworkNode implements NetworkHandler, ContextHolder { private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class); private final ExecutorService pool; private final List<Connection> connections = new LinkedList<>(); - private BitmessageContext ctx; + private InternalContext ctx; private ServerSocket serverSocket; private Thread connectionManager; @@ -51,7 +51,7 @@ public class NetworkNode implements NetworkHandler, ContextHolder { } @Override - public void setContext(BitmessageContext context) { + public void setContext(InternalContext context) { this.ctx = context; } @@ -88,7 +88,7 @@ public class NetworkNode implements NetworkHandler, ContextHolder { } } if (connections.size() < 1) { - List<NetworkAddress> addresses = ctx.getAddressRepository().getKnownAddresses(8, ctx.getStreams()); + List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses(8, ctx.getStreams()); for (NetworkAddress address : addresses) { try { startConnection(new Connection(ctx, CLIENT, new Socket(address.toInetAddress(), address.getPort()), listener)); diff --git a/inventory/build.gradle b/repositories/build.gradle similarity index 100% rename from inventory/build.gradle rename to repositories/build.gradle diff --git a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcAddressRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java similarity index 51% rename from inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcAddressRepository.java rename to repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java index dd5fc3c..c26a7d4 100644 --- a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcAddressRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package ch.dissem.bitmessage.inventory; +package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.payload.Pubkey; @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.sql.*; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -36,12 +37,41 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito private static final Logger LOG = LoggerFactory.getLogger(JdbcAddressRepository.class); @Override - public List<BitmessageAddress> findIdentities() { + public BitmessageAddress findContact(byte[] ripeOrTag) { + for (BitmessageAddress address : find("public_key is null")) { + if (address.getVersion() > 3) { + if (Arrays.equals(ripeOrTag, address.getTag())) return address; + } else { + if (Arrays.equals(ripeOrTag, address.getRipe())) return address; + } + } + return null; + } + + @Override + public BitmessageAddress findIdentity(byte[] ripeOrTag) { + for (BitmessageAddress address : find("private_key is not null")) { + if (address.getVersion() > 3) { + if (Arrays.equals(ripeOrTag, address.getTag())) return address; + } else { + if (Arrays.equals(ripeOrTag, address.getRipe())) return address; + } + } + return null; + } + + @Override + public List<BitmessageAddress> getIdentities() { return find("private_signing_key IS NOT NULL"); } @Override - public List<BitmessageAddress> findContacts() { + public List<BitmessageAddress> getSubscriptions() { + return find("subscribed = '1'"); + } + + @Override + public List<BitmessageAddress> getContacts() { return find("private_signing_key IS NULL"); } @@ -49,7 +79,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito List<BitmessageAddress> result = new LinkedList<>(); try { Statement stmt = getConnection().createStatement(); - ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key FROM Address WHERE " + where); + ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key, subscribed FROM Address WHERE " + where); while (rs.next()) { BitmessageAddress address; Blob privateKeyBlob = rs.getBlob("private_key"); @@ -61,11 +91,12 @@ 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()); address.setPubkey(pubkey); } } address.setAlias(rs.getString("alias")); + address.setSubscribed(rs.getBoolean("subscribed")); result.add(address); } @@ -75,20 +106,51 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito return result; } + private boolean exists(BitmessageAddress address) { + try { + Statement stmt = getConnection().createStatement(); + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Address WHERE address='" + address.getAddress() + "'"); + rs.next(); + return rs.getInt(0) > 0; + } catch (SQLException e) { + LOG.error(e.getMessage(), e); + } + return false; + } + @Override public void save(BitmessageAddress address) { try { - PreparedStatement ps = getConnection().prepareStatement( - "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()); - writeBlob(ps, 4, address.getPrivateKey()); + if (exists(address)) { + update(address); + } else { + insert(address); + } } catch (IOException | SQLException e) { LOG.error(e.getMessage(), e); } } + private void update(BitmessageAddress address) throws IOException, SQLException { + PreparedStatement ps = getConnection().prepareStatement( + "UPDATE Address SET address=?, alias=?, public_key=?, private_key=?"); + ps.setString(1, address.getAddress()); + ps.setString(2, address.getAlias()); + writeBlob(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 (?, ?, ?, ?, ?)"); + ps.setString(1, address.getAddress()); + ps.setString(2, address.getAlias()); + writeBlob(ps, 3, address.getPubkey()); + writeBlob(ps, 4, address.getPrivateKey()); + ps.executeUpdate(); + } + @Override public void remove(BitmessageAddress address) { try { @@ -98,4 +160,11 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito LOG.error(e.getMessage(), e); } } + + @Override + public BitmessageAddress getAddress(String address) { + List<BitmessageAddress> result = find("address = '" + address + "'"); + if (result.size() > 0) return result.get(0); + return null; + } } diff --git a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java similarity index 98% rename from inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcHelper.java rename to repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java index 624e180..693e571 100644 --- a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcHelper.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package ch.dissem.bitmessage.inventory; +package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.Streamable; import org.flywaydb.core.Flyway; diff --git a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcInventory.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java similarity index 99% rename from inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcInventory.java rename to repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java index e9a3fc2..1956e17 100644 --- a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcInventory.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package ch.dissem.bitmessage.inventory; +package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.payload.ObjectType; diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java new file mode 100644 index 0000000..6c7dfed --- /dev/null +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java @@ -0,0 +1,192 @@ +/* + * 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.InternalContext; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.valueobject.Label; +import ch.dissem.bitmessage.ports.MessageRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.sql.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +public class JdbcMessageRepository extends JdbcHelper implements MessageRepository, InternalContext.ContextHolder { + private static final Logger LOG = LoggerFactory.getLogger(JdbcMessageRepository.class); + + private InternalContext ctx; + + @Override + public List<String> getLabels() { + List<String> result = new LinkedList<>(); + try { + Statement stmt = getConnection().createStatement(); + ResultSet rs = stmt.executeQuery("SELECT label FROM Label ORDER BY order"); + while (rs.next()) { + result.add(rs.getString("label")); + } + } catch (SQLException e) { + LOG.error(e.getMessage(), e); + } + return result; + } + + @Override + public List<Plaintext> findMessages(Label label) { + return find("id IN SELECT message_id FROM Message_Label WHERE label_id=" + label.getId()); + } + + @Override + public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) { + return find("status='" + status.name() + "' AND to='" + recipient.getAddress() + "'"); + } + + @Override + public List<Plaintext> findMessages(Plaintext.Status status) { + return find("status='" + status.name() + "'"); + } + + private List<Plaintext> find(String where) { + 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); + 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.sent(rs.getLong("sent")); + builder.received(rs.getLong("received")); + builder.status(Plaintext.Status.valueOf(rs.getString("status"))); + builder.labels(findLabels(id)); + result.add(builder.build()); + } + } catch (IOException | SQLException e) { + LOG.error(e.getMessage(), e); + } + return result; + } + + private Collection<Label> findLabels(long messageId) { + 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); + while (rs.next()) { + result.add(new Label(rs.getString("label"), rs.getInt("color"))); + } + } catch (SQLException e) { + LOG.error(e.getMessage(), e); + } + return result; + } + + @Override + public void save(Plaintext message) { + // save from address if necessary + if (message.getId() == null) { + BitmessageAddress savedAddress = ctx.getAddressRepo().getAddress(message.getFrom().getAddress()); + if (savedAddress.getPrivateKey() == null) { + if (savedAddress.getAlias() != null) { + message.getFrom().setAlias(savedAddress.getAlias()); + } + ctx.getAddressRepo().save(message.getFrom()); + } + } + + Connection connection = getConnection(); + try { + connection.setAutoCommit(false); + // save message + if (message.getId() == null) { + insert(connection, message); + + // remove existing labels + Statement stmt = connection.createStatement(); + stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=" + message.getId()); + } else { + update(connection, message); + } + + // save labels + PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Label VALUES (" + message.getId() + ", ?)"); + for (Label label : message.getLabels()) { + ps.setLong(1, (Long) label.getId()); + ps.executeUpdate(); + } + + connection.commit(); + } catch (IOException | SQLException e) { + try { + connection.rollback(); + } catch (SQLException e1) { + LOG.debug(e1.getMessage(), e); + } + throw new RuntimeException(e); + } + } + + 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); + ps.setString(1, message.getFrom().getAddress()); + ps.setString(2, message.getTo().getAddress()); + writeBlob(ps, 3, message); + ps.setLong(4, message.getSent()); + ps.setLong(5, message.getReceived()); + ps.setString(6, message.getStatus().name()); + ps.executeUpdate(); + + // get generated id + ResultSet rs = ps.getGeneratedKeys(); + rs.next(); + message.setId(rs.getLong(1)); + } + + private void update(Connection connection, Plaintext message) throws SQLException, IOException { + PreparedStatement ps = connection.prepareStatement( + "UPDATE Message SET \"sent\"=?, \"received\"=?, \"status\"=?"); + ps.setLong(1, message.getSent()); + ps.setLong(2, message.getReceived()); + ps.setString(3, message.getStatus().name()); + ps.executeUpdate(); + } + + @Override + public void remove(Plaintext message) { + try { + Statement stmt = getConnection().createStatement(); + stmt.executeUpdate("DELETE FROM Message WHERE id = " + message.getId()); + } catch (SQLException e) { + LOG.error(e.getMessage(), e); + } + } + + @Override + public void setContext(InternalContext context) { + this.ctx = context; + } +} diff --git a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcNodeRegistry.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java similarity index 98% rename from inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcNodeRegistry.java rename to repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java index 13450bd..1a9bc39 100644 --- a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcNodeRegistry.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package ch.dissem.bitmessage.inventory; +package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.ports.NodeRegistry; diff --git a/inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleInventory.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/SimpleInventory.java similarity index 97% rename from inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleInventory.java rename to repositories/src/main/java/ch/dissem/bitmessage/repository/SimpleInventory.java index e88c536..32ed44a 100644 --- a/inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleInventory.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/SimpleInventory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package ch.dissem.bitmessage.inventory; +package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.payload.ObjectType; diff --git a/inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleNodeRegistry.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/SimpleNodeRegistry.java similarity index 96% rename from inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleNodeRegistry.java rename to repositories/src/main/java/ch/dissem/bitmessage/repository/SimpleNodeRegistry.java index 39a6fa4..d3dadd2 100644 --- a/inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleNodeRegistry.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/SimpleNodeRegistry.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package ch.dissem.bitmessage.inventory; +package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.ports.NodeRegistry; diff --git a/repositories/src/main/resources/db/migration/V1.0__Create_node_table.sql b/repositories/src/main/resources/db/migration/V1.0__Create_node_table.sql new file mode 100644 index 0000000..32868ea --- /dev/null +++ b/repositories/src/main/resources/db/migration/V1.0__Create_node_table.sql @@ -0,0 +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, + + PRIMARY KEY ("ip", "port", "stream") +); \ No newline at end of file diff --git a/repositories/src/main/resources/db/migration/V1.1__Create_inventory_table.sql b/repositories/src/main/resources/db/migration/V1.1__Create_inventory_table.sql new file mode 100644 index 0000000..0e5d9b5 --- /dev/null +++ b/repositories/src/main/resources/db/migration/V1.1__Create_inventory_table.sql @@ -0,0 +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 +); \ No newline at end of file diff --git a/repositories/src/main/resources/db/migration/V1.2__Create_address_table.sql b/repositories/src/main/resources/db/migration/V1.2__Create_address_table.sql new file mode 100644 index 0000000..2898544 --- /dev/null +++ b/repositories/src/main/resources/db/migration/V1.2__Create_address_table.sql @@ -0,0 +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' +); \ No newline at end of file diff --git a/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql b/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql new file mode 100644 index 0000000..275d927 --- /dev/null +++ b/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql @@ -0,0 +1,32 @@ +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 +); + +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") +); + +CREATE TABLE Message_Label ( + "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") +); + +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); diff --git a/settings.gradle b/settings.gradle index b66fc83..26ef61b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ include 'domain' include 'networking' -include 'inventory' +include 'repositories' include 'demo'