async Task <BitcoinMessage> ReadMessageAsync(CancellationToken cancellation) { // First find and read the magic. This will block until a valid magic has been read, or throw if the connection is lost / stream ended await ReadMagicAsync(ChatClientConfiguration.NetworkMagicBytes, cancellation).ConfigureAwait(false); // Then read the rest of the header (we read the magic already), which is formed of command, length, and a required checksum. int headerExMagicSize = BitcoinMessage.ModernHeaderSize - BitcoinMessage.MagicSize; var completeHeaderBuffer = new byte[BitcoinMessage .ModernHeaderSize]; // magic already read, now read: 12 bytes command, 4 bytes payload length, 4 bytes checksum = 20 bytes if (!await this .ReadBytesAsync(completeHeaderBuffer, BitcoinMessage.MagicSize, headerExMagicSize, cancellation) .ConfigureAwait(false)) { throw new ConnectedPeerException("Stream ended before the header could be read.", null); } Buffer.BlockCopy(ChatClientConfiguration.NetworkMagicBytes, 0, completeHeaderBuffer, 0, BitcoinMessage.MagicSize); var command = Encoding.ASCII .GetString(completeHeaderBuffer, BitcoinMessage.MagicSize, BitcoinMessage.CommandSize).TrimEnd('\0'); // Then extract the length, which is the message payload size. uint payloadLength = BitConverter.ToUInt32(completeHeaderBuffer, BitcoinMessage.MagicSize + BitcoinMessage.CommandSize); if (payloadLength > 0x00400000) { throw new ProtocolViolationException("Message payload too big (over 0x00400000 bytes)"); } this._logger.LogInformation($"Received message '{command}', payload length: {payloadLength} bytes."); var checkSumBytes = new byte[4]; Buffer.BlockCopy(completeHeaderBuffer, BitcoinMessage.MagicSize + BitcoinMessage.CommandSize + BitcoinMessage.PayloadLengthSize, checkSumBytes, 0, BitcoinMessage.ChecksumSize); byte[] payloadBuffer = new byte[payloadLength]; if (!await this.ReadBytesAsync(payloadBuffer, 0, (int)payloadLength, cancellation).ConfigureAwait(false)) { throw new ConnectedPeerException("Stream ended before the payload could be read.", null); } byte[] completeWithHeaderAndPayload = new byte[BitcoinMessage.ModernHeaderSize + payloadLength]; Buffer.BlockCopy(completeHeaderBuffer, 0, completeWithHeaderAndPayload, 0, completeHeaderBuffer.Length); Buffer.BlockCopy(payloadBuffer, 0, completeWithHeaderAndPayload, completeHeaderBuffer.Length, payloadBuffer.Length); var ret = new BitcoinMessage(command, payloadBuffer, checkSumBytes); return(ret); }
public async Task VersionHandshakeAsync(string userAgent, byte[] nonce, CancellationToken cancellationToken) { try { BitcoinVersionPayload outgoingVersionPayload = PayloadFactory.CreateVersionPayload(userAgent, nonce, (IPEndPoint)this._tcpClient.Client.LocalEndPoint, (IPEndPoint)this._tcpClient.Client.RemoteEndPoint); var myVersionMessage = new BitcoinMessage("version", outgoingVersionPayload.Serialize()); this._logger.LogInformation("Sending own version."); await WriteAsync(myVersionMessage.Serialize()); this._logger.LogInformation("'version' sent."); // do it like this so that there is no specific expected order for version and verack, but expect both. while (this.PeerVersionPayload == null || this.PeerVerAckReceived == false) { var reply = await ReadMessageAsync(cancellationToken); if (reply.Command == "version") { this.PeerVersionPayload = new BitcoinVersionPayload(reply.PayloadBytes); this._logger.LogInformation( $"Version payload decoded from {this.PeerVersionPayload.UserAgent.Text} at {this.PeerVersionPayload.Sender}."); this._logger.LogInformation($"Setting own address to {this.PeerVersionPayload.Receiver}."); this.SelfFromPeer = this.PeerVersionPayload.Receiver.ToPeer(); this._logger.LogInformation("Sending version acknowledgement."); await WriteAsync(new BitcoinMessage("verack", new byte[0]).Serialize()); this._logger.LogInformation("'verack' sent."); } else if (reply.Command == "verack") { this.PeerVerAckReceived = true; } else { throw new InvalidOperationException( $"Received unexpected message '{reply.Command}' during handshake."); } } } catch (Exception e) { DisposeAndThrow(e); } }