/** * 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.devcoin.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; /** * <p>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.</p> */ 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 byte[] payloadBytes; protected transient int payloadCursor; 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 = offset; 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); } Message(NetworkParameters params, byte[] msg, byte[] payloadBytes, int offset, final boolean parseLazy, final boolean parseRetain, int length, int payloadCursor) throws ProtocolException { this.parseLazy = parseLazy; this.parseRetain = parseRetain; this.protocolVersion = NetworkParameters.PROTOCOL_VERSION; this.params = params; this.bytes = msg; this.payloadBytes = payloadBytes; this.payloadCursor = payloadCursor; this.cursor = offset; 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; } // 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. * <p/> * 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. * <p/> * 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. * <p/> * 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. * <br/> * This method may return the original byte array used to construct this message if the * following conditions are met: * <ol> * <li>1) The message was parsed from a byte array with parseRetain = true</li> * <li>2) The message has not been modified</li> * <li>3) The array had an offset of 0 and no surplus bytes</li> * </ol> * * 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.debug("Warning: {} 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() { return null; } /** * 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. * * @return */ 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); } } }