Some refactoring to move some common code into an AbstractConnection
This commit is contained in:
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user