3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-31 07:12:17 +00:00

Split out parsing of header and payload. This is useful for high-performance programs that don't always need to parse the payload. Patch from shadders (CLA agreement pending).

This commit is contained in:
Mike Hearn 2011-09-15 15:03:15 +00:00
parent bf7b8f133c
commit 10b40cbb48
3 changed files with 160 additions and 77 deletions

View File

@ -24,7 +24,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -42,8 +41,7 @@ import static com.google.bitcoin.core.Utils.*;
* </ul> * </ul>
* *
*/ */
public class BitcoinSerializer public class BitcoinSerializer {
{
private static final Logger log = LoggerFactory.getLogger(BitcoinSerializer.class); private static final Logger log = LoggerFactory.getLogger(BitcoinSerializer.class);
private static final int COMMAND_LEN = 12; private static final int COMMAND_LEN = 12;
@ -62,6 +60,7 @@ public class BitcoinSerializer
names.put(Ping.class, "ping"); names.put(Ping.class, "ping");
names.put(VersionAck.class, "verack"); names.put(VersionAck.class, "verack");
names.put(GetBlocksMessage.class, "getblocks"); names.put(GetBlocksMessage.class, "getblocks");
names.put(GetAddrMessage.class, "getaddr");
} }
/** /**
@ -75,10 +74,21 @@ public class BitcoinSerializer
this.usesChecksumming = usesChecksumming; this.usesChecksumming = usesChecksumming;
} }
public void useChecksumming(boolean usesChecksumming) { public void setUseChecksumming(boolean usesChecksumming) {
this.usesChecksumming = usesChecksumming; this.usesChecksumming = usesChecksumming;
} }
public boolean getUseChecksumming() {
return usesChecksumming;
}
/**
* Provides the expected header length, which varies depending on whether checksumming is used.
* Header length includes 4 byte magic number.
*/
public int getHeaderLength() {
return 4 + COMMAND_LEN + 4 + (usesChecksumming ? 4 : 0);
}
/** /**
* Writes message to to the output stream. * Writes message to to the output stream.
@ -115,9 +125,7 @@ public class BitcoinSerializer
log.debug("Sending {} message: {}", name, bytesToHexString(header) + bytesToHexString(payload)); log.debug("Sending {} message: {}", name, bytesToHexString(header) + bytesToHexString(payload));
} }
/** /** Reads a message from the given InputStream and returns it. */
* Reads a message from the given InputStream and returns it.
*/
public Message deserialize(InputStream in) throws ProtocolException, IOException { public Message deserialize(InputStream in) throws ProtocolException, IOException {
// A BitCoin protocol message has the following format. // A BitCoin protocol message has the following format.
// //
@ -134,53 +142,33 @@ public class BitcoinSerializer
// Satoshi's implementation ignores garbage before the magic header bytes. We have to do the same because // Satoshi's implementation ignores garbage before the magic header bytes. We have to do the same because
// sometimes it sends us stuff that isn't part of any message. // sometimes it sends us stuff that isn't part of any message.
seekPastMagicBytes(in); seekPastMagicBytes(in);
// Now read in the header. BitcoinPacketHeader header = new BitcoinPacketHeader(usesChecksumming, in);
byte[] header = new byte[COMMAND_LEN + 4 + (usesChecksumming ? 4 : 0)];
int readCursor = 0;
while (readCursor < header.length) {
int bytesRead = in.read(header, readCursor, header.length - readCursor);
if (bytesRead == -1) {
// There's no more data to read.
throw new IOException("Socket is disconnected");
}
readCursor += bytesRead;
}
int cursor = 0;
// The command is a NULL terminated string, unless the command fills all twelve bytes
// in which case the termination is implicit.
String command;
int mark = cursor;
for (; header[cursor] != 0 && cursor - mark < COMMAND_LEN; cursor++);
byte[] commandBytes = new byte[cursor - mark];
System.arraycopy(header, mark, commandBytes, 0, cursor - mark);
try {
command = new String(commandBytes, "US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // Cannot happen.
}
cursor = mark + COMMAND_LEN;
int size = (int) readUint32(header, cursor);
cursor += 4;
if (size > Message.MAX_SIZE)
throw new ProtocolException("Message size too large: " + size);
// Old clients don't send the checksum.
byte[] checksum = new byte[4];
if (usesChecksumming) {
// Note that the size read above includes the checksum bytes.
System.arraycopy(header, cursor, checksum, 0, 4);
cursor += 4;
}
// Now try to read the whole message. // Now try to read the whole message.
readCursor = 0; return deserializePayload(header, in);
byte[] payloadBytes = new byte[size];
}
/**
* Deserializes only the header in case packet meta data is needed before decoding
* the payload. This method assumes you have already called seekPastMagicBytes()
*/
public BitcoinPacketHeader deserializeHeader(InputStream in) throws ProtocolException, IOException {
return new BitcoinPacketHeader(usesChecksumming, in);
}
/**
* Deserialize payload only. You must provide a header typically obtained by calling deserializeHeader.
* @param header
* @param in
* @return
* @throws ProtocolException
* @throws IOException
*/
public Message deserializePayload(BitcoinPacketHeader header, InputStream in) throws ProtocolException, IOException {
int readCursor = 0;
byte[] payloadBytes = new byte[header.size];
while (readCursor < payloadBytes.length - 1) { while (readCursor < payloadBytes.length - 1) {
int bytesRead = in.read(payloadBytes, readCursor, size - readCursor); int bytesRead = in.read(payloadBytes, readCursor, header.size - readCursor);
if (bytesRead == -1) { if (bytesRead == -1) {
throw new IOException("Socket is disconnected"); throw new IOException("Socket is disconnected");
} }
@ -190,28 +178,27 @@ public class BitcoinSerializer
// Verify the checksum. // Verify the checksum.
if (usesChecksumming) { if (usesChecksumming) {
byte[] hash = doubleDigest(payloadBytes); byte[] hash = doubleDigest(payloadBytes);
if (checksum[0] != hash[0] || checksum[1] != hash[1] || if (header.checksum[0] != hash[0] || header.checksum[1] != hash[1] ||
checksum[2] != hash[2] || checksum[3] != hash[3]) { header.checksum[2] != hash[2] || header.checksum[3] != hash[3]) {
throw new ProtocolException("Checksum failed to verify, actual " + throw new ProtocolException("Checksum failed to verify, actual " +
bytesToHexString(hash) + bytesToHexString(hash) +
" vs " + bytesToHexString(checksum)); " vs " + bytesToHexString(header.checksum));
} }
} }
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Received {} byte '{}' message: {}", new Object[]{ log.debug("Received {} byte '{}' message: {}", new Object[]{
size, header.size,
command, header.command,
Utils.bytesToHexString(payloadBytes) Utils.bytesToHexString(payloadBytes)
}); });
} }
try { try {
return makeMessage(command, payloadBytes); return makeMessage(header.command, payloadBytes);
} catch (Exception e) { } catch (Exception e) {
throw new ProtocolException("Error deserializing message " + Utils.bytesToHexString(payloadBytes) + "\n", e); throw new ProtocolException("Error deserializing message " + Utils.bytesToHexString(payloadBytes) + "\n", e);
} }
} }
private Message makeMessage(String command, byte[] payloadBytes) throws ProtocolException { private Message makeMessage(String command, byte[] payloadBytes) throws ProtocolException {
@ -237,21 +224,7 @@ public class BitcoinSerializer
} }
} }
private Constructor<? extends Message> makeConstructor(Class<? extends Message> c) { public void seekPastMagicBytes(InputStream in) throws IOException {
Class<?> parTypes[] = new Class<?>[2];
parTypes[0] = NetworkParameters.class;
parTypes[1] = byte[].class;
try {
return c.getDeclaredConstructor(parTypes);
} catch (NoSuchMethodException e) {
return null;
}
}
private void seekPastMagicBytes(InputStream in) throws IOException {
int magicCursor = 3; // Which byte of the magic we're looking for currently. int magicCursor = 3; // Which byte of the magic we're looking for currently.
while (true) { while (true) {
int b = in.read(); // Read a byte. int b = in.read(); // Read a byte.
@ -275,4 +248,86 @@ public class BitcoinSerializer
} }
} }
} }
public class BitcoinPacketHeader {
final byte[] header;
final String command;
final int size;
final byte[] checksum;
BitcoinPacketHeader(boolean usesCheckSumminng, InputStream in) throws ProtocolException, IOException {
header = new byte[COMMAND_LEN + 4 + (usesChecksumming ? 4 : 0)];
int readCursor = 0;
while (readCursor < header.length) {
int bytesRead = in.read(header, readCursor, header.length - readCursor);
if (bytesRead == -1) {
// There's no more data to read.
throw new IOException("Incomplete packet in underlying stream");
}
readCursor += bytesRead;
}
int cursor = 0;
// The command is a NULL terminated string, unless the command fills all twelve bytes
// in which case the termination is implicit.
int mark = cursor;
for (; header[cursor] != 0 && cursor - mark < COMMAND_LEN; cursor++);
byte[] commandBytes = new byte[cursor - mark];
System.arraycopy(header, mark, commandBytes, 0, cursor - mark);
try {
command = new String(commandBytes, "US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // Cannot happen.
}
cursor = mark + COMMAND_LEN;
size = (int) readUint32(header, cursor);
cursor += 4;
if (size > Message.MAX_SIZE)
throw new ProtocolException("Message size too large: " + size);
// Old clients don't send the checksum.
checksum = new byte[4];
if (usesChecksumming) {
// Note that the size read above includes the checksum bytes.
System.arraycopy(header, cursor, checksum, 0, 4);
cursor += 4;
}
}
public boolean hasCheckSum() {
return checksum != null;
}
/**
* @return the header
*/
public byte[] getHeader() {
return header;
}
/**
* @return the command
*/
public String getCommand() {
return command;
}
/**
* @return the size
*/
public int getPayloadSize() {
return size;
}
/**
* @return the checksum
*/
public byte[] getChecksum() {
return checksum;
}
}
} }

View File

@ -0,0 +1,28 @@
/**
* Copyright 2011 Google Inc.
*
* 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 com.google.bitcoin.core;
public class GetAddrMessage extends Message {
public GetAddrMessage(NetworkParameters params) {
super(params);
}
@Override
void parse() throws ProtocolException {
// TODO: Implement this.
}
}

View File

@ -110,7 +110,7 @@ public class NetworkConnection {
throw new ProtocolException("Peer does not have a copy of the block chain."); throw new ProtocolException("Peer does not have a copy of the block chain.");
} }
// newer clients use checksumming // newer clients use checksumming
serializer.useChecksumming(peerVersion >= 209); serializer.setUseChecksumming(peerVersion >= 209);
// Handshake is done! // Handshake is done!
} }