It's now possible to send a 'version' message that will be accepted by the other node.

This commit is contained in:
2015-03-31 21:06:42 +02:00
parent c2624dcd15
commit 0c4b39bdee
31 changed files with 1351 additions and 45 deletions

View File

@ -25,9 +25,9 @@ import java.util.ArrayList;
import java.util.List;
/**
* Created by chris on 13.03.15.
* The 'addr' command holds a list of known active Bitmessage nodes.
*/
public class Addr implements MessagePayload {
public class Addr implements Command {
private final List<NetworkAddress> addresses;
private Addr(Builder builder) {

View File

@ -0,0 +1,71 @@
/*
* 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.utils.Base58;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static ch.dissem.bitmessage.utils.Security.ripemd160;
import static ch.dissem.bitmessage.utils.Security.sha512;
/**
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
* holding private keys.
*/
public abstract class BitmessageAddress {
private long version;
private long streamNumber;
private Pubkey pubkey;
public BitmessageAddress(Pubkey pubkey) {
this.pubkey = pubkey;
}
public BitmessageAddress(String address) {
Base58.decode(address.substring(3));
}
@Override
public String toString() {
try {
byte[] combinedKeys = new byte[pubkey.getSigningKey().length + pubkey.getEncryptionKey().length];
System.arraycopy(pubkey.getSigningKey(), 0, combinedKeys, 0, pubkey.getSigningKey().length);
System.arraycopy(pubkey.getEncryptionKey(), 0, combinedKeys, pubkey.getSigningKey().length, pubkey.getEncryptionKey().length);
byte[] hash = ripemd160(sha512(combinedKeys));
ByteArrayOutputStream stream = new ByteArrayOutputStream();
Encode.varInt(version, stream);
Encode.varInt(streamNumber, stream);
stream.write(hash);
byte[] checksum = Security.doubleSha512(stream.toByteArray());
for (int i = 0; i < 4; i++) {
stream.write(checksum[i]);
}
return "BM-" + Base58.encode(stream.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -17,8 +17,8 @@
package ch.dissem.bitmessage.entity;
/**
* Created by chris on 10.03.15.
* A command can hold a network message payload
*/
public interface MessagePayload extends Streamable {
public interface Command extends Streamable {
String getCommand();
}

View File

@ -0,0 +1,70 @@
/*
* 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.valueobject.InventoryVector;
import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
/**
* The 'getdata' command is used to request objects from a node.
*/
public class GetData implements Command {
List<InventoryVector> inventory;
private GetData(Builder builder) {
inventory = builder.inventory;
}
@Override
public String getCommand() {
return "getdata";
}
@Override
public void write(OutputStream stream) throws IOException {
Encode.varInt(inventory.size(), stream);
for (InventoryVector iv : inventory) {
iv.write(stream);
}
}
public static final class Builder {
private List<InventoryVector> inventory = new LinkedList<>();
public Builder() {
}
public Builder addInventoryVector(InventoryVector inventoryVector) {
this.inventory.add(inventoryVector);
return this;
}
public Builder inventory(List<InventoryVector> inventory) {
this.inventory = inventory;
return this;
}
public GetData build() {
return new GetData(this);
}
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.valueobject.InventoryVector;
import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
/**
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
*/
public class Inv implements Command {
private List<InventoryVector> inventory;
private Inv(Builder builder) {
inventory = builder.inventory;
}
@Override
public String getCommand() {
return "inv";
}
@Override
public void write(OutputStream stream) throws IOException {
Encode.varInt(inventory.size(), stream);
for (InventoryVector iv : inventory) {
iv.write(stream);
}
}
public static final class Builder {
private List<InventoryVector> inventory = new LinkedList<>();
public Builder() {
}
public Builder addInventoryVector(InventoryVector inventoryVector) {
this.inventory.add(inventoryVector);
return this;
}
public Builder inventory(List<InventoryVector> inventory) {
this.inventory = inventory;
return this;
}
public Inv build() {
return new Inv(this);
}
}
}

View File

@ -16,28 +16,35 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.utils.Encode;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import static ch.dissem.bitmessage.utils.Security.sha512;
/**
* Created by chris on 10.03.15.
* A network message is exchanged between two nodes.
*/
public class NetworkMessage implements Streamable {
/**
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown
*/
private final static int MAGIC = 0xE9BEB4D9;
public final static int MAGIC = 0xE9BEB4D9;
public final static byte[] MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array();
private MessagePayload payload;
private final NetworkAddress targetNode;
public NetworkMessage(MessagePayload payload) {
private final Command payload;
public NetworkMessage(NetworkAddress target, Command payload) {
this.targetNode = target;
this.payload = payload;
}
@ -45,18 +52,21 @@ public class NetworkMessage implements Streamable {
* First 4 bytes of sha512(payload)
*/
private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
MessageDigest mda = MessageDigest.getInstance("SHA-512");
byte[] d = mda.digest(bytes);
byte[] d = sha512(bytes);
return new byte[]{d[0], d[1], d[2], d[3]};
}
/**
* The actual data, a message or an object. Not to be confused with objectPayload.
*/
public MessagePayload getPayload() {
public Command getPayload() {
return payload;
}
public NetworkAddress getTargetNode() {
return targetNode;
}
@Override
public void write(OutputStream stream) throws IOException {
// magic

View File

@ -0,0 +1,109 @@
/*
* 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.ObjectPayload;
import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.OutputStream;
/**
* The 'object' command sends an object that is shared throughout the network.
*/
public class ObjectMessage implements Command {
private long nonce;
private long expiresTime;
private long objectType;
/**
* The object's version
*/
private long version;
private long streamNumber;
private ObjectPayload payload;
private ObjectMessage(Builder builder) {
nonce = builder.nonce;
expiresTime = builder.expiresTime;
objectType = builder.objectType;
version = builder.version;
streamNumber = builder.streamNumber;
payload = builder.payload;
}
@Override
public String getCommand() {
return "object";
}
@Override
public void write(OutputStream stream) throws IOException {
Encode.int64(nonce, stream);
Encode.int64(expiresTime, stream);
Encode.int32(objectType, stream);
Encode.varInt(version, stream);
Encode.varInt(streamNumber, stream);
payload.write(stream);
}
public static final class Builder {
private long nonce;
private long expiresTime;
private long objectType;
private long version;
private long streamNumber;
private ObjectPayload payload;
public Builder() {
}
public Builder nonce(long nonce) {
this.nonce = nonce;
return this;
}
public Builder expiresTime(long expiresTime) {
this.expiresTime = expiresTime;
return this;
}
public Builder objectType(long objectType) {
this.objectType = objectType;
return this;
}
public Builder version(long version) {
this.version = version;
return this;
}
public Builder streamNumber(long streamNumber) {
this.streamNumber = streamNumber;
return this;
}
public Builder payload(ObjectPayload payload) {
this.payload = payload;
return this;
}
public ObjectMessage build() {
return new ObjectMessage(this);
}
}
}

View File

@ -20,7 +20,7 @@ import java.io.IOException;
import java.io.OutputStream;
/**
* Created by chris on 10.03.15.
* An object that can be written to an {@link OutputStream}
*/
public interface Streamable {
void write(OutputStream stream) throws IOException;

View File

@ -20,9 +20,9 @@ import java.io.IOException;
import java.io.OutputStream;
/**
* Created by chris on 10.03.15.
* The 'verack' command answers a 'version' command, accepting the other node's version.
*/
public class VerAck implements MessagePayload {
public class VerAck implements Command {
@Override
public String getCommand() {
return "verack";
@ -30,6 +30,6 @@ public class VerAck implements MessagePayload {
@Override
public void write(OutputStream stream) throws IOException {
// NO OP
// 'verack' doesn't have any payload, so there is nothing to write
}
}

View File

@ -24,9 +24,10 @@ import java.io.OutputStream;
import java.util.Random;
/**
* Created by chris on 10.03.15.
* The 'version' command advertises this node's latest supported protocol version upon initiation.
*/
public class Version implements MessagePayload {
public class Version implements Command {
public static final int CURRENT = 3;
/**
* Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's
* version is lower but continue with the connection if it is higher.
@ -115,7 +116,7 @@ public class Version implements MessagePayload {
@Override
public String getCommand() {
return "ver";
return "version";
}
@Override
@ -123,8 +124,8 @@ public class Version implements MessagePayload {
Encode.int32(version, stream);
Encode.int64(services, stream);
Encode.int64(timestamp, stream);
addrRecv.write(stream);
addrFrom.write(stream);
addrRecv.write(stream, true);
addrFrom.write(stream, true);
Encode.int64(nonce, stream);
Encode.varString(userAgent, stream);
Encode.varIntList(streamNumbers, stream);
@ -132,18 +133,28 @@ public class Version implements MessagePayload {
public static final class Builder {
private int version = 3;
private long services = 1; // This is a normal network node
private long timestamp = System.currentTimeMillis() / 1000;
private int version;
private long services;
private long timestamp;
private NetworkAddress addrRecv;
private NetworkAddress addrFrom;
private long nonce = new Random().nextInt();
private String userAgent = "/Jabit:0.0.1/";
private long[] streamNumbers = {1};
private long nonce;
private String userAgent;
private long[] streamNumbers;
public Builder() {
}
public Builder defaults() {
version = CURRENT;
services = 1;
timestamp = System.currentTimeMillis() / 1000;
nonce = new Random().nextInt();
userAgent = "/Jabit:0.0.1/";
streamNumbers = new long[]{1};
return this;
}
public Builder version(int version) {
this.version = version;
return this;
@ -179,7 +190,7 @@ public class Version implements MessagePayload {
return this;
}
public Builder streamNumbers(long... streamNumbers) {
public Builder streams(long... streamNumbers) {
this.streamNumbers = streamNumbers;
return this;
}

View File

@ -0,0 +1,36 @@
/*
* 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 java.io.IOException;
import java.io.OutputStream;
/**
* Created by chris on 24.03.15.
*/
public class GenericPayload implements ObjectPayload {
private byte[] data;
public GenericPayload(byte[] data) {
this.data = data;
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(data);
}
}

View File

@ -0,0 +1,50 @@
/*
* 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 java.io.IOException;
import java.io.OutputStream;
/**
* Created by chris on 24.03.15.
*/
public class GetPubkey implements ObjectPayload {
private byte[] ripe;
private byte[] tag;
public GetPubkey(byte[] ripeOrTag) {
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.");
}
}
@Override
public void write(OutputStream stream) throws IOException {
if (tag != null) {
stream.write(tag);
} else {
stream.write(ripe);
}
}
}

View File

@ -14,10 +14,12 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.entity;
package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.Streamable;
/**
* Created by chris on 16.03.15.
* The payload of an 'object' command. This is shared by the network.
*/
public interface ObjectPayload {
public interface ObjectPayload extends Streamable {
}

View File

@ -0,0 +1,30 @@
/*
* 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;
/**
* Created by chris on 24.03.15.
*/
public interface Pubkey extends ObjectPayload {
long getVersion();
long getStream();
byte[] getSigningKey();
byte[] getEncryptionKey();
}

View File

@ -0,0 +1,59 @@
/*
* 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.utils.Encode;
import java.io.IOException;
import java.io.OutputStream;
/**
* Created by chris on 24.03.15.
*/
public class V2Pubkey implements Pubkey {
protected long streamNumber;
protected long behaviorBitfield;
protected byte[] publicSigningKey;
protected byte[] publicEncryptionKey;
@Override
public long getVersion() {
return 2;
}
@Override
public long getStream() {
return streamNumber;
}
@Override
public byte[] getSigningKey() {
return publicSigningKey;
}
@Override
public byte[] getEncryptionKey() {
return publicEncryptionKey;
}
@Override
public void write(OutputStream stream) throws IOException {
Encode.int32(behaviorBitfield, stream);
stream.write(publicSigningKey);
stream.write(publicEncryptionKey);
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.utils.Encode;
import java.io.IOException;
import java.io.OutputStream;
/**
* Created by chris on 27.03.15.
*/
public class V3Pubkey extends V2Pubkey {
long nonceTrialsPerByte;
long extraBytes;
byte[] signature;
@Override
public void write(OutputStream stream) throws IOException {
super.write(stream);
Encode.varInt(nonceTrialsPerByte, stream);
Encode.varInt(extraBytes, stream);
Encode.varInt(signature.length, stream);
stream.write(signature);
}
@Override
public long getVersion() {
return 3;
}
}

View File

@ -0,0 +1,62 @@
/*
* 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 java.io.IOException;
import java.io.OutputStream;
/**
* Created by chris on 27.03.15.
*/
public class V4Pubkey implements Pubkey {
private long streamNumber;
private byte[] tag;
private byte[] encrypted;
private V3Pubkey decrypted;
public V4Pubkey(V3Pubkey decrypted) {
this.decrypted = decrypted;
// TODO: this.tag = new BitmessageAddress(this).doubleHash
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(tag);
stream.write(encrypted);
}
@Override
public long getVersion() {
return 4;
}
@Override
public long getStream() {
return streamNumber;
}
@Override
public byte[] getSigningKey() {
return decrypted.getSigningKey();
}
@Override
public byte[] getEncryptionKey() {
return decrypted.getEncryptionKey();
}
}

View File

@ -21,13 +21,15 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Objects;
/**
* Created by chris on 10.03.15.
* A node's address. It's written in IPv6 format.
*/
public class NetworkAddress implements Streamable {
private long time;
@ -35,7 +37,7 @@ public class NetworkAddress implements Streamable {
/**
* Stream number for this node
*/
private int stream;
private long stream;
/**
* same service(s) listed in version
@ -88,8 +90,14 @@ public class NetworkAddress implements Streamable {
@Override
public void write(OutputStream stream) throws IOException {
Encode.int64(time, stream);
Encode.int32(this.stream, stream);
write(stream, false);
}
public void write(OutputStream stream, boolean light) throws IOException {
if (!light) {
Encode.int64(time, stream);
Encode.int32(this.stream, stream);
}
Encode.int64(services, stream);
stream.write(ipv6);
Encode.int16(port, stream);
@ -97,7 +105,7 @@ public class NetworkAddress implements Streamable {
public static final class Builder {
private long time;
private int stream;
private long stream;
private long services = 1;
private byte[] ipv6;
private int port;
@ -110,7 +118,7 @@ public class NetworkAddress implements Streamable {
return this;
}
public Builder stream(final int stream) {
public Builder stream(final long stream) {
this.stream = stream;
return this;
}
@ -120,6 +128,24 @@ public class NetworkAddress implements Streamable {
return this;
}
public Builder ip(InetAddress inetAddress) {
byte[] addr = inetAddress.getAddress();
if (addr.length == 16) {
this.ipv6 = addr;
} else if (addr.length == 4) {
this.ipv6 = new byte[16];
System.arraycopy(addr, 0, this.ipv6, 12, 4);
} else {
throw new IllegalArgumentException("Weird address " + inetAddress);
}
return this;
}
public Builder ipv6(byte[] ipv6) {
this.ipv6 = ipv6;
return this;
}
public Builder ipv6(int p00, int p01, int p02, int p03,
int p04, int p05, int p06, int p07,
int p08, int p09, int p10, int p11,

View File

@ -0,0 +1,54 @@
/*
* 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.factory;
import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
/**
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
*/
public class Factory {
public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws IOException {
return new V3MessageFactory().read(stream);
}
static ObjectPayload getObjectPayload(long objectType, long version, InputStream stream, int length) throws IOException {
if (objectType < 4) {
switch ((int) objectType) {
case 0: // getpubkey
return new GetPubkey(Decode.bytes(stream, length));
case 1: // pubkey
break;
case 2: // msg
break;
case 3: // broadcast
break;
}
throw new RuntimeException("This must not happen, someone broke something in the code!");
} else {
// passthrough message
return new GenericPayload(Decode.bytes(stream, length));
}
}
}

View File

@ -0,0 +1,172 @@
/*
* 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.factory;
import ch.dissem.bitmessage.entity.*;
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.utils.Decode;
import ch.dissem.bitmessage.utils.Security;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Creates protocol v3 network messages from {@link InputStream InputStreams}
*/
class V3MessageFactory {
public NetworkMessage read(InputStream stream) throws IOException {
if (testMagic(stream)) {
String command = getCommand(stream);
int length = (int) Decode.uint32(stream);
byte[] checksum = Decode.bytes(stream, 4);
byte[] payloadBytes = Decode.bytes(stream, length);
if (testChecksum(checksum, payloadBytes)) {
Command payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length);
return new NetworkMessage(payload);
} else {
throw new IOException("Checksum failed for message '" + command + "'");
}
}
return null;
}
private Command getPayload(String command, InputStream stream, int length) throws IOException {
switch (command) {
case "version":
return parseVersion(stream);
case "verack":
return new VerAck();
case "addr":
return parseAddr(stream);
case "inv":
return parseInv(stream);
case "getdata":
return parseGetData(stream);
case "object":
return parseObject(stream, length);
default:
return null;
}
}
private ObjectMessage parseObject(InputStream stream, int length) throws IOException {
long nonce = Decode.int64(stream);
long expiresTime = Decode.int64(stream);
long objectType = Decode.uint32(stream);
long version = Decode.varInt(stream);
long streamNumber = Decode.varInt(stream);
ObjectPayload payload = Factory.getObjectPayload(objectType, version, stream, length);
return new ObjectMessage.Builder()
.nonce(nonce)
.expiresTime(expiresTime)
.objectType(objectType)
.version(version)
.streamNumber(streamNumber)
.payload(payload)
.build();
}
private GetData parseGetData(InputStream stream) throws IOException {
long count = Decode.varInt(stream);
GetData.Builder builder = new GetData.Builder();
for (int i = 0; i < count; i++) {
builder.addInventoryVector(parseInventoryVector(stream));
}
return builder.build();
}
private Inv parseInv(InputStream stream) throws IOException {
long count = Decode.varInt(stream);
Inv.Builder builder = new Inv.Builder();
for (int i = 0; i < count; i++) {
builder.addInventoryVector(parseInventoryVector(stream));
}
return builder.build();
}
private Addr parseAddr(InputStream stream) throws IOException {
long count = Decode.varInt(stream);
Addr.Builder builder = new Addr.Builder();
for (int i = 0; i < count; i++) {
builder.addAddress(parseAddress(stream));
}
return builder.build();
}
private Version parseVersion(InputStream stream) throws IOException {
int version = Decode.int32(stream);
long services = Decode.int64(stream);
long timestamp = Decode.int64(stream);
NetworkAddress addrRecv = parseAddress(stream);
NetworkAddress addrFrom = parseAddress(stream);
long nonce = Decode.int64(stream);
String userAgent = Decode.varString(stream);
long[] streamNumbers = Decode.varIntList(stream);
return new Version.Builder()
.version(version)
.services(services)
.timestamp(timestamp)
.addrRecv(addrRecv).addrFrom(addrFrom)
.nonce(nonce)
.userAgent(userAgent)
.streams(streamNumbers).build();
}
private InventoryVector parseInventoryVector(InputStream stream) throws IOException {
return new InventoryVector(Decode.bytes(stream, 32));
}
private NetworkAddress parseAddress(InputStream stream) throws IOException {
long time = Decode.int64(stream);
long streamNumber = Decode.uint32(stream); // This isn't consistent, not sure if this is correct
long services = Decode.int64(stream);
byte[] ipv6 = Decode.bytes(stream, 16);
int port = Decode.uint16(stream);
return new NetworkAddress.Builder().time(time).stream(streamNumber).services(services).ipv6(ipv6).port(port).build();
}
private boolean testChecksum(byte[] checksum, byte[] payload) {
byte[] payloadChecksum = Security.sha512(payload);
for (int i = 0; i < checksum.length; i++) {
if (checksum[i] != payloadChecksum[i]) {
return false;
}
}
return true;
}
private String getCommand(InputStream stream) throws IOException {
byte[] bytes = new byte[12];
stream.read(bytes);
return new String(bytes, "ASCII");
}
private boolean testMagic(InputStream stream) throws IOException {
for (byte b : NetworkMessage.MAGIC_BYTES) {
if (b != stream.read()) return false;
}
return true;
}
}

View File

@ -16,13 +16,13 @@
package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.entity.ObjectPayload;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import java.util.List;
/**
* Created by chris on 16.03.15.
* The Inventory stores and retrieves objects, cleans up outdated objects and can tell which objects are still missing.
*/
public interface Inventory {
public List<InventoryVector> getInventory();

View File

@ -19,13 +19,15 @@ package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import java.io.IOException;
/**
* Created by chris on 16.03.15.
* Handles incoming messages
*/
public interface NetworkMessageReceiver {
public void registerListener(int port);
public void registerListener(int port) throws IOException;
public void registerListener(NetworkAddress node, MessageListener listener);
public void registerListener(NetworkAddress node, MessageListener listener) throws IOException;
public static interface MessageListener {
public void receive(NetworkMessage message);

View File

@ -20,7 +20,7 @@ import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
/**
* Created by chris on 16.03.15.
* Sends messages
*/
public interface NetworkMessageSender {
public void send(NetworkAddress node, NetworkMessage message);

View File

@ -0,0 +1,32 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
/**
* Base58 encoder and decoder
*/
public class Base58 {
private static char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
public static String encode(byte[] input) {
return null; // TODO
}
public static byte[] decode(String input) {
return null; // TODO
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* This class handles decoding simple types from byte stream, according to
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
*/
public class Decode {
public static byte[] bytes(InputStream stream, int count) throws IOException {
byte[] result = new byte[count];
stream.read(result);
return result;
}
public static long[] varIntList(InputStream stream) throws IOException {
int length = (int) varInt(stream);
long[] result = new long[length];
for (int i = 0; i < length; i++) {
result[i] = varInt(stream);
}
return result;
}
public static long varInt(InputStream stream) throws IOException {
int first = stream.read();
switch (first) {
case 0xfd:
return uint16(stream);
case 0xfe:
return uint32(stream);
case 0xff:
return int64(stream);
default:
return first;
}
}
public static int uint8(InputStream stream) throws IOException {
return stream.read();
}
public static int uint16(InputStream stream) throws IOException {
return stream.read() * 256 + stream.read();
}
public static long uint32(InputStream stream) throws IOException {
return stream.read() * 16777216L + stream.read() * 65536L + stream.read() * 256L + stream.read();
}
public static int int32(InputStream stream) throws IOException {
return ByteBuffer.wrap(bytes(stream, 4)).getInt();
}
public static long int64(InputStream stream) throws IOException {
return ByteBuffer.wrap(bytes(stream, 8)).getLong();
}
public static String varString(InputStream stream) throws IOException {
int length = (int) varInt(stream);
// FIXME: technically, it says the length in characters, but I think this one might be correct
byte[] bytes = new byte[length];
// FIXME: I'm also not quite sure if this works, maybe the read return value needs to be handled properly
stream.read(bytes);
return new String(bytes, "utf-8");
}
}

View File

@ -21,7 +21,8 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Created by chris on 13.03.15.
* This class handles encoding simple types from byte stream, according to
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
*/
public class Encode {
public static void varIntList(long[] values, OutputStream stream) throws IOException {
@ -71,6 +72,7 @@ public class Encode {
public static void varString(String value, OutputStream stream) throws IOException {
byte[] bytes = value.getBytes("utf-8");
// FIXME: technically, it says the length in characters, but I think this one might be correct
varInt(bytes.length, stream);
stream.write(bytes);
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Provides some methods to help with hashing and encryption.
*/
public class Security {
public static byte[] sha512(byte[] data) {
try {
MessageDigest mda = MessageDigest.getInstance("SHA-512");
return mda.digest(data);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static byte[] doubleSha512(byte[] data) {
try {
MessageDigest mda = MessageDigest.getInstance("SHA-512");
return mda.digest(mda.digest(data));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static byte[] ripemd160(byte[] data) {
try {
MessageDigest mda = MessageDigest.getInstance("RIPEMD-160");
return mda.digest(data);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
import org.junit.Test;
import java.io.*;
import static org.junit.Assert.assertEquals;
/**
* Created by chris on 20.03.15.
*/
public class DecodeTest {
@Test
public void ensureDecodingWorks() throws Exception {
// This should test all relevant cases for var_int and therefore also uint_16, uint_32 and int_64
testCodec(0);
for (long i = 1; i > 0; i = 3 * i + 7) {
testCodec(i);
}
}
private void testCodec(long number) throws IOException {
ByteArrayOutputStream is = new ByteArrayOutputStream();
Encode.varInt(number, is);
assertEquals(number, Decode.varInt(new ByteArrayInputStream(is.toByteArray())));
}
}