Jabit/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java

224 lines
8.1 KiB
Java

/*
* 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.GenericPayload;
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.exception.NodeException;
import ch.dissem.bitmessage.utils.AccessCounter;
import ch.dissem.bitmessage.utils.Decode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Creates protocol v3 network messages from {@link InputStream InputStreams}
*/
class V3MessageFactory {
private static Logger LOG = LoggerFactory.getLogger(V3MessageFactory.class);
public static NetworkMessage read(InputStream in) throws IOException {
findMagic(in);
String command = getCommand(in);
int length = (int) Decode.uint32(in);
byte[] checksum = Decode.bytes(in, 4);
byte[] payloadBytes = Decode.bytes(in, length);
if (testChecksum(checksum, payloadBytes)) {
MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length);
if (payload != null)
return new NetworkMessage(payload);
else
return null;
} else {
throw new IOException("Checksum failed for message '" + command + "'");
}
}
private static MessagePayload 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 readObject(stream, length);
case "custom":
return readCustom(stream, length);
default:
LOG.debug("Unknown command: " + command);
return null;
}
}
private static MessagePayload readCustom(InputStream in, int length) throws IOException {
return CustomMessage.read(in, length);
}
public static ObjectMessage readObject(InputStream in, int length) throws IOException {
AccessCounter counter = new AccessCounter();
byte nonce[] = Decode.bytes(in, 8, counter);
long expiresTime = Decode.int64(in, counter);
long objectType = Decode.uint32(in, counter);
long version = Decode.varInt(in, counter);
long stream = Decode.varInt(in, counter);
byte[] data = Decode.bytes(in, length - counter.length());
ObjectPayload payload;
try {
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length);
} catch (Exception e) {
LOG.trace("Could not parse object payload - using generic payload instead", e);
payload = new GenericPayload(version, stream, data);
}
return new ObjectMessage.Builder()
.nonce(nonce)
.expiresTime(expiresTime)
.objectType(objectType)
.stream(stream)
.payload(payload)
.build();
}
private static 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 static 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 static 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, false));
}
return builder.build();
}
private static 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, true);
NetworkAddress addrFrom = parseAddress(stream, true);
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 static InventoryVector parseInventoryVector(InputStream stream) throws IOException {
return new InventoryVector(Decode.bytes(stream, 32));
}
private static NetworkAddress parseAddress(InputStream stream, boolean light) throws IOException {
long time;
long streamNumber;
if (!light) {
time = Decode.int64(stream);
streamNumber = Decode.uint32(stream); // This isn't consistent, not sure if this is correct
} else {
time = 0;
streamNumber = 0;
}
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 static 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 static String getCommand(InputStream stream) throws IOException {
byte[] bytes = new byte[12];
int end = -1;
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) stream.read();
if (end == -1) {
if (bytes[i] == 0) end = i;
} else {
if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command");
}
}
return new String(bytes, 0, end, "ASCII");
}
private static void findMagic(InputStream in) throws IOException {
int pos = 0;
for (int i = 0; i < 1620000; i++) {
byte b = (byte) in.read();
if (b == MAGIC_BYTES[pos]) {
if (pos + 1 == MAGIC_BYTES.length) {
return;
}
} else if (pos > 0 && b == MAGIC_BYTES[0]) {
pos = 1;
} else {
pos = 0;
}
pos++;
}
throw new NodeException("Failed to find MAGIC bytes in stream");
}
}