It's now possible to send a 'version' message that will be accepted by the other node.
This commit is contained in:
parent
c2624dcd15
commit
0c4b39bdee
@ -1,4 +1,9 @@
|
||||
Jabit
|
||||
=====
|
||||
|
||||
A Java implementation for the Bitmessage protocol. To build, use command `gradle build`.
|
||||
A Java implementation for the Bitmessage protocol. To build, use command `gradle build`. Note that for some tests to run, a standard Bitmessage client needs to run on the same system, using port 8444 (the default port).
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
If you're able to audit Jabit to verify its security, you would be very very welcome. Please be aware though that the official Bitmessage project would like an audit, too, and they were first in line.
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
70
domain/src/main/java/ch/dissem/bitmessage/entity/Inv.java
Normal file
70
domain/src/main/java/ch/dissem/bitmessage/entity/Inv.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
32
domain/src/main/java/ch/dissem/bitmessage/utils/Base58.java
Normal file
32
domain/src/main/java/ch/dissem/bitmessage/utils/Base58.java
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||