Skip to main content

urma_runtime/p2p/
wire.rs

1use crate::config::{
2    P2P_MAX_ADDRESSES, P2P_MAX_HEADERS_PER_MESSAGE, P2P_MAX_INVENTORY, P2P_MAX_PAYLOAD_BYTES,
3};
4use crate::error::{Error, ensure};
5use bitcoin::block::Header;
6use bitcoin::consensus::encode::{Decodable, VarInt, serialize};
7use bitcoin::hashes::{Hash, sha256d};
8use bitcoin::p2p::Magic;
9use bitcoin::p2p::address::{AddrV2Message, Address};
10use bitcoin::p2p::message::{NetworkMessage, RawNetworkMessage};
11use bitcoin::p2p::message_blockdata::Inventory;
12use bitcoin::p2p::message_network::VersionMessage;
13use std::io::{Read, Write};
14
15pub enum Message {
16    Version(VersionMessage),
17    Verack,
18    Ping(u64),
19    Pong(u64),
20    Addr(Vec<(u32, Address)>),
21    AddrV2(Vec<AddrV2Message>),
22    Headers(Vec<Header>),
23    Inv(Vec<Inventory>),
24    NotFound(Vec<Inventory>),
25    Block(Vec<u8>),
26    Other(String),
27}
28
29impl Message {
30    pub fn command(&self) -> String {
31        match self {
32            Self::Version(_) => "version".into(),
33            Self::Verack => "verack".into(),
34            Self::Ping(_) => "ping".into(),
35            Self::Pong(_) => "pong".into(),
36            Self::Addr(rows) => format!("addr({})", rows.len()),
37            Self::AddrV2(rows) => format!("addrv2({})", rows.len()),
38            Self::Headers(rows) => format!("headers({})", rows.len()),
39            Self::Inv(rows) => format!("inv({})", rows.len()),
40            Self::NotFound(rows) => format!("notfound({})", rows.len()),
41            Self::Block(raw) => format!("block({})", raw.len()),
42            Self::Other(name) => name.clone(),
43        }
44    }
45}
46
47pub(super) fn send(
48    stream: &mut impl Write,
49    magic: [u8; 4],
50    payload: NetworkMessage,
51) -> Result<(), Error> {
52    let frame = RawNetworkMessage::new(Magic::from_bytes(magic), payload);
53    stream.write_all(&serialize(&frame))?;
54    stream.flush()?;
55    Ok(())
56}
57
58pub(super) fn receive(stream: &mut impl Read, magic: [u8; 4]) -> Result<Message, Error> {
59    let mut head = [0u8; 24];
60    stream.read_exact(&mut head)?;
61    ensure!(
62        head[..4] == magic,
63        "peer message carries a foreign network magic"
64    );
65    let command = command_name(&head[4..16])?;
66    let length = usize::try_from(u32::from_le_bytes([head[16], head[17], head[18], head[19]]))?;
67    ensure!(
68        length <= P2P_MAX_PAYLOAD_BYTES,
69        "peer message of {length} bytes exceeds the client payload limit"
70    );
71    let mut payload = Vec::new();
72    payload.try_reserve_exact(length)?;
73    payload.resize(length, 0);
74    stream.read_exact(&mut payload)?;
75    let digest = sha256d::Hash::hash(&payload);
76    ensure!(
77        digest.as_byte_array()[..4] == head[20..24],
78        "peer message checksum mismatch"
79    );
80    decode(&command, payload)
81}
82
83fn command_name(raw: &[u8]) -> Result<String, Error> {
84    let end = raw.iter().take_while(|byte| **byte != 0).count();
85    ensure!(
86        raw[end..].iter().all(|byte| *byte == 0),
87        "peer command has bytes after its terminator"
88    );
89    let name = std::str::from_utf8(&raw[..end])?;
90    ensure!(
91        name.bytes()
92            .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit()),
93        "peer command is not lowercase ascii"
94    );
95    Ok(name.to_owned())
96}
97
98fn decode(command: &str, payload: Vec<u8>) -> Result<Message, Error> {
99    let mut cursor = payload.as_slice();
100    let message = match command {
101        "version" => Message::Version(exact(&mut cursor)?),
102        "verack" => Message::Verack,
103        "ping" => Message::Ping(exact(&mut cursor)?),
104        "pong" => Message::Pong(exact(&mut cursor)?),
105        "addr" => Message::Addr(bounded_list(&mut cursor, P2P_MAX_ADDRESSES, "addr")?),
106        "addrv2" => Message::AddrV2(bounded_list(&mut cursor, P2P_MAX_ADDRESSES, "addrv2")?),
107        "inv" => Message::Inv(bounded_list(&mut cursor, P2P_MAX_INVENTORY, "inv")?),
108        "notfound" => Message::NotFound(bounded_list(&mut cursor, P2P_MAX_INVENTORY, "notfound")?),
109        "headers" => Message::Headers(headers(&mut cursor)?),
110        "block" => return Ok(Message::Block(payload)),
111        other => return Ok(Message::Other(other.to_owned())),
112    };
113    ensure!(
114        cursor.is_empty(),
115        "peer {command} message has trailing bytes"
116    );
117    Ok(message)
118}
119
120fn exact<T: Decodable>(cursor: &mut &[u8]) -> Result<T, Error> {
121    Ok(T::consensus_decode_from_finite_reader(cursor)?)
122}
123
124fn bounded_list<T: Decodable>(
125    cursor: &mut &[u8],
126    limit: usize,
127    command: &str,
128) -> Result<Vec<T>, Error> {
129    let count = usize::try_from(VarInt::consensus_decode_from_finite_reader(cursor)?.0)?;
130    ensure!(
131        count <= limit,
132        "peer {command} message lists {count} items, above the client limit of {limit}"
133    );
134    let mut rows = Vec::new();
135    rows.try_reserve_exact(count)?;
136    for _index in 0..count {
137        rows.push(exact(cursor)?);
138    }
139    Ok(rows)
140}
141
142fn headers(cursor: &mut &[u8]) -> Result<Vec<Header>, Error> {
143    let count = usize::try_from(VarInt::consensus_decode_from_finite_reader(cursor)?.0)?;
144    ensure!(
145        count <= P2P_MAX_HEADERS_PER_MESSAGE,
146        "peer headers message lists {count} headers, above the protocol limit"
147    );
148    let mut rows = Vec::new();
149    rows.try_reserve_exact(count)?;
150    for _index in 0..count {
151        rows.push(exact::<Header>(cursor)?);
152        let transactions: u8 = exact(cursor)?;
153        ensure!(
154            transactions == 0,
155            "peer headers message embeds transactions"
156        );
157    }
158    Ok(rows)
159}