Some refactoring to move some common code into an AbstractConnection

This commit is contained in:
2016-06-01 17:38:49 +02:00
parent 425a9dd6bf
commit cde4f7b3ce
15 changed files with 748 additions and 340 deletions

View File

@ -24,6 +24,8 @@ import ch.dissem.bitmessage.utils.UnixTime;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
@ -215,6 +217,17 @@ public class NetworkAddress implements Streamable {
return this;
}
public Builder address(SocketAddress address) {
if (address instanceof InetSocketAddress) {
InetSocketAddress inetAddress = (InetSocketAddress) address;
ip(inetAddress.getAddress());
port(inetAddress.getPort());
} else {
throw new IllegalArgumentException("Unknown type of address: " + address.getClass());
}
return this;
}
public NetworkAddress build() {
if (time == 0) {
time = UnixTime.now();

View File

@ -62,7 +62,7 @@ class V3MessageFactory {
}
}
private static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException {
static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException {
switch (command) {
case "version":
return parseVersion(stream);

View File

@ -0,0 +1,143 @@
/*
* Copyright 2016 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.MessagePayload;
import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.utils.Decode;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Similar to the {@link V3MessageFactory}, but used for NIO buffers which may or may not contain a whole message.
*/
public class V3MessageReader {
private ReaderState state = ReaderState.MAGIC;
private String command;
private int length;
private byte[] checksum;
private List<NetworkMessage> messages = new LinkedList<>();
public void update(ByteBuffer buffer) {
while (buffer.hasRemaining()) {
switch (state) {
case MAGIC:
if (!findMagicBytes(buffer)) return;
state = ReaderState.HEADER;
case HEADER:
if (buffer.remaining() < 20) {
buffer.compact();
return;
}
command = getCommand(buffer);
length = (int) Decode.uint32(buffer);
if (length > MAX_PAYLOAD_SIZE) {
throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected.");
}
checksum = new byte[4];
buffer.get(checksum);
state = ReaderState.DATA;
if (buffer.remaining() < length) {
// We need to compact the buffer to make sure the message fits even if it's really big.
buffer.compact();
}
case DATA:
if (buffer.remaining() < length) return;
if (!testChecksum(buffer)) {
throw new NodeException("Checksum failed for message '" + command + "'");
}
try {
MessagePayload payload = V3MessageFactory.getPayload(
command,
new ByteArrayInputStream(buffer.array(), buffer.arrayOffset() + buffer.position(), length),
length);
if (payload != null) {
messages.add(new NetworkMessage(payload));
}
} catch (IOException e) {
throw new NodeException(e.getMessage());
}
state = ReaderState.MAGIC;
}
}
}
public List<NetworkMessage> getMessages() {
return messages;
}
private boolean findMagicBytes(ByteBuffer buffer) {
int i = 0;
while (buffer.hasRemaining()) {
if (buffer.get() == MAGIC_BYTES[i]) {
buffer.mark();
i++;
if (i == MAGIC_BYTES.length) return true;
} else {
i = 0;
}
}
if (i > 0) {
buffer.reset();
buffer.compact();
} else {
buffer.clear();
}
return false;
}
private static String getCommand(ByteBuffer buffer) {
int start = buffer.position();
int i = 0;
while (i < 12 && buffer.get() != 0) i++;
int end = start + i;
while (i < 12) {
if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command");
i++;
}
try {
return new String(buffer.array(), start, end, "ASCII");
} catch (UnsupportedEncodingException e) {
throw new ApplicationException(e);
}
}
private boolean testChecksum(ByteBuffer buffer) {
byte[] payloadChecksum = cryptography().sha512(buffer.array(),
buffer.arrayOffset() + buffer.position(), length);
for (int i = 0; i < checksum.length; i++) {
if (checksum[i] != payloadChecksum[i]) {
return false;
}
}
return true;
}
private enum ReaderState {MAGIC, HEADER, DATA}
}

View File

@ -61,6 +61,12 @@ public abstract class AbstractCryptography implements Cryptography, InternalCont
this.context = context;
}
public byte[] sha512(byte[] data, int offset, int length) {
MessageDigest mda = md("SHA-512");
mda.update(data, offset, length);
return mda.digest();
}
public byte[] sha512(byte[]... data) {
return hash("SHA-512", data);
}

View File

@ -30,6 +30,18 @@ import java.security.SecureRandom;
* which should be secure enough.
*/
public interface Cryptography {
/**
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
* success on the same thread.
*
* @param data to get hashed
* @param offset of the data to be hashed
* @param length of the data to be hashed
* @return SHA-512 hash of data within the given range
*/
byte[] sha512(byte[] data, int offset, int length);
/**
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in

View File

@ -23,6 +23,7 @@ import ch.dissem.bitmessage.utils.Property;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Collection;
import java.util.concurrent.Future;
/**
@ -30,6 +31,8 @@ import java.util.concurrent.Future;
*/
public interface NetworkHandler {
int NETWORK_MAGIC_NUMBER = 8;
int MAX_PAYLOAD_SIZE = 1600003;
int MAX_MESSAGE_SIZE = 24 + MAX_PAYLOAD_SIZE;
/**
* Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
@ -65,6 +68,13 @@ public interface NetworkHandler {
*/
void offer(InventoryVector iv);
/**
* Request each of those objects from a node that knows of the requested object.
*
* @param inventoryVectors of the objects to be requested
*/
void request(Collection<InventoryVector> inventoryVectors);
Property getNetworkStatus();
boolean isRunning();

View File

@ -111,6 +111,10 @@ public class Decode {
return stream.read() * 16777216L + stream.read() * 65536L + stream.read() * 256L + stream.read();
}
public static long uint32(ByteBuffer buffer) {
return buffer.get() * 16777216L + buffer.get() * 65536L + buffer.get() * 256L + buffer.get();
}
public static int int32(InputStream stream) throws IOException {
return int32(stream, null);
}