/** * 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 java.io.*; import java.math.BigInteger; import java.util.Arrays; /** * 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. * * This class is not useful for library users. If you want to talk to the network see the {@link Peer} class. */ public abstract class Message implements Serializable { private static final long serialVersionUID = -3561053461717079135L; public static final int MAX_SIZE = 0x02000000; // 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; // The raw message bytes themselves. protected transient byte[] bytes; protected transient int protocolVersion; // This will be saved by subclasses that implement Serializable. protected NetworkParameters params; /** This exists for the Java serialization framework to use only. */ protected Message() { } Message(NetworkParameters params) { this.params = params; } @SuppressWarnings("unused") Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion) throws ProtocolException { this.protocolVersion = protocolVersion; this.params = params; this.bytes = msg; this.cursor = this.offset = offset; parse(); if (SELF_CHECK && !this.getClass().getSimpleName().equals("VersionMessage")) { 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)); } this.bytes = null; } Message(NetworkParameters params, byte[] msg, int offset) throws ProtocolException { this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION); } // 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; public byte[] bitcoinSerialize() { ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { bitcoinSerializeToStream(stream); } catch (IOException e) { // Cannot happen, we are serializing to a memory stream. throw new RuntimeException(e); } return stream.toByteArray(); } /** * Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). */ void bitcoinSerializeToStream(OutputStream stream) throws IOException { } int getMessageSize() { return cursor - offset; } long readUint32() { long u = Utils.readUint32(bytes, cursor); cursor += 4; return u; } byte[] readHash() { 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 hash; } BigInteger readUint64() { // 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); } long readVarInt() { VarInt varint = new VarInt(bytes, cursor); cursor += varint.getSizeInBytes(); return varint.value; } byte[] readBytes(int length) { byte[] b = new byte[length]; System.arraycopy(bytes, cursor, b, 0, length); cursor += length; return b; } String readStr() { VarInt varInt = new VarInt(bytes, cursor); if (varInt.value == 0) { cursor += 1; return ""; } byte[] characters = new byte[(int)varInt.value]; System.arraycopy(bytes, cursor, characters, 0, characters.length); cursor += varInt.getSizeInBytes(); try { return new String(characters, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); // Cannot happen, UTF-8 is always supported. } } }