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:
parent
bf7b8f133c
commit
10b40cbb48
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
28
src/com/google/bitcoin/core/GetAddrMessage.java
Normal file
28
src/com/google/bitcoin/core/GetAddrMessage.java
Normal 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.
|
||||||
|
}
|
||||||
|
}
|
@ -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!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user