com.google.bitcoin.core.Message Maven / Gradle / Ivy
The newest version!
/**
* 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
import static com.google.common.base.Preconditions.checkState;
/**
* A Message is a data structure that can be serialized/deserialized using both the Bitcoin proprietary serialization
* format and built-in Java object serialization. Specific types of messages that are used both in the block chain,
* and on the wire, are derived from this class.
*/
public abstract class Message implements Serializable {
private static final Logger log = LoggerFactory.getLogger(Message.class);
private static final long serialVersionUID = -3561053461717079135L;
public static final int MAX_SIZE = 0x02000000;
public static final int UNKNOWN_LENGTH = Integer.MIN_VALUE;
// Useful to ensure serialize/deserialize are consistent with each other.
private static final boolean SELF_CHECK = false;
// The offset is how many bytes into the provided byte array this message starts at.
protected transient int offset;
// The cursor keeps track of where we are in the byte array as we parse it.
// Note that it's relative to the start of the array NOT the start of the message.
protected transient int cursor;
protected transient int length = UNKNOWN_LENGTH;
// The raw message bytes themselves.
protected transient byte[] bytes;
protected transient boolean parsed = false;
protected transient boolean recached = false;
protected transient final boolean parseLazy;
protected transient final boolean parseRetain;
protected transient int protocolVersion;
protected transient byte[] checksum;
// This will be saved by subclasses that implement Serializable.
protected NetworkParameters params;
/**
* This exists for the Java serialization framework to use only.
*/
protected Message() {
parsed = true;
parseLazy = false;
parseRetain = false;
}
Message(NetworkParameters params) {
this.params = params;
parsed = true;
parseLazy = false;
parseRetain = false;
}
Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion) throws ProtocolException {
this(params, msg, offset, protocolVersion, false, false, UNKNOWN_LENGTH);
}
/**
*
* @param params NetworkParameters object.
* @param msg Bitcoin protocol formatted byte array containing message content.
* @param offset The location of the first msg byte within the array.
* @param protocolVersion Bitcoin protocol version.
* @param parseLazy Whether to perform a full parse immediately or delay until a read is requested.
* @param parseRetain Whether to retain the backing byte array for quick reserialization.
* If true and the backing byte array is invalidated due to modification of a field then
* the cached bytes may be repopulated and retained if the message is serialized again in the future.
* @param length The length of message if known. Usually this is provided when deserializing of the wire
* as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH
* @throws ProtocolException
*/
Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion, final boolean parseLazy,
final boolean parseRetain, int length) throws ProtocolException {
this.parseLazy = parseLazy;
this.parseRetain = parseRetain;
this.protocolVersion = protocolVersion;
this.params = params;
this.bytes = msg;
this.cursor = this.offset = offset;
this.length = length;
if (parseLazy) {
parseLite();
} else {
parseLite();
parse();
parsed = true;
}
if (this.length == UNKNOWN_LENGTH)
checkState(false, "Length field has not been set in constructor for %s after %s parse. " +
"Refer to Message.parseLite() for detail of required Length field contract.",
getClass().getSimpleName(), parseLazy ? "lite" : "full");
if (SELF_CHECK) {
selfCheck(msg, offset);
}
if (parseRetain || !parsed)
return;
this.bytes = null;
}
private void selfCheck(byte[] msg, int offset) {
if (!(this instanceof VersionMessage)) {
maybeParse();
byte[] msgbytes = new byte[cursor - offset];
System.arraycopy(msg, offset, msgbytes, 0, cursor - offset);
byte[] reserialized = bitcoinSerialize();
if (!Arrays.equals(reserialized, msgbytes))
throw new RuntimeException("Serialization is wrong: \n" +
Utils.bytesToHexString(reserialized) + " vs \n" +
Utils.bytesToHexString(msgbytes));
}
}
Message(NetworkParameters params, byte[] msg, int offset) throws ProtocolException {
this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION, false, false, UNKNOWN_LENGTH);
}
Message(NetworkParameters params, byte[] msg, int offset, final boolean parseLazy, final boolean parseRetain, int length) throws ProtocolException {
this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION, parseLazy, parseRetain, length);
}
// These methods handle the serialization/deserialization using the custom Bitcoin protocol.
// It's somewhat painful to work with in Java, so some of these objects support a second
// serialization mechanism - the standard Java serialization system. This is used when things
// are serialized to the wallet.
abstract void parse() throws ProtocolException;
/**
* Perform the most minimal parse possible to calculate the length of the message.
* This is only required for subclasses of ChildClass as root level messages will have their length passed
* into the constructor.
*
* Implementations should adhere to the following contract: If parseLazy = true the 'length'
* field must be set before returning. If parseLazy = false the length field must be set either
* within the parseLite() method OR the parse() method. The overriding requirement is that length
* must be set to non UNKNOWN_MESSAGE value by the time the constructor exits.
*
* @return
* @throws ProtocolException
*/
protected abstract void parseLite() throws ProtocolException;
/**
* Ensure the object is parsed if needed. This should be called in every getter before returning a value.
* If the lazy parse flag is not set this is a method returns immediately.
*/
protected synchronized void maybeParse() {
if (parsed || bytes == null)
return;
try {
parse();
parsed = true;
if (!parseRetain)
bytes = null;
} catch (ProtocolException e) {
throw new LazyParseException("ProtocolException caught during lazy parse. For safe access to fields call ensureParsed before attempting read or write access", e);
}
}
/**
* In lazy parsing mode access to getters and setters may throw an unchecked LazyParseException. If guaranteed safe access is required
* this method will force parsing to occur immediately thus ensuring LazyParseExeption will never be thrown from this Message.
* If the Message contains child messages (e.g. a Block containing Transaction messages) this will not force child messages to parse.
*
* This could be overidden for Transaction and it's child classes to ensure the entire tree of Message objects is parsed.
*
* @throws ProtocolException
*/
public void ensureParsed() throws ProtocolException {
try {
maybeParse();
} catch (LazyParseException e) {
if (e.getCause() instanceof ProtocolException)
throw (ProtocolException) e.getCause();
throw new ProtocolException(e);
}
}
/**
* To be called before any change of internal values including any setters. This ensures any cached byte array is
* removed after performing a lazy parse if necessary to ensure the object is fully populated.
*
* Child messages of this object(e.g. Transactions belonging to a Block) will not have their internal byte caches
* invalidated unless they are also modified internally.
*/
protected void unCache() {
maybeParse();
checksum = null;
bytes = null;
recached = false;
}
protected void adjustLength(int newArraySize, int adjustment) {
if (length == UNKNOWN_LENGTH)
return;
// Our own length is now unknown if we have an unknown length adjustment.
if (adjustment == UNKNOWN_LENGTH) {
length = UNKNOWN_LENGTH;
return;
}
length += adjustment;
// Check if we will need more bytes to encode the length prefix.
if (newArraySize == 1)
length++; // The assumption here is we never call adjustLength with the same arraySize as before.
else if (newArraySize != 0)
length += VarInt.sizeOf(newArraySize) - VarInt.sizeOf(newArraySize - 1);
}
/**
* used for unit testing
*/
public boolean isParsed() {
return parsed;
}
/**
* used for unit testing
*/
public boolean isCached() {
//return parseLazy ? parsed && bytes != null : bytes != null;
return bytes != null;
}
public boolean isRecached() {
return recached;
}
/**
* Should only used by BitcoinSerializer for cached checksum
*
* @return the checksum
*/
byte[] getChecksum() {
return checksum;
}
/**
* Should only used by BitcoinSerializer for caching checksum
*
* @param checksum the checksum to set
*/
void setChecksum(byte[] checksum) {
if (checksum.length != 4)
throw new IllegalArgumentException("Checksum length must be 4 bytes, actual length: " + checksum.length);
this.checksum = checksum;
}
/**
* Returns a copy of the array returned by {@link Message#unsafeBitcoinSerialize()}, which is safe to mutate.
* If you need extra performance and can guarantee you won't write to the array, you can use the unsafe version.
*
* @return a freshly allocated serialized byte array
*/
public byte[] bitcoinSerialize() {
byte[] bytes = unsafeBitcoinSerialize();
byte[] copy = new byte[bytes.length];
System.arraycopy(bytes, 0, copy, 0, bytes.length);
return copy;
}
/**
* Serialize this message to a byte array that conforms to the bitcoin wire protocol.
*
* This method may return the original byte array used to construct this message if the
* following conditions are met:
*
* - 1) The message was parsed from a byte array with parseRetain = true
* - 2) The message has not been modified
* - 3) The array had an offset of 0 and no surplus bytes
*
*
* If condition 3 is not met then an copy of the relevant portion of the array will be returned.
* Otherwise a full serialize will occur. For this reason you should only use this API if you can guarantee you
* will treat the resulting array as read only.
*
* @return a byte array owned by this object, do NOT mutate it.
*/
public byte[] unsafeBitcoinSerialize() {
// 1st attempt to use a cached array.
if (bytes != null) {
if (offset == 0 && length == bytes.length) {
// Cached byte array is the entire message with no extras so we can return as is and avoid an array
// copy.
return bytes;
}
byte[] buf = new byte[length];
System.arraycopy(bytes, offset, buf, 0, length);
return buf;
}
// No cached array available so serialize parts by stream.
ByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(length < 32 ? 32 : length + 32);
try {
bitcoinSerializeToStream(stream);
} catch (IOException e) {
// Cannot happen, we are serializing to a memory stream.
}
if (parseRetain) {
// A free set of steak knives!
// If there happens to be a call to this method we gain an opportunity to recache
// the byte array and in this case it contains no bytes from parent messages.
// This give a dual benefit. Releasing references to the larger byte array so that it
// it is more likely to be GC'd. And preventing double serializations. E.g. calculating
// merkle root calls this method. It is will frequently happen prior to serializing the block
// which means another call to bitcoinSerialize is coming. If we didn't recache then internal
// serialization would occur a 2nd time and every subsequent time the message is serialized.
bytes = stream.toByteArray();
cursor = cursor - offset;
offset = 0;
recached = true;
length = bytes.length;
return bytes;
}
// Record length. If this Message wasn't parsed from a byte stream it won't have length field
// set (except for static length message types). Setting it makes future streaming more efficient
// because we can preallocate the ByteArrayOutputStream buffer and avoid resizing.
byte[] buf = stream.toByteArray();
length = buf.length;
return buf;
}
/**
* Serialize this message to the provided OutputStream using the bitcoin wire format.
*
* @param stream
* @throws IOException
*/
final public void bitcoinSerialize(OutputStream stream) throws IOException {
// 1st check for cached bytes.
if (bytes != null && length != UNKNOWN_LENGTH) {
stream.write(bytes, offset, length);
return;
}
bitcoinSerializeToStream(stream);
}
/**
* Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize().
*/
void bitcoinSerializeToStream(OutputStream stream) throws IOException {
log.error("Error: {} class has not implemented bitcoinSerializeToStream method. Generating message with no payload", getClass());
}
/**
* This method is a NOP for all classes except Block and Transaction. It is only declared in Message
* so BitcoinSerializer can avoid 2 instanceof checks + a casting.
*/
public Sha256Hash getHash() {
throw new UnsupportedOperationException();
}
/**
* This should be overridden to extract correct message size in the case of lazy parsing. Until this method is
* implemented in a subclass of ChildMessage lazy parsing may have no effect.
*
* This default implementation is a safe fall back that will ensure it returns a correct value by parsing the message.
*/
public int getMessageSize() {
if (length != UNKNOWN_LENGTH)
return length;
maybeParse();
if (length == UNKNOWN_LENGTH)
checkState(false, "Length field has not been set in %s after full parse.", getClass().getSimpleName());
return length;
}
long readUint32() throws ProtocolException {
try {
long u = Utils.readUint32(bytes, cursor);
cursor += 4;
return u;
} catch (ArrayIndexOutOfBoundsException e) {
throw new ProtocolException(e);
}
}
Sha256Hash readHash() throws ProtocolException {
try {
byte[] hash = new byte[32];
System.arraycopy(bytes, cursor, hash, 0, 32);
// We have to flip it around, as it's been read off the wire in little endian.
// Not the most efficient way to do this but the clearest.
hash = Utils.reverseBytes(hash);
cursor += 32;
return new Sha256Hash(hash);
} catch (IndexOutOfBoundsException e) {
throw new ProtocolException(e);
}
}
long readInt64() throws ProtocolException {
try {
long u = Utils.readInt64(bytes, cursor);
cursor += 8;
return u;
} catch (ArrayIndexOutOfBoundsException e) {
throw new ProtocolException(e);
}
}
BigInteger readUint64() throws ProtocolException {
try {
// Java does not have an unsigned 64 bit type. So scrape it off the wire then flip.
byte[] valbytes = new byte[8];
System.arraycopy(bytes, cursor, valbytes, 0, 8);
valbytes = Utils.reverseBytes(valbytes);
cursor += valbytes.length;
return new BigInteger(valbytes);
} catch (IndexOutOfBoundsException e) {
throw new ProtocolException(e);
}
}
long readVarInt() throws ProtocolException {
return readVarInt(0);
}
long readVarInt(int offset) throws ProtocolException {
try {
VarInt varint = new VarInt(bytes, cursor + offset);
cursor += offset + varint.getOriginalSizeInBytes();
return varint.value;
} catch (ArrayIndexOutOfBoundsException e) {
throw new ProtocolException(e);
}
}
byte[] readBytes(int length) throws ProtocolException {
try {
byte[] b = new byte[length];
System.arraycopy(bytes, cursor, b, 0, length);
cursor += length;
return b;
} catch (IndexOutOfBoundsException e) {
throw new ProtocolException(e);
}
}
byte[] readByteArray() throws ProtocolException {
long len = readVarInt();
return readBytes((int)len);
}
String readStr() throws ProtocolException {
try {
VarInt varInt = new VarInt(bytes, cursor);
if (varInt.value == 0) {
cursor += 1;
return "";
}
cursor += varInt.getOriginalSizeInBytes();
byte[] characters = new byte[(int) varInt.value];
System.arraycopy(bytes, cursor, characters, 0, characters.length);
cursor += characters.length;
try {
return new String(characters, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // Cannot happen, UTF-8 is always supported.
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new ProtocolException(e);
} catch (IndexOutOfBoundsException e) {
throw new ProtocolException(e);
}
}
boolean hasMoreBytes() {
return cursor < bytes.length;
}
/** Network parameters this message was created with. */
public NetworkParameters getParams() {
return params;
}
public static class LazyParseException extends RuntimeException {
private static final long serialVersionUID = 6971943053112975594L;
public LazyParseException(String message, Throwable cause) {
super(message, cause);
}
public LazyParseException(String message) {
super(message);
}
}
}