Some work on addresses and private keys that still doesn't work. As a side effect, sending objects now works basically.

This commit is contained in:
Christian Basler 2015-05-01 17:14:43 +02:00
parent 8d1466f6f4
commit a65907f13b
29 changed files with 414 additions and 138 deletions

View File

@ -16,18 +16,17 @@
package ch.dissem.bitmessage.demo; package ch.dissem.bitmessage.demo;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.ObjectPayload; import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.inventory.JdbcAddressRepository; 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.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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Scanner; import java.util.Arrays;
import java.util.List;
/** /**
* Created by chris on 06.04.15. * Created by chris on 06.04.15.
@ -36,26 +35,60 @@ public class Main {
private final static Logger LOG = LoggerFactory.getLogger(Main.class); private final static Logger LOG = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
BitmessageContext ctx = new BitmessageContext.Builder() final BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
.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('.');
}
});
System.out.print("Press Enter to exit\n"); // BitmessageContext ctx = new BitmessageContext.Builder()
Scanner scanner = new Scanner(System.in); // .addressRepo(new JdbcAddressRepository())
scanner.nextLine(); // .inventory(new JdbcInventory())
LOG.info("Shutting down client"); // .nodeRegistry(new JdbcNodeRegistry())
ctx.getNetworkHandler().stop(); // .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<ObjectMessage> objects = new JdbcInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY);
System.out.println("Address version: " + address.getVersion());
System.out.println("Address stream: " + address.getStream());
for (ObjectMessage o : objects) {
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();
}
}
}
} }
} }

View File

@ -16,11 +16,15 @@
package ch.dissem.bitmessage; package ch.dissem.bitmessage;
import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.ports.Inventory; import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.ports.NetworkHandler; import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.ports.NodeRegistry; 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.Collection;
import java.util.TreeSet; import java.util.TreeSet;
@ -29,15 +33,16 @@ import java.util.TreeSet;
*/ */
public class BitmessageContext { public class BitmessageContext {
public static final int CURRENT_VERSION = 3; 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 final TreeSet<Long> streams;
private NodeRegistry nodeRegistry;
private NetworkHandler networkHandler;
private AddressRepository addressRepo;
private Collection<Long> streams = new TreeSet<>(); private final int port;
private int port;
private long networkNonceTrialsPerByte = 1000; private long networkNonceTrialsPerByte = 1000;
private long networkExtraBytes = 1000; private long networkExtraBytes = 1000;
@ -48,9 +53,10 @@ public class BitmessageContext {
nodeRegistry = builder.nodeRegistry; nodeRegistry = builder.nodeRegistry;
networkHandler = builder.networkHandler; networkHandler = builder.networkHandler;
addressRepo = builder.addressRepo; addressRepo = builder.addressRepo;
proofOfWorkEngine = builder.proofOfWorkEngine;
streams = builder.streams; streams = builder.streams;
init(inventory, nodeRegistry, networkHandler, addressRepo); init(inventory, nodeRegistry, networkHandler, addressRepo, proofOfWorkEngine);
} }
private void init(Object... objects) { 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() { public Inventory getInventory() {
return inventory; return inventory;
} }
@ -113,7 +133,8 @@ public class BitmessageContext {
private NodeRegistry nodeRegistry; private NodeRegistry nodeRegistry;
private NetworkHandler networkHandler; private NetworkHandler networkHandler;
private AddressRepository addressRepo; private AddressRepository addressRepo;
private Collection<Long> streams; private ProofOfWorkEngine proofOfWorkEngine;
private TreeSet<Long> streams;
public Builder() { public Builder() {
} }
@ -143,8 +164,13 @@ public class BitmessageContext {
return this; return this;
} }
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
this.proofOfWorkEngine = proofOfWorkEngine;
return this;
}
public Builder streams(Collection<Long> streams) { public Builder streams(Collection<Long> streams) {
this.streams = streams; this.streams = new TreeSet<>(streams);
return this; return this;
} }
@ -164,6 +190,9 @@ public class BitmessageContext {
if (streams == null) { if (streams == null) {
streams(1); streams(1);
} }
if (proofOfWorkEngine == null) {
proofOfWorkEngine = new MultiThreadedPOWEngine();
}
return new BitmessageContext(this); return new BitmessageContext(this);
} }

