package org.ripple.power.txns.btc;
import java.io.EOFException;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.HashMap;
import org.ripple.power.Helper;
/**
* <p>Each message on the network consists of the message header followed by an optional payload.</p>
*
* <p>Message Header:</p>
* <pre>
* Size Field Description
* ==== ===== ===========
* 4 bytes Magic Magic number
* 12 bytes Command Null-terminated ASCII command
* 4 bytes Length Payload length
* 4 bytes Checksum First 4 bytes of the SHA-256 double digest of the payload
* </pre>
*/
public class MessageHeader {
/** Message header length */
public static final int HEADER_LENGTH = 24;
/** Checksum for zero-length payload */
public static final byte[] ZERO_LENGTH_CHECKSUM = new byte[] {
(byte)0x5d, (byte)0xf6, (byte)0xe0, (byte)0xe2
};
/** Message commands */
public enum MessageCommand {
ADDR, // 'addr' message
ALERT, // 'alert' message
BLOCK, // 'block' message
FILTERADD, // 'filteradd' message
FILTERCLEAR, // 'filterclear' message
FILTERLOAD, // 'filterload' message
GETADDR, // 'getaddr' message
GETBLOCKS, // 'getblocks' message
GETDATA, // 'getdata' message
GETHEADERS, // 'getheaders' message
HEADERS, // 'headers' message
INV, // 'inv' message
MEMPOOL, // 'mempool' message
MERKLEBLOCK, // 'merkleblock' message
NOTFOUND, // 'notfound' message
PING, // 'ping' message
PONG, // 'pong' message
REJECT, // 'reject' message
TX, // 'tx' message
VERACK, // 'verack' message
VERSION // 'version' message
}
/** Message command map */
public static final Map<String, MessageCommand> cmdMap = new HashMap<>();
static {
cmdMap.put("addr", MessageCommand.ADDR);
cmdMap.put("alert", MessageCommand.ALERT);
cmdMap.put("block", MessageCommand.BLOCK);
cmdMap.put("filteradd", MessageCommand.FILTERADD);
cmdMap.put("filterclear", MessageCommand.FILTERCLEAR);
cmdMap.put("filterload", MessageCommand.FILTERLOAD);
cmdMap.put("getaddr", MessageCommand.GETADDR);
cmdMap.put("getblocks", MessageCommand.GETBLOCKS);
cmdMap.put("getdata", MessageCommand.GETDATA);
cmdMap.put("getheaders", MessageCommand.GETHEADERS);
cmdMap.put("headers", MessageCommand.HEADERS);
cmdMap.put("inv", MessageCommand.INV);
cmdMap.put("mempool", MessageCommand.MEMPOOL);
cmdMap.put("merkleblock", MessageCommand.MERKLEBLOCK);
cmdMap.put("notfound", MessageCommand.NOTFOUND);
cmdMap.put("ping", MessageCommand.PING);
cmdMap.put("pong", MessageCommand.PONG);
cmdMap.put("reject", MessageCommand.REJECT);
cmdMap.put("tx", MessageCommand.TX);
cmdMap.put("verack", MessageCommand.VERACK);
cmdMap.put("version", MessageCommand.VERSION);
}
/**
* Build the message header and then construct a buffer containing the message header
* and the message data
*
* @param cmd Message command
* @param msgData Message data
* @return Message buffer
*/
public static ByteBuffer buildMessage(String cmd, SerializedBuffer msgData) {
return buildMessage(cmd, msgData.toByteArray());
}
/**
* Build the message header and then construct a buffer containing the message header
* and the message data
*
* @param cmd Message command
* @param msgBytes Message data
* @return Message buffer
*/
public static ByteBuffer buildMessage(String cmd, byte[] msgBytes) {
byte[] bytes = new byte[HEADER_LENGTH + msgBytes.length];
//
// Set the magic number
//
Helper.uint32ToByteArrayLE(NetParams.MAGIC_NUMBER, bytes, 0);
//
// Set the command name (single-byte ASCII characters)
//
for (int i=0; i<cmd.length(); i++)
bytes[4+i] = (byte)cmd.codePointAt(i);
//
// Set the payload length
//
Helper.uint32ToByteArrayLE(msgBytes.length, bytes, 16);
//
// Compute the payload checksum
//
// The message header contains a fixed checksum value when there is no payload
//
if (msgBytes.length == 0) {
System.arraycopy(ZERO_LENGTH_CHECKSUM, 0, bytes, 20, 4);
} else {
byte[] digest = Helper.doubleDigest(msgBytes);
System.arraycopy(digest, 0, bytes, 20, 4);
System.arraycopy(msgBytes, 0, bytes, 24, msgBytes.length);
}
return ByteBuffer.wrap(bytes);
}
/**
* Processes the message header and returns the message command. A VerificationException
* is thrown if the message header is incomplete, has an incorrect magic value, or the
* checksum is not correct.
*
* @param msgBuffer Message buffer
* @return Message command
* @throws EOFException End-of-data processing stream
* @throws VerificationException Message verification failed
*/
public static MessageCommand processMessage(SerializedBuffer msgBuffer)
throws EOFException, VerificationException {
//
// Get the message bytes
//
byte[] msgBytes;
if (msgBuffer.getBufferStart() == 0)
msgBytes = msgBuffer.array();
else
msgBytes = msgBuffer.getBytes(msgBuffer.available());
if (msgBytes.length < HEADER_LENGTH)
throw new EOFException("Message header is too short");
msgBuffer.setPosition(HEADER_LENGTH);
//
// Verify the magic number
//
long magic = Helper.readUint32LE(msgBytes, 0);
if (magic != NetParams.MAGIC_NUMBER)
throw new VerificationException(String.format("Message header magic number %d is invalid", magic));
//
// Verify the payload checksum
//
if (msgBytes.length > HEADER_LENGTH) {
byte[] digest = Helper.doubleDigest(msgBytes, HEADER_LENGTH, msgBytes.length-HEADER_LENGTH);
if (digest[0] != msgBytes[20] || digest[1] != msgBytes[21] ||
digest[2] != msgBytes[22] || digest[3] != msgBytes[23])
throw new VerificationException("Message checksum incorrect");
}
//
// Build the command name
//
StringBuilder cmdString = new StringBuilder(16);
for (int i=4; i<16; i++) {
if (msgBytes[i] == 0)
break;
cmdString.appendCodePoint(((int)msgBytes[i])&0xff);
}
String cmd = cmdString.toString();
//
// Get the message command
//
MessageCommand cmdOp = cmdMap.get(cmd);
if (cmdOp == null)
throw new VerificationException(String.format("Message '%s' is not supported", cmd));
return cmdOp;
}
}