public Message Deserialize(Stream @in)
{
// A BitCoin protocol message has the following format.
//
// - 4 byte magic number: 0xfabfb5da for the testnet or
// 0xf9beb4d9 for production
// - 12 byte command in ASCII
// - 4 byte payload size
// - 4 byte checksum
// - Payload data
//
// The checksum is the first 4 bytes of a SHA256 hash of the message payload. It isn't
// present for all messages, notably, the first one on a connection.
//
// 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.
SeekPastMagicBytes(@in);
// Now read in the header.
var header = new byte[_commandLen + 4 + (_usesChecksumming ? 4 : 0)];
var readCursor = 0;
while (readCursor < header.Length)
{
var 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;
}
var cursor = 0;
// The command is a NULL terminated string, unless the command fills all twelve bytes
// in which case the termination is implicit.
var mark = cursor;
for (; header[cursor] != 0 && cursor - mark < _commandLen; cursor++)
{
}
var commandBytes = new byte[cursor - mark];
Array.Copy(header, mark, commandBytes, 0, commandBytes.Length);
for (var i = 0; i < commandBytes.Length; i++)
{
// Emulate ASCII by replacing extended characters with question marks.
if (commandBytes[i] >= 0x80)
{
commandBytes[i] = 0x3F;
}
}
var command = Encoding.UTF8.GetString(commandBytes, 0, commandBytes.Length);
cursor = mark + _commandLen;
var size = Utils.ReadUint32(header, cursor);
cursor += 4;
if (size > Message.MaxSize)
throw new ProtocolException("Message size too large: " + size);
// Old clients don't send the checksum.
var checksum = new byte[4];
if (_usesChecksumming)
{
// Note that the size read above includes the checksum bytes.
Array.Copy(header, cursor, checksum, 0, 4);
}
// Now try to read the whole message.
readCursor = 0;
var payloadBytes = new byte[size];
while (readCursor < payloadBytes.Length - 1)
{
var bytesRead = @in.Read(payloadBytes, readCursor, (int) (size - readCursor));
if (bytesRead == -1)
{
throw new IOException("Socket is disconnected");
}
readCursor += bytesRead;
}
// Verify the checksum.
if (_usesChecksumming)
{
var hash = Utils.DoubleDigest(payloadBytes);
if (checksum[0] != hash[0] || checksum[1] != hash[1] ||
checksum[2] != hash[2] || checksum[3] != hash[3])
{
throw new ProtocolException("Checksum failed to verify, actual " +
Utils.BytesToHexString(hash) +
" vs " + Utils.BytesToHexString(checksum));
}
}
if (_log.IsDebugEnabled)
{
_log.DebugFormat("Received {0} byte '{1}' message: {2}",
size,
command,
Utils.BytesToHexString(payloadBytes)
);
}
try
{
return MakeMessage(command, payloadBytes);
}
catch (Exception e)
{
throw new ProtocolException("Error deserializing message " + Utils.BytesToHexString(payloadBytes) + Environment.NewLine + e.Message, e);
}
}