View File

@ -25,6 +25,9 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; 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 * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
* holding private keys. * holding private keys.
@ -53,10 +56,10 @@ public class BitmessageAddress {
byte[] bytes = Base58.decode(address.substring(3)); byte[] bytes = Base58.decode(address.substring(3));
ByteArrayInputStream in = new ByteArrayInputStream(bytes); ByteArrayInputStream in = new ByteArrayInputStream(bytes);
AccessCounter counter = new AccessCounter(); AccessCounter counter = new AccessCounter();
this.version = Decode.varInt(in, counter); this.version = varInt(in, counter);
this.stream = Decode.varInt(in, counter); this.stream = varInt(in, counter);
this.ripe = Decode.bytes(in, bytes.length - counter.length() - 4); this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
testChecksum(Decode.bytes(in, 4), bytes); testChecksum(bytes(in, 4), bytes);
this.address = generateAddress(); this.address = generateAddress();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -108,10 +111,6 @@ public class BitmessageAddress {
return privateKey; return privateKey;
} }
public void setAlias(String alias) {
this.alias = alias;
}
public String getAddress() { public String getAddress() {
return address; return address;
} }
@ -120,6 +119,10 @@ public class BitmessageAddress {
return alias; return alias;
} }
public void setAlias(String alias) {
this.alias = alias;
}
@Override @Override
public String toString() { public String toString() {
return alias != null ? alias : address; return alias != null ? alias : address;

View File

@ -89,20 +89,20 @@ public class ObjectMessage implements MessagePayload {
} }
@Override @Override
public void write(OutputStream stream) throws IOException { public void write(OutputStream out) throws IOException {
stream.write(nonce); out.write(nonce);
stream.write(getPayloadBytesWithoutNonce()); out.write(getPayloadBytesWithoutNonce());
} }
public byte[] getPayloadBytesWithoutNonce() throws IOException { public byte[] getPayloadBytesWithoutNonce() throws IOException {
if (payloadBytes == null) { if (payloadBytes == null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
Encode.int64(expiresTime, stream); Encode.int64(expiresTime, out);
Encode.int32(objectType, stream); Encode.int32(objectType, out);
Encode.varInt(version, stream); Encode.varInt(version, out);
Encode.varInt(this.stream, stream); Encode.varInt(stream, out);
payload.write(stream); payload.write(out);
payloadBytes = stream.toByteArray(); payloadBytes = out.toByteArray();
} }
return payloadBytes; return payloadBytes;
} }
@ -110,8 +110,8 @@ public class ObjectMessage implements MessagePayload {
public static final class Builder { public static final class Builder {
private byte[] nonce; private byte[] nonce;
private long expiresTime; private long expiresTime;
private long objectType; private long objectType = -1;
private long version; private long version = -1;
private long streamNumber; private long streamNumber;
private ObjectPayload payload; private ObjectPayload payload;
@ -138,13 +138,15 @@ public class ObjectMessage implements MessagePayload {
return this; return this;
} }
public Builder streamNumber(long streamNumber) { public Builder stream(long streamNumber) {
this.streamNumber = streamNumber; this.streamNumber = streamNumber;
return this; return this;
} }
public Builder payload(ObjectPayload payload) { public Builder payload(ObjectPayload payload) {
this.payload = payload; this.payload = payload;
if (this.objectType == -1)
this.objectType = payload.getType().getNumber();
return this; return this;
} }

View File

@ -39,6 +39,11 @@ public class GenericPayload implements ObjectPayload {
return new GenericPayload(stream, Decode.bytes(is, length)); return new GenericPayload(stream, Decode.bytes(is, length));
} }
@Override
public ObjectType getType() {
return null;
}
@Override @Override
public long getStream() { public long getStream() {
return stream; return stream;

View File

@ -16,6 +16,8 @@
package ch.dissem.bitmessage.entity.payload; package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Decode; import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException; import java.io.IOException;
@ -30,22 +32,30 @@ public class GetPubkey implements ObjectPayload {
private byte[] ripe; private byte[] ripe;
private byte[] tag; 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; this.stream = stream;
switch (ripeOrTag.length) { if (version < 4) {
case 20:
ripe = ripeOrTag; ripe = ripeOrTag;
break; } else {
case 32:
tag = ripeOrTag; tag = ripeOrTag;
break;
default:
throw new RuntimeException("ripe (20 bytes) or tag (32 bytes) expected, but pubkey was " + ripeOrTag.length + " bytes long.");
} }
} }
public static GetPubkey read(InputStream is, long stream, int length) throws IOException { public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException {
return new GetPubkey(stream, Decode.bytes(is, length)); return new GetPubkey(stream, version, Decode.bytes(is, length));
}
@Override
public ObjectType getType() {
return ObjectType.GET_PUBKEY;
} }
@Override @Override

View File

@ -44,6 +44,11 @@ public class Msg implements ObjectPayload {
return new Msg(stream, Decode.bytes(is, length)); return new Msg(stream, Decode.bytes(is, length));
} }
@Override
public ObjectType getType() {
return ObjectType.MSG;
}
@Override @Override
public long getStream() { public long getStream() {
return stream; return stream;

View File

@ -22,5 +22,7 @@ import ch.dissem.bitmessage.entity.Streamable;
* The payload of an 'object' command. This is shared by the network. * The payload of an 'object' command. This is shared by the network.
*/ */
public interface ObjectPayload extends Streamable { public interface ObjectPayload extends Streamable {
ObjectType getType();
long getStream(); long getStream();
} }

View File

@ -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;
}
}

View File

@ -56,6 +56,11 @@ public class V2Pubkey extends Pubkey {
return 2; return 2;
} }
@Override
public ObjectType getType() {
return ObjectType.PUBKEY;
}
@Override @Override
public long getStream() { public long getStream() {
return stream; return stream;

View File

@ -40,6 +40,11 @@ public class V4Broadcast implements Broadcast {
return new V4Broadcast(stream, Decode.bytes(is, length)); return new V4Broadcast(stream, Decode.bytes(is, length));
} }
@Override
public ObjectType getType() {
return ObjectType.BROADCAST;
}
@Override @Override
public long getStream() { public long getStream() {
return stream; return stream;

View File

@ -62,6 +62,11 @@ public class V4Pubkey extends Pubkey {
return 4; return 4;
} }
@Override
public ObjectType getType() {
return ObjectType.PUBKEY;
}
@Override @Override
public long getStream() { public long getStream() {
return stream; return stream;

View File

@ -103,7 +103,7 @@ public class NetworkAddress implements Streamable {
@Override @Override
public String toString() { public String toString() {
return toInetAddress() + ":" + port; return "[" + toInetAddress() + "]:" + port;
} }
@Override @Override

View File

@ -101,27 +101,28 @@ public class Factory {
} }
static ObjectPayload getObjectPayload(long objectType, long version, long streamNumber, InputStream stream, int length) throws IOException { static ObjectPayload getObjectPayload(long objectType, long version, long streamNumber, InputStream stream, int length) throws IOException {
if (objectType < 4) { ObjectType type = ObjectType.fromNumber(objectType);
switch ((int) objectType) { if (type != null) {
case 0: switch (type) {
case GET_PUBKEY:
return parseGetPubkey(version, streamNumber, stream, length); return parseGetPubkey(version, streamNumber, stream, length);
case 1: case PUBKEY:
return parsePubkey(version, streamNumber, stream, length); return parsePubkey(version, streamNumber, stream, length);
case 2: case MSG:
return parseMsg(version, streamNumber, stream, length); return parseMsg(version, streamNumber, stream, length);
case 3: case BROADCAST:
return parseBroadcast(version, streamNumber, stream, length); return parseBroadcast(version, streamNumber, stream, length);
default: default:
LOG.error("This should not happen, someone broke something in the code!"); 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 // 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); return GenericPayload.read(stream, streamNumber, length);
} }
private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { 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 { public static Pubkey readPubkey(long version, long stream, InputStream is, int length) throws IOException {

View File

@ -46,7 +46,10 @@ class V3MessageFactory {
if (testChecksum(checksum, payloadBytes)) { if (testChecksum(checksum, payloadBytes)) {
MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length); MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length);
if (payload != null)
return new NetworkMessage(payload); return new NetworkMessage(payload);
else
return null;
} else { } else {
throw new IOException("Checksum failed for message '" + command + "'"); throw new IOException("Checksum failed for message '" + command + "'");
} }
@ -84,14 +87,14 @@ class V3MessageFactory {
long version = Decode.varInt(stream, counter); long version = Decode.varInt(stream, counter);
long streamNumber = 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() return new ObjectMessage.Builder()
.nonce(nonce) .nonce(nonce)
.expiresTime(expiresTime) .expiresTime(expiresTime)
.objectType(objectType) .objectType(objectType)
.version(version) .version(version)
.streamNumber(streamNumber) .stream(streamNumber)
.payload(payload) .payload(payload)
.build(); .build();
} }

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.ports; package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import java.util.List; import java.util.List;
@ -31,6 +32,8 @@ public interface Inventory {
ObjectMessage getObject(InventoryVector vector); ObjectMessage getObject(InventoryVector vector);
List<ObjectMessage> getObjects(long stream, long version, ObjectType type);
void storeObject(ObjectMessage object); void storeObject(ObjectMessage object);
void cleanup(); void cleanup();

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.payload.ObjectPayload; import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
/** /**
* Handles incoming messages * Handles incoming messages
@ -27,7 +28,7 @@ public interface NetworkHandler {
void stop(); void stop();
void send(ObjectPayload payload); void offer(InventoryVector iv);
interface MessageListener { interface MessageListener {
void receive(ObjectPayload payload); void receive(ObjectPayload payload);

View File

@ -80,6 +80,9 @@ public class Security {
} }
public static void doProofOfWork(ObjectMessage object, ProofOfWorkEngine worker, long nonceTrialsPerByte, long extraBytes) throws IOException { 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[] initialHash = getInitialHash(object);
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);

View File

@ -20,7 +20,7 @@ package ch.dissem.bitmessage.utils;
* Created by chris on 13.04.15. * Created by chris on 13.04.15.
*/ */
public class Strings { public class Strings {
public static CharSequence join(byte[]... objects) { public static StringBuilder join(byte[]... objects) {
StringBuilder streamList = new StringBuilder(); StringBuilder streamList = new StringBuilder();
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {
if (i > 0) streamList.append(", "); if (i > 0) streamList.append(", ");
@ -29,7 +29,7 @@ public class Strings {
return streamList; return streamList;
} }
public static CharSequence join(long... objects) { public static StringBuilder join(long... objects) {
StringBuilder streamList = new StringBuilder(); StringBuilder streamList = new StringBuilder();
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {
if (i > 0) streamList.append(", "); if (i > 0) streamList.append(", ");
@ -38,7 +38,7 @@ public class Strings {
return streamList; return streamList;
} }
public static CharSequence join(Object... objects) { public static StringBuilder join(Object... objects) {
StringBuilder streamList = new StringBuilder(); StringBuilder streamList = new StringBuilder();
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {
if (i > 0) streamList.append(", "); if (i > 0) streamList.append(", ");
@ -47,9 +47,8 @@ public class Strings {
return streamList; return streamList;
} }
public static CharSequence hex(byte[] bytes) { public static StringBuilder hex(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.length + 2); StringBuilder hex = new StringBuilder(bytes.length + 2);
hex.append("0x");
for (byte b : bytes) { for (byte b : bytes) {
hex.append(String.format("%02x", b)); hex.append(String.format("%02x", b));
} }

View File

@ -26,4 +26,8 @@ public class UnixTime {
public static long now() { public static long now() {
return System.currentTimeMillis() / 1000; return System.currentTimeMillis() / 1000;
} }
public static long now(long shiftSeconds) {
return (System.currentTimeMillis() / 1000) + shiftSeconds;
}
} }

View File

@ -16,14 +16,14 @@
package ch.dissem.bitmessage.entity; package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.Base58; import ch.dissem.bitmessage.utils.*;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Security;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals; import java.io.IOException;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.*;
public class BitmessageAddressTest { public class BitmessageAddressTest {
@Test @Test
@ -49,24 +49,59 @@ public class BitmessageAddressTest {
assertNotNull(address.getPubkey()); 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 @Test
public void testV3Import() { public void testV3Import() {
assertEquals(3, new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn").getVersion()); String address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn";
assertEquals(1, new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn").getStream()); assertEquals(3, new BitmessageAddress(address_string).getVersion());
assertEquals(1, new BitmessageAddress(address_string).getStream());
byte[] privsigningkey = Base58.decode("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9"); byte[] privsigningkey = getSecret("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9");
byte[] privencryptionkey = Base58.decode("5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck"); byte[] privencryptionkey = getSecret("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);
privsigningkey = Bytes.expand(privsigningkey, 32); System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n");
privencryptionkey = Bytes.expand(privencryptionkey, 32);
// privsigningkey = Bytes.expand(privsigningkey, 32);
// privencryptionkey = Bytes.expand(privencryptionkey, 32);
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey, BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); 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 @Test

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.*; import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -69,7 +70,7 @@ public class SerializationTest {
} }
private void doTest(String resourceName, int version, Class<?> expectedPayloadType) throws IOException { 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); InputStream in = new ByteArrayInputStream(data);
ObjectMessage object = Factory.getObjectMessage(version, in, data.length); ObjectMessage object = Factory.getObjectMessage(version, in, data.length);
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
@ -77,16 +78,4 @@ public class SerializationTest {
assertArrayEquals(data, out.toByteArray()); assertArrayEquals(data, out.toByteArray());
assertEquals(expectedPayloadType.getCanonicalName(), object.getPayload().getClass().getCanonicalName()); 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();
}
} }

View File

@ -32,7 +32,14 @@ public class BytesTest {
public static final Random rnd = new Random(); public static final Random rnd = new Random();
@Test @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}; byte[] bytes = {0, -1};
Bytes.inc(bytes); Bytes.inc(bytes);
assertArrayEquals(TestUtils.int16(256), bytes); assertArrayEquals(TestUtils.int16(256), bytes);

View File

@ -29,6 +29,7 @@ public class StringsTest {
@Test @Test
public void testHexString() { 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());
} }
} }

View File

@ -16,8 +16,13 @@
package ch.dissem.bitmessage.utils; 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.ByteArrayOutputStream;
import java.io.IOException; 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. * 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); Encode.int16(number, out);
return out.toByteArray(); 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();
}
} }

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.inventory; package ch.dissem.bitmessage.inventory;
import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.Inventory; import ch.dissem.bitmessage.ports.Inventory;
@ -62,9 +63,41 @@ public class JdbcInventory extends JdbcHelper implements Inventory {
public ObjectMessage getObject(InventoryVector vector) { public ObjectMessage getObject(InventoryVector vector) {
try { try {
Statement stmt = getConnection().createStatement(); Statement stmt = getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = " + vector); ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'");
if (rs.next()) {
Blob data = rs.getBlob("data"); Blob data = rs.getBlob("data");
return Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()); 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<ObjectMessage> 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<ObjectMessage> 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) { } catch (Exception e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.inventory; package ch.dissem.bitmessage.inventory;
import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.ports.Inventory; import ch.dissem.bitmessage.ports.Inventory;
import sun.reflect.generics.reflectiveObjects.NotImplementedException; import sun.reflect.generics.reflectiveObjects.NotImplementedException;
@ -43,6 +44,11 @@ public class SimpleInventory implements Inventory {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@Override
public List<ObjectMessage> getObjects(long stream, long version, ObjectType type) {
return new LinkedList<>();
}
@Override @Override
public void storeObject(ObjectMessage object) { public void storeObject(ObjectMessage object) {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -91,6 +91,7 @@ public class Connection implements Runnable {
switch (state) { switch (state) {
case ACTIVE: case ACTIVE:
receiveMessage(msg.getPayload()); receiveMessage(msg.getPayload());
sendQueue();
break; break;
default: default:
@ -121,13 +122,18 @@ public class Connection implements Runnable {
} }
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
if (state == ACTIVE) { if (state == ACTIVE) {
sendQueue();
}
}
}
}
private void sendQueue() {
LOG.debug("Sending " + sendingQueue.size() + " messages to node " + node);
for (MessagePayload msg = sendingQueue.poll(); msg != null; msg = sendingQueue.poll()) { for (MessagePayload msg = sendingQueue.poll(); msg != null; msg = sendingQueue.poll()) {
send(msg); send(msg);
} }
} }
}
}
}
private void receiveMessage(MessagePayload messagePayload) { private void receiveMessage(MessagePayload messagePayload) {
switch (messagePayload.getCommand()) { switch (messagePayload.getCommand()) {
@ -140,11 +146,10 @@ public class Connection implements Runnable {
break; break;
case GETDATA: case GETDATA:
GetData getData = (GetData) messagePayload; GetData getData = (GetData) messagePayload;
// for (InventoryVector iv : getData.getInventory()) { for (InventoryVector iv : getData.getInventory()) {
// ObjectMessage om = ctx.getInventory().getObject(iv); ObjectMessage om = ctx.getInventory().getObject(iv);
// sendingQueue.offer(om); if (om != null) sendingQueue.offer(om);
// } }
LOG.error("Node requests data!!!! This shouldn't happen, the hash is done wrong!!!");
break; break;
case OBJECT: case OBJECT:
ObjectMessage objectMessage = (ObjectMessage) messagePayload; 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} public enum State {SERVER, CLIENT, ACTIVE, DISCONNECTED}
} }

View File

@ -18,7 +18,7 @@ package ch.dissem.bitmessage.networking;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.BitmessageContext.ContextHolder; 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.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.ports.NetworkHandler; import ch.dissem.bitmessage.ports.NetworkHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -40,9 +40,9 @@ import static ch.dissem.bitmessage.networking.Connection.State.*;
*/ */
public class NetworkNode implements NetworkHandler, ContextHolder { public class NetworkNode implements NetworkHandler, ContextHolder {
private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class); private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class);
private BitmessageContext ctx;
private final ExecutorService pool; private final ExecutorService pool;
private final List<Connection> connections = new LinkedList<>(); private final List<Connection> connections = new LinkedList<>();
private BitmessageContext ctx;
private ServerSocket serverSocket; private ServerSocket serverSocket;
private Thread connectionManager; private Thread connectionManager;
@ -137,7 +137,15 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
} }
@Override @Override
public void send(final ObjectPayload payload) { public void offer(final InventoryVector iv) {
// TODO: sendingQueue.add(message); // 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);
}
}
} }
} }