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 671bb29..76f3642 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -16,18 +16,17 @@ package ch.dissem.bitmessage.demo; -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.entity.payload.ObjectPayload; -import ch.dissem.bitmessage.inventory.JdbcAddressRepository; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.payload.ObjectType; +import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.inventory.JdbcInventory; -import ch.dissem.bitmessage.inventory.JdbcNodeRegistry; -import ch.dissem.bitmessage.networking.NetworkNode; -import ch.dissem.bitmessage.ports.NetworkHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Scanner; +import java.util.Arrays; +import java.util.List; /** * Created by chris on 06.04.15. @@ -36,26 +35,60 @@ public class Main { private final static Logger LOG = LoggerFactory.getLogger(Main.class); public static void main(String[] args) throws IOException { - BitmessageContext ctx = new BitmessageContext.Builder() - .addressRepo(new JdbcAddressRepository()) - .inventory(new JdbcInventory()) - .nodeRegistry(new JdbcNodeRegistry()) - .networkHandler(new NetworkNode()) - .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('.'); - } - }); + final BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - System.out.print("Press Enter to exit\n"); - Scanner scanner = new Scanner(System.in); - scanner.nextLine(); - LOG.info("Shutting down client"); - ctx.getNetworkHandler().stop(); +// BitmessageContext ctx = new BitmessageContext.Builder() +// .addressRepo(new JdbcAddressRepository()) +// .inventory(new JdbcInventory()) +// .nodeRegistry(new JdbcNodeRegistry()) +// .networkHandler(new NetworkNode()) +// .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 V3Pubkey) { +// V3Pubkey pubkey = (V3Pubkey) payload; +// try { +// address.setPubkey(pubkey); +// System.out.println(address); +// } catch (Exception ignore) { +// System.err.println("Received pubkey we didn't request."); +// } +// } +// } +// }); +// +// 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.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) { + Pubkey pubkey = (Pubkey) o.getPayload(); + if (Arrays.equals(address.getRipe(), pubkey.getRipe())) + System.out.println("Pubkey found!"); + try { + address.setPubkey(pubkey); + System.out.println(address); + } catch (Exception ignore) { + System.out.println("But setPubkey failed? " + address.getRipe().length + "/" + pubkey.getRipe().length); + if (Arrays.equals(address.getRipe(), pubkey.getRipe())) { + ignore.printStackTrace(); + } + } + } } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index ed355ab..0d45e22 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -16,11 +16,15 @@ package ch.dissem.bitmessage; -import ch.dissem.bitmessage.ports.AddressRepository; -import ch.dissem.bitmessage.ports.Inventory; -import ch.dissem.bitmessage.ports.NetworkHandler; -import ch.dissem.bitmessage.ports.NodeRegistry; +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.Collection; import java.util.TreeSet; @@ -29,15 +33,16 @@ import java.util.TreeSet; */ 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 Inventory inventory; - private NodeRegistry nodeRegistry; - private NetworkHandler networkHandler; - private AddressRepository addressRepo; + private final TreeSet streams; - private Collection streams = new TreeSet<>(); - - private int port; + private final int port; private long networkNonceTrialsPerByte = 1000; private long networkExtraBytes = 1000; @@ -48,9 +53,10 @@ public class BitmessageContext { nodeRegistry = builder.nodeRegistry; networkHandler = builder.networkHandler; addressRepo = builder.addressRepo; + proofOfWorkEngine = builder.proofOfWorkEngine; streams = builder.streams; - init(inventory, nodeRegistry, networkHandler, addressRepo); + init(inventory, nodeRegistry, networkHandler, addressRepo, proofOfWorkEngine); } private void init(Object... objects) { @@ -61,6 +67,20 @@ public class BitmessageContext { } } + 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) + .build(); + Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes); + inventory.storeObject(object); + networkHandler.offer(object.getInventoryVector()); + } + public Inventory getInventory() { return inventory; } @@ -113,7 +133,8 @@ public class BitmessageContext { private NodeRegistry nodeRegistry; private NetworkHandler networkHandler; private AddressRepository addressRepo; - private Collection streams; + private ProofOfWorkEngine proofOfWorkEngine; + private TreeSet streams; public Builder() { } @@ -143,8 +164,13 @@ public class BitmessageContext { return this; } + public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) { + this.proofOfWorkEngine = proofOfWorkEngine; + return this; + } + public Builder streams(Collection streams) { - this.streams = streams; + this.streams = new TreeSet<>(streams); return this; } @@ -164,6 +190,9 @@ public class BitmessageContext { if (streams == null) { streams(1); } + if (proofOfWorkEngine == null) { + proofOfWorkEngine = new MultiThreadedPOWEngine(); + } return new BitmessageContext(this); } 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 9eb5955..0abb03f 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java @@ -25,6 +25,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; +import static ch.dissem.bitmessage.utils.Decode.bytes; +import static ch.dissem.bitmessage.utils.Decode.varInt; + /** * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address * holding private keys. @@ -53,10 +56,10 @@ public class BitmessageAddress { byte[] bytes = Base58.decode(address.substring(3)); ByteArrayInputStream in = new ByteArrayInputStream(bytes); AccessCounter counter = new AccessCounter(); - this.version = Decode.varInt(in, counter); - this.stream = Decode.varInt(in, counter); - this.ripe = Decode.bytes(in, bytes.length - counter.length() - 4); - testChecksum(Decode.bytes(in, 4), bytes); + this.version = varInt(in, counter); + this.stream = varInt(in, counter); + this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20); + testChecksum(bytes(in, 4), bytes); this.address = generateAddress(); } catch (IOException e) { throw new RuntimeException(e); @@ -108,10 +111,6 @@ public class BitmessageAddress { return privateKey; } - public void setAlias(String alias) { - this.alias = alias; - } - public String getAddress() { return address; } @@ -120,6 +119,10 @@ public class BitmessageAddress { return alias; } + public void setAlias(String alias) { + this.alias = alias; + } + @Override public String toString() { return alias != null ? alias : address; 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 65c40ec..e0f6864 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java @@ -89,20 +89,20 @@ public class ObjectMessage implements MessagePayload { } @Override - public void write(OutputStream stream) throws IOException { - stream.write(nonce); - stream.write(getPayloadBytesWithoutNonce()); + public void write(OutputStream out) throws IOException { + out.write(nonce); + out.write(getPayloadBytesWithoutNonce()); } public byte[] getPayloadBytesWithoutNonce() throws IOException { if (payloadBytes == null) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.int64(expiresTime, stream); - Encode.int32(objectType, stream); - Encode.varInt(version, stream); - Encode.varInt(this.stream, stream); - payload.write(stream); - payloadBytes = stream.toByteArray(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Encode.int64(expiresTime, out); + Encode.int32(objectType, out); + Encode.varInt(version, out); + Encode.varInt(stream, out); + payload.write(out); + payloadBytes = out.toByteArray(); } return payloadBytes; } @@ -110,8 +110,8 @@ public class ObjectMessage implements MessagePayload { public static final class Builder { private byte[] nonce; private long expiresTime; - private long objectType; - private long version; + private long objectType = -1; + private long version = -1; private long streamNumber; private ObjectPayload payload; @@ -138,13 +138,15 @@ public class ObjectMessage implements MessagePayload { return this; } - public Builder streamNumber(long streamNumber) { + public Builder stream(long streamNumber) { this.streamNumber = streamNumber; return this; } public Builder payload(ObjectPayload payload) { this.payload = payload; + if (this.objectType == -1) + this.objectType = payload.getType().getNumber(); return this; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java index 9c74348..dbd3dd6 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java @@ -39,6 +39,11 @@ public class GenericPayload implements ObjectPayload { return new GenericPayload(stream, Decode.bytes(is, length)); } + @Override + public ObjectType getType() { + return null; + } + @Override public long getStream() { return stream; 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 f44eafa..0b1fce8 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 @@ -16,6 +16,8 @@ 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 java.io.IOException; @@ -30,22 +32,30 @@ public class GetPubkey implements ObjectPayload { private byte[] ripe; private byte[] tag; - private GetPubkey(long stream, byte[] ripeOrTag) { + public GetPubkey(BitmessageAddress address) { + this.stream = address.getStream(); + if (address.getVersion() < 4) + this.ripe = address.getRipe(); + else + this.tag = ((V4Pubkey) address.getPubkey()).getTag(); + } + + private GetPubkey(long stream, long version, byte[] ripeOrTag) { this.stream = stream; - switch (ripeOrTag.length) { - case 20: - ripe = ripeOrTag; - break; - case 32: - tag = ripeOrTag; - break; - default: - throw new RuntimeException("ripe (20 bytes) or tag (32 bytes) expected, but pubkey was " + ripeOrTag.length + " bytes long."); + if (version < 4) { + ripe = ripeOrTag; + } else { + tag = ripeOrTag; } } - public static GetPubkey read(InputStream is, long stream, int length) throws IOException { - return new GetPubkey(stream, Decode.bytes(is, length)); + public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException { + return new GetPubkey(stream, version, Decode.bytes(is, length)); + } + + @Override + public ObjectType getType() { + return ObjectType.GET_PUBKEY; } @Override 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 229a95a..94b8dd1 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 @@ -44,6 +44,11 @@ public class Msg implements ObjectPayload { return new Msg(stream, Decode.bytes(is, length)); } + @Override + public ObjectType getType() { + return ObjectType.MSG; + } + @Override public long getStream() { return stream; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java index d3aefe2..67bc856 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java @@ -22,5 +22,7 @@ import ch.dissem.bitmessage.entity.Streamable; * The payload of an 'object' command. This is shared by the network. */ public interface ObjectPayload extends Streamable { + ObjectType getType(); + long getStream(); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java new file mode 100644 index 0000000..06ea92f --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * Known types for 'object' messages. Must not be used where an unknown type must be resent. + */ +public enum ObjectType { + GET_PUBKEY(0), + PUBKEY(1), + MSG(2), + BROADCAST(3); + + int number; + + ObjectType(int number) { + this.number = number; + } + + public static ObjectType fromNumber(long number) { + for (ObjectType type : values()) { + if (type.number == number) return type; + } + return null; + } + + public long getNumber() { + return number; + } +} 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 6675e96..0411822 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 @@ -56,6 +56,11 @@ public class V2Pubkey extends Pubkey { return 2; } + @Override + public ObjectType getType() { + return ObjectType.PUBKEY; + } + @Override public long getStream() { return stream; 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 d3b72f8..af5b0c9 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 @@ -40,6 +40,11 @@ public class V4Broadcast implements Broadcast { return new V4Broadcast(stream, Decode.bytes(is, length)); } + @Override + public ObjectType getType() { + return ObjectType.BROADCAST; + } + @Override public long getStream() { return stream; 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 54e6644..db1d3f5 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 @@ -62,6 +62,11 @@ public class V4Pubkey extends Pubkey { return 4; } + @Override + public ObjectType getType() { + return ObjectType.PUBKEY; + } + @Override public long getStream() { return stream; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java index 0be2d7e..562f6e5 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java @@ -103,7 +103,7 @@ public class NetworkAddress implements Streamable { @Override public String toString() { - return toInetAddress() + ":" + port; + return "[" + toInetAddress() + "]:" + port; } @Override diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java index 8d0e804..f6f8fd8 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java @@ -101,27 +101,28 @@ public class Factory { } static ObjectPayload getObjectPayload(long objectType, long version, long streamNumber, InputStream stream, int length) throws IOException { - if (objectType < 4) { - switch ((int) objectType) { - case 0: + ObjectType type = ObjectType.fromNumber(objectType); + if (type != null) { + switch (type) { + case GET_PUBKEY: return parseGetPubkey(version, streamNumber, stream, length); - case 1: + case PUBKEY: return parsePubkey(version, streamNumber, stream, length); - case 2: + case MSG: return parseMsg(version, streamNumber, stream, length); - case 3: + case BROADCAST: return parseBroadcast(version, streamNumber, stream, length); default: LOG.error("This should not happen, someone broke something in the code!"); } } // fallback: just store the message - we don't really care what it is - LOG.warn("Unexpected object type: " + objectType); +// LOG.info("Unexpected object type: " + objectType); return GenericPayload.read(stream, streamNumber, length); } private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { - return GetPubkey.read(stream, streamNumber, length); + return GetPubkey.read(stream, streamNumber, length, version); } public static Pubkey readPubkey(long version, long stream, InputStream is, int length) throws IOException { diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java index 99ef744..23f97e2 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java +++ b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java @@ -46,7 +46,10 @@ class V3MessageFactory { if (testChecksum(checksum, payloadBytes)) { MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length); - return new NetworkMessage(payload); + if (payload != null) + return new NetworkMessage(payload); + else + return null; } else { throw new IOException("Checksum failed for message '" + command + "'"); } @@ -84,14 +87,14 @@ class V3MessageFactory { long version = Decode.varInt(stream, counter); long streamNumber = Decode.varInt(stream, counter); - ObjectPayload payload = Factory.getObjectPayload(objectType, version, streamNumber, stream, length-counter.length()); + ObjectPayload payload = Factory.getObjectPayload(objectType, version, streamNumber, stream, length - counter.length()); return new ObjectMessage.Builder() .nonce(nonce) .expiresTime(expiresTime) .objectType(objectType) .version(version) - .streamNumber(streamNumber) + .stream(streamNumber) .payload(payload) .build(); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java b/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java index b0b1a86..31e0e8d 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/Inventory.java @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.ports; import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.payload.ObjectType; import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import java.util.List; @@ -31,6 +32,8 @@ public interface Inventory { ObjectMessage getObject(InventoryVector vector); + List getObjects(long stream, long version, ObjectType type); + void storeObject(ObjectMessage object); void cleanup(); 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 616673f..620b57d 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java @@ -18,6 +18,7 @@ package ch.dissem.bitmessage.ports; import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.entity.payload.ObjectPayload; +import ch.dissem.bitmessage.entity.valueobject.InventoryVector; /** * Handles incoming messages @@ -27,7 +28,7 @@ public interface NetworkHandler { void stop(); - void send(ObjectPayload payload); + void offer(InventoryVector iv); interface MessageListener { void receive(ObjectPayload payload); 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 a078f94..0cef4ff 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java @@ -80,6 +80,9 @@ public class Security { } public static void doProofOfWork(ObjectMessage object, ProofOfWorkEngine worker, long nonceTrialsPerByte, long extraBytes) throws IOException { + if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000; + if (extraBytes < 1000) extraBytes = 1000; + byte[] initialHash = getInitialHash(object); byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java index 46da936..8529f4e 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Strings.java @@ -20,7 +20,7 @@ package ch.dissem.bitmessage.utils; * Created by chris on 13.04.15. */ public class Strings { - public static CharSequence join(byte[]... objects) { + public static StringBuilder join(byte[]... objects) { StringBuilder streamList = new StringBuilder(); for (int i = 0; i < objects.length; i++) { if (i > 0) streamList.append(", "); @@ -29,7 +29,7 @@ public class Strings { return streamList; } - public static CharSequence join(long... objects) { + public static StringBuilder join(long... objects) { StringBuilder streamList = new StringBuilder(); for (int i = 0; i < objects.length; i++) { if (i > 0) streamList.append(", "); @@ -38,7 +38,7 @@ public class Strings { return streamList; } - public static CharSequence join(Object... objects) { + public static StringBuilder join(Object... objects) { StringBuilder streamList = new StringBuilder(); for (int i = 0; i < objects.length; i++) { if (i > 0) streamList.append(", "); @@ -47,9 +47,8 @@ public class Strings { return streamList; } - public static CharSequence hex(byte[] bytes) { + public static StringBuilder hex(byte[] bytes) { StringBuilder hex = new StringBuilder(bytes.length + 2); - hex.append("0x"); for (byte b : bytes) { hex.append(String.format("%02x", b)); } 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 374f077..da5d585 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java @@ -26,4 +26,8 @@ public class UnixTime { public static long now() { return System.currentTimeMillis() / 1000; } + + public static long now(long shiftSeconds) { + return (System.currentTimeMillis() / 1000) + shiftSeconds; + } } diff --git a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java index 3da1811..dcbc766 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java @@ -16,14 +16,14 @@ package ch.dissem.bitmessage.entity; +import ch.dissem.bitmessage.entity.payload.V3Pubkey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.utils.Base58; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.Security; +import ch.dissem.bitmessage.utils.*; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import java.io.IOException; + +import static org.junit.Assert.*; public class BitmessageAddressTest { @Test @@ -49,24 +49,59 @@ public class BitmessageAddressTest { assertNotNull(address.getPubkey()); } + @Test + public void testV3() { +// ripe 007402be6e76c3cb87caa946d0c003a3d4d8e1d5 +// publicSigningKey in hex: 0435e3f10f4884ec42f11f1a815ace8c7c4575cad455ca98db19a245c4c57baebdce990919b647f2657596b75aa939b858bd70c55a03492dd95119bef009cf9eea +// publicEncryptionKey in hex: 04bf30a7ee7854f9381332a6285659215a6a4b2ab3479fa87fe996f7cd11710367748371d8d2545f8466964dd3140ab80508b2b18e45616ef6cc4d8e54db923761 + BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); + V3Pubkey pubkey = new V3Pubkey.Builder() + .stream(1) + .publicSigningKey(Bytes.fromHex("0435e3f10f4884ec42f11f1a815ace8c7c4575cad455ca98db19a245c4c57baebdce990919b647f2657596b75aa939b858bd70c55a03492dd95119bef009cf9eea")) + .publicEncryptionKey(Bytes.fromHex("04bf30a7ee7854f9381332a6285659215a6a4b2ab3479fa87fe996f7cd11710367748371d8d2545f8466964dd3140ab80508b2b18e45616ef6cc4d8e54db923761")) + .build(); + address.setPubkey(pubkey); + assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.getRipe()); + } + + @Test + public void testV3PubkeyImport() throws IOException { + ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload"); + V3Pubkey pubkey = (V3Pubkey) object.getPayload(); + BitmessageAddress address = new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"); + address.setPubkey(pubkey); + } + @Test public void testV3Import() { - assertEquals(3, new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn").getVersion()); - assertEquals(1, new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn").getStream()); + String address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"; + assertEquals(3, new BitmessageAddress(address_string).getVersion()); + assertEquals(1, new BitmessageAddress(address_string).getStream()); - byte[] privsigningkey = Base58.decode("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9"); - byte[] privencryptionkey = Base58.decode("5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck"); - assertEquals((byte) 0x80, privsigningkey[0]); - assertEquals((byte) 0x80, privencryptionkey[0]); - privsigningkey = Bytes.subArray(privsigningkey, 1, privsigningkey.length - 5); - privencryptionkey = Bytes.subArray(privencryptionkey, 1, privencryptionkey.length - 5); + byte[] privsigningkey = getSecret("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9"); + byte[] privencryptionkey = getSecret("5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck"); - privsigningkey = Bytes.expand(privsigningkey, 32); - privencryptionkey = Bytes.expand(privencryptionkey, 32); + System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n"); + +// privsigningkey = Bytes.expand(privsigningkey, 32); +// privencryptionkey = Bytes.expand(privencryptionkey, 32); BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey, Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); - assertEquals("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn", address.getAddress()); + assertEquals(address_string, address.getAddress()); + } + + private byte[] getSecret(String walletImportFormat) { + byte[] bytes = Base58.decode("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9"); + assertEquals(37, bytes.length); + assertEquals((byte) 0x80, bytes[0]); + byte[] checksum = Bytes.subArray(bytes, bytes.length - 4, 4); + byte[] secret = Bytes.subArray(bytes, 1, 32); +// assertArrayEquals("Checksum failed", checksum, Bytes.subArray(Security.doubleSha512(new byte[]{(byte) 0x80}, secret, new byte[]{0x01}), 0, 4)); + byte[] result = new byte[33]; + result[0] = 0x04; + System.arraycopy(secret, 0, result, 1, secret.length); + return result; } @Test diff --git a/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java index a3163f2..baefe7b 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java @@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity; import ch.dissem.bitmessage.entity.payload.*; import ch.dissem.bitmessage.factory.Factory; +import ch.dissem.bitmessage.utils.TestUtils; import org.junit.Test; import java.io.ByteArrayInputStream; @@ -69,7 +70,7 @@ public class SerializationTest { } private void doTest(String resourceName, int version, Class expectedPayloadType) throws IOException { - byte[] data = getBytes(resourceName); + byte[] data = TestUtils.getBytes(resourceName); InputStream in = new ByteArrayInputStream(data); ObjectMessage object = Factory.getObjectMessage(version, in, data.length); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -77,16 +78,4 @@ public class SerializationTest { assertArrayEquals(data, out.toByteArray()); assertEquals(expectedPayloadType.getCanonicalName(), object.getPayload().getClass().getCanonicalName()); } - - private byte[] getBytes(String resourceName) throws IOException { - InputStream in = getClass().getClassLoader().getResourceAsStream(resourceName); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int len = in.read(buffer); - while (len != -1) { - out.write(buffer, 0, len); - len = in.read(buffer); - } - return out.toByteArray(); - } } diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java index 797e1e6..7c83724 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java @@ -32,7 +32,14 @@ public class BytesTest { public static final Random rnd = new Random(); @Test - public void testIncrement() throws IOException { + public void ensureExpandsCorrectly() { + byte[] source = {1}; + byte[] expected = {0,1}; + assertArrayEquals(expected, Bytes.expand(source, 2)); + } + + @Test + public void ensureIncrementCarryWorks() throws IOException { byte[] bytes = {0, -1}; Bytes.inc(bytes); assertArrayEquals(TestUtils.int16(256), bytes); diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java index 9fb87a4..c154b5e 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java @@ -29,6 +29,7 @@ public class StringsTest { @Test public void testHexString() { - assertEquals("0x48656c6c6f21", Strings.hex("Hello!".getBytes())); + assertEquals("48656c6c6f21", Strings.hex("Hello!".getBytes()).toString()); + assertEquals("0001", Strings.hex(new byte[]{0, 1}).toString()); } } diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java b/domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java index 6a7f244..53370b5 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java +++ b/domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java @@ -16,8 +16,13 @@ package ch.dissem.bitmessage.utils; +import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.factory.Factory; + +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; /** * If there's ever a need for this in production code, it should be rewritten to be more efficient. @@ -28,4 +33,22 @@ public class TestUtils { Encode.int16(number, out); return out.toByteArray(); } + + public static ObjectMessage loadObjectMessage(int version, String resourceName) throws IOException { + byte[] data = getBytes(resourceName); + InputStream in = new ByteArrayInputStream(data); + return Factory.getObjectMessage(version, in, data.length); + } + + public static byte[] getBytes(String resourceName) throws IOException { + InputStream in = TestUtils.class.getClassLoader().getResourceAsStream(resourceName); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len = in.read(buffer); + while (len != -1) { + out.write(buffer, 0, len); + len = in.read(buffer); + } + return out.toByteArray(); + } } diff --git a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcInventory.java b/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcInventory.java index c0bcb18..e9a3fc2 100644 --- a/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcInventory.java +++ b/inventory/src/main/java/ch/dissem/bitmessage/inventory/JdbcInventory.java @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.inventory; import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.payload.ObjectType; import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.ports.Inventory; @@ -62,9 +63,41 @@ public class JdbcInventory extends JdbcHelper implements Inventory { public ObjectMessage getObject(InventoryVector vector) { try { Statement stmt = getConnection().createStatement(); - ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = " + vector); - Blob data = rs.getBlob("data"); - return Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()); + ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'"); + if (rs.next()) { + Blob data = rs.getBlob("data"); + return Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()); + } else { + LOG.info("Object requested that we don't have. IV: " + vector); + return null; + } + } catch (Exception e) { + LOG.error(e.getMessage(), e); + throw new RuntimeException(e); + } + } + + @Override + public List getObjects(long stream, long version, ObjectType type) { + try { + StringBuilder query = new StringBuilder("SELECT data, version FROM Inventory WHERE 1=1"); + if (stream >= 0) { + query.append(" AND stream = ").append(stream); + } + if (version >= 0) { + query.append(" AND version = ").append(version); + } + if (type != null) { + query.append(" AND type = ").append(type.getNumber()); + } + Statement stmt = getConnection().createStatement(); + ResultSet rs = stmt.executeQuery(query.toString()); + List result = new LinkedList<>(); + while (rs.next()) { + Blob data = rs.getBlob("data"); + result.add(Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length())); + } + return result; } catch (Exception e) { LOG.error(e.getMessage(), e); throw new RuntimeException(e); diff --git a/inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleInventory.java b/inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleInventory.java index 2574d68..e88c536 100644 --- a/inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleInventory.java +++ b/inventory/src/main/java/ch/dissem/bitmessage/inventory/SimpleInventory.java @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.inventory; 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; @@ -43,6 +44,11 @@ public class SimpleInventory implements Inventory { throw new NotImplementedException(); } + @Override + public List getObjects(long stream, long version, ObjectType type) { + return new LinkedList<>(); + } + @Override public void storeObject(ObjectMessage object) { throw new NotImplementedException(); 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 443e356..ee8c4c6 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java @@ -91,6 +91,7 @@ public class Connection implements Runnable { switch (state) { case ACTIVE: receiveMessage(msg.getPayload()); + sendQueue(); break; default: @@ -121,14 +122,19 @@ public class Connection implements Runnable { } } catch (SocketTimeoutException e) { if (state == ACTIVE) { - for (MessagePayload msg = sendingQueue.poll(); msg != null; msg = sendingQueue.poll()) { - send(msg); - } + sendQueue(); } } } } + private void sendQueue() { + LOG.debug("Sending " + sendingQueue.size() + " messages to node " + node); + for (MessagePayload msg = sendingQueue.poll(); msg != null; msg = sendingQueue.poll()) { + send(msg); + } + } + private void receiveMessage(MessagePayload messagePayload) { switch (messagePayload.getCommand()) { case INV: @@ -140,11 +146,10 @@ public class Connection implements Runnable { break; case GETDATA: GetData getData = (GetData) messagePayload; -// for (InventoryVector iv : getData.getInventory()) { -// ObjectMessage om = ctx.getInventory().getObject(iv); -// sendingQueue.offer(om); -// } - LOG.error("Node requests data!!!! This shouldn't happen, the hash is done wrong!!!"); + for (InventoryVector iv : getData.getInventory()) { + ObjectMessage om = ctx.getInventory().getObject(iv); + if (om != null) sendingQueue.offer(om); + } break; case OBJECT: ObjectMessage objectMessage = (ObjectMessage) messagePayload; @@ -201,5 +206,12 @@ public class Connection implements Runnable { } } + public void offer(InventoryVector iv) { + LOG.debug("Offering " + iv + " to node " + node.toString()); + sendingQueue.offer(new Inv.Builder() + .addInventoryVector(iv) + .build()); + } + public enum State {SERVER, CLIENT, ACTIVE, DISCONNECTED} } 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 8cd85f6..67389bc 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java @@ -18,7 +18,7 @@ package ch.dissem.bitmessage.networking; import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext.ContextHolder; -import ch.dissem.bitmessage.entity.payload.ObjectPayload; +import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.ports.NetworkHandler; import org.slf4j.Logger; @@ -40,9 +40,9 @@ import static ch.dissem.bitmessage.networking.Connection.State.*; */ public class NetworkNode implements NetworkHandler, ContextHolder { private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class); - private BitmessageContext ctx; private final ExecutorService pool; private final List connections = new LinkedList<>(); + private BitmessageContext ctx; private ServerSocket serverSocket; private Thread connectionManager; @@ -137,7 +137,15 @@ public class NetworkNode implements NetworkHandler, ContextHolder { } @Override - public void send(final ObjectPayload payload) { - // TODO: sendingQueue.add(message); + public void offer(final InventoryVector iv) { + // TODO: + // - should offer to (random) 8 nodes during 8 seconds (if possible) + // - should probably offer later if no connection available at the moment? + synchronized (connections) { + LOG.debug(connections.size() + " connections available to offer " + iv); + for (Connection connection : connections) { + connection.offer(iv); + } + } } }