cuprate_wire/
p2p.rs

1// Rust Levin Library
2// Written in 2023 by
3//   Cuprate Contributors
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15
16//! This module defines a Monero `Message` enum which contains
17//! every possible Monero network message (levin body)
18
19use std::fmt::Formatter;
20
21use bytes::{Buf, BytesMut};
22
23use cuprate_epee_encoding::epee_object;
24use cuprate_levin::{
25    BucketBuilder, BucketError, LevinBody, LevinCommand as LevinCommandTrait, MessageType,
26};
27
28pub mod admin;
29pub mod common;
30pub mod protocol;
31
32use admin::*;
33pub use common::{BasicNodeData, CoreSyncData, PeerListEntryBase};
34use protocol::*;
35
36#[derive(Copy, Clone, Eq, PartialEq, Debug)]
37#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
38pub enum LevinCommand {
39    Handshake,
40    TimedSync,
41    Ping,
42    SupportFlags,
43
44    NewBlock,
45    NewTransactions,
46    GetObjectsRequest,
47    GetObjectsResponse,
48    ChainRequest,
49    ChainResponse,
50    NewFluffyBlock,
51    FluffyMissingTxsRequest,
52    GetTxPoolCompliment,
53
54    Unknown(u32),
55}
56
57impl std::fmt::Display for LevinCommand {
58    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59        if let Self::Unknown(id) = self {
60            return f.write_str(&format!("unknown id: {id}"));
61        }
62
63        f.write_str(match self {
64            Self::Handshake => "handshake",
65            Self::TimedSync => "timed sync",
66            Self::Ping => "ping",
67            Self::SupportFlags => "support flags",
68
69            Self::NewBlock => "new block",
70            Self::NewTransactions => "new transactions",
71            Self::GetObjectsRequest => "get objects request",
72            Self::GetObjectsResponse => "get objects response",
73            Self::ChainRequest => "chain request",
74            Self::ChainResponse => "chain response",
75            Self::NewFluffyBlock => "new fluffy block",
76            Self::FluffyMissingTxsRequest => "fluffy missing transaction request",
77            Self::GetTxPoolCompliment => "get transaction pool compliment",
78
79            Self::Unknown(_) => unreachable!(),
80        })
81    }
82}
83
84impl LevinCommandTrait for LevinCommand {
85    fn bucket_size_limit(&self) -> u64 {
86        // https://github.com/monero-project/monero/blob/00fd416a99686f0956361d1cd0337fe56e58d4a7/src/cryptonote_basic/connection_context.cpp#L37
87        #[expect(clippy::match_same_arms, reason = "formatting is more clear")]
88        match self {
89            Self::Handshake => 65536,
90            Self::TimedSync => 65536,
91            Self::Ping => 4096,
92            Self::SupportFlags => 4096,
93
94            Self::NewBlock => 1024 * 1024 * 128, // 128 MB (max packet is a bit less than 100 MB though)
95            Self::NewTransactions => 1024 * 1024 * 128, // 128 MB (max packet is a bit less than 100 MB though)
96            Self::GetObjectsRequest => 1024 * 1024 * 2, // 2 MB
97            Self::GetObjectsResponse => 1024 * 1024 * 128, // 128 MB (max packet is a bit less than 100 MB though)
98            Self::ChainRequest => 512 * 1024,              // 512 kB
99            Self::ChainResponse => 1024 * 1024 * 4,        // 4 MB
100            Self::NewFluffyBlock => 1024 * 1024 * 4,       // 4 MB
101            Self::FluffyMissingTxsRequest => 1024 * 1024,  // 1 MB
102            Self::GetTxPoolCompliment => 1024 * 1024 * 4,  // 4 MB
103
104            Self::Unknown(_) => u64::MAX,
105        }
106    }
107
108    fn is_handshake(&self) -> bool {
109        matches!(self, Self::Handshake)
110    }
111}
112
113impl From<u32> for LevinCommand {
114    fn from(value: u32) -> Self {
115        match value {
116            1001 => Self::Handshake,
117            1002 => Self::TimedSync,
118            1003 => Self::Ping,
119            1007 => Self::SupportFlags,
120
121            2001 => Self::NewBlock,
122            2002 => Self::NewTransactions,
123            2003 => Self::GetObjectsRequest,
124            2004 => Self::GetObjectsResponse,
125            2006 => Self::ChainRequest,
126            2007 => Self::ChainResponse,
127            2008 => Self::NewFluffyBlock,
128            2009 => Self::FluffyMissingTxsRequest,
129            2010 => Self::GetTxPoolCompliment,
130
131            x => Self::Unknown(x),
132        }
133    }
134}
135
136impl From<LevinCommand> for u32 {
137    fn from(value: LevinCommand) -> Self {
138        match value {
139            LevinCommand::Handshake => 1001,
140            LevinCommand::TimedSync => 1002,
141            LevinCommand::Ping => 1003,
142            LevinCommand::SupportFlags => 1007,
143
144            LevinCommand::NewBlock => 2001,
145            LevinCommand::NewTransactions => 2002,
146            LevinCommand::GetObjectsRequest => 2003,
147            LevinCommand::GetObjectsResponse => 2004,
148            LevinCommand::ChainRequest => 2006,
149            LevinCommand::ChainResponse => 2007,
150            LevinCommand::NewFluffyBlock => 2008,
151            LevinCommand::FluffyMissingTxsRequest => 2009,
152            LevinCommand::GetTxPoolCompliment => 2010,
153
154            LevinCommand::Unknown(x) => x,
155        }
156    }
157}
158
159fn decode_message<B: Buf, T: cuprate_epee_encoding::EpeeObject, Ret>(
160    ret: impl FnOnce(T) -> Ret,
161    buf: &mut B,
162) -> Result<Ret, BucketError> {
163    let t = cuprate_epee_encoding::from_bytes(buf)
164        .map_err(|e| BucketError::BodyDecodingError(e.into()))?;
165    Ok(ret(t))
166}
167
168fn build_message<T: cuprate_epee_encoding::EpeeObject>(
169    id: LevinCommand,
170    val: T,
171    builder: &mut BucketBuilder<LevinCommand>,
172) -> Result<(), BucketError> {
173    builder.set_command(id);
174    builder.set_body(
175        cuprate_epee_encoding::to_bytes(val)
176            .map(BytesMut::freeze)
177            .map_err(|e| BucketError::BodyDecodingError(e.into()))?,
178    );
179    Ok(())
180}
181
182#[derive(Debug, Clone)]
183pub enum ProtocolMessage {
184    NewBlock(NewBlock),
185    NewFluffyBlock(NewFluffyBlock),
186    GetObjectsRequest(GetObjectsRequest),
187    GetObjectsResponse(GetObjectsResponse),
188    ChainRequest(ChainRequest),
189    ChainEntryResponse(ChainResponse),
190    NewTransactions(NewTransactions),
191    FluffyMissingTransactionsRequest(FluffyMissingTransactionsRequest),
192    GetTxPoolCompliment(GetTxPoolCompliment),
193}
194
195impl ProtocolMessage {
196    pub const fn command(&self) -> LevinCommand {
197        use LevinCommand as C;
198
199        match self {
200            Self::NewBlock(_) => C::NewBlock,
201            Self::NewFluffyBlock(_) => C::NewFluffyBlock,
202            Self::GetObjectsRequest(_) => C::GetObjectsRequest,
203            Self::GetObjectsResponse(_) => C::GetObjectsResponse,
204            Self::ChainRequest(_) => C::ChainRequest,
205            Self::ChainEntryResponse(_) => C::ChainResponse,
206            Self::NewTransactions(_) => C::NewTransactions,
207            Self::FluffyMissingTransactionsRequest(_) => C::FluffyMissingTxsRequest,
208            Self::GetTxPoolCompliment(_) => C::GetTxPoolCompliment,
209        }
210    }
211
212    fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
213        use LevinCommand as C;
214
215        Ok(match command {
216            C::NewBlock => decode_message(ProtocolMessage::NewBlock, buf)?,
217            C::NewTransactions => decode_message(ProtocolMessage::NewTransactions, buf)?,
218            C::GetObjectsRequest => decode_message(ProtocolMessage::GetObjectsRequest, buf)?,
219            C::GetObjectsResponse => decode_message(ProtocolMessage::GetObjectsResponse, buf)?,
220            C::ChainRequest => decode_message(ProtocolMessage::ChainRequest, buf)?,
221            C::ChainResponse => decode_message(ProtocolMessage::ChainEntryResponse, buf)?,
222            C::NewFluffyBlock => decode_message(ProtocolMessage::NewFluffyBlock, buf)?,
223            C::FluffyMissingTxsRequest => {
224                decode_message(ProtocolMessage::FluffyMissingTransactionsRequest, buf)?
225            }
226            C::GetTxPoolCompliment => decode_message(ProtocolMessage::GetTxPoolCompliment, buf)?,
227            _ => return Err(BucketError::UnknownCommand),
228        })
229    }
230
231    fn build(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
232        use LevinCommand as C;
233
234        match self {
235            Self::NewBlock(val) => build_message(C::NewBlock, val, builder)?,
236            Self::NewTransactions(val) => {
237                build_message(C::NewTransactions, val, builder)?;
238            }
239            Self::GetObjectsRequest(val) => {
240                build_message(C::GetObjectsRequest, val, builder)?;
241            }
242            Self::GetObjectsResponse(val) => {
243                build_message(C::GetObjectsResponse, val, builder)?;
244            }
245            Self::ChainRequest(val) => build_message(C::ChainRequest, val, builder)?,
246            Self::ChainEntryResponse(val) => {
247                build_message(C::ChainResponse, val, builder)?;
248            }
249            Self::NewFluffyBlock(val) => build_message(C::NewFluffyBlock, val, builder)?,
250            Self::FluffyMissingTransactionsRequest(val) => {
251                build_message(C::FluffyMissingTxsRequest, val, builder)?;
252            }
253            Self::GetTxPoolCompliment(val) => {
254                build_message(C::GetTxPoolCompliment, val, builder)?;
255            }
256        }
257        Ok(())
258    }
259}
260
261#[derive(Debug, Clone)]
262pub enum AdminRequestMessage {
263    Handshake(HandshakeRequest),
264    Ping,
265    SupportFlags,
266    TimedSync(TimedSyncRequest),
267}
268
269impl AdminRequestMessage {
270    pub const fn command(&self) -> LevinCommand {
271        use LevinCommand as C;
272
273        match self {
274            Self::Handshake(_) => C::Handshake,
275            Self::Ping => C::Ping,
276            Self::SupportFlags => C::SupportFlags,
277            Self::TimedSync(_) => C::TimedSync,
278        }
279    }
280
281    fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
282        use LevinCommand as C;
283
284        Ok(match command {
285            C::Handshake => decode_message(AdminRequestMessage::Handshake, buf)?,
286            C::TimedSync => decode_message(AdminRequestMessage::TimedSync, buf)?,
287            C::Ping => {
288                cuprate_epee_encoding::from_bytes::<EmptyMessage, _>(buf)
289                    .map_err(|e| BucketError::BodyDecodingError(e.into()))?;
290
291                Self::Ping
292            }
293            C::SupportFlags => {
294                cuprate_epee_encoding::from_bytes::<EmptyMessage, _>(buf)
295                    .map_err(|e| BucketError::BodyDecodingError(e.into()))?;
296
297                Self::SupportFlags
298            }
299            _ => return Err(BucketError::UnknownCommand),
300        })
301    }
302
303    fn build(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
304        use LevinCommand as C;
305
306        match self {
307            Self::Handshake(val) => build_message(C::Handshake, val, builder)?,
308            Self::TimedSync(val) => build_message(C::TimedSync, val, builder)?,
309            Self::Ping => build_message(C::Ping, EmptyMessage, builder)?,
310            Self::SupportFlags => {
311                build_message(C::SupportFlags, EmptyMessage, builder)?;
312            }
313        }
314        Ok(())
315    }
316}
317
318#[derive(Debug, Clone)]
319pub enum AdminResponseMessage {
320    Handshake(HandshakeResponse),
321    Ping(PingResponse),
322    SupportFlags(SupportFlagsResponse),
323    TimedSync(TimedSyncResponse),
324}
325
326impl AdminResponseMessage {
327    pub const fn command(&self) -> LevinCommand {
328        use LevinCommand as C;
329
330        match self {
331            Self::Handshake(_) => C::Handshake,
332            Self::Ping(_) => C::Ping,
333            Self::SupportFlags(_) => C::SupportFlags,
334            Self::TimedSync(_) => C::TimedSync,
335        }
336    }
337
338    fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
339        use LevinCommand as C;
340
341        Ok(match command {
342            C::Handshake => decode_message(AdminResponseMessage::Handshake, buf)?,
343            C::TimedSync => decode_message(AdminResponseMessage::TimedSync, buf)?,
344            C::Ping => decode_message(AdminResponseMessage::Ping, buf)?,
345            C::SupportFlags => decode_message(AdminResponseMessage::SupportFlags, buf)?,
346            _ => return Err(BucketError::UnknownCommand),
347        })
348    }
349
350    fn build(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
351        use LevinCommand as C;
352
353        match self {
354            Self::Handshake(val) => build_message(C::Handshake, val, builder)?,
355            Self::TimedSync(val) => build_message(C::TimedSync, val, builder)?,
356            Self::Ping(val) => build_message(C::Ping, val, builder)?,
357            Self::SupportFlags(val) => {
358                build_message(C::SupportFlags, val, builder)?;
359            }
360        }
361        Ok(())
362    }
363}
364
365#[derive(Debug, Clone)]
366pub enum Message {
367    Request(AdminRequestMessage),
368    Response(AdminResponseMessage),
369    Protocol(ProtocolMessage),
370}
371
372impl Message {
373    pub const fn is_request(&self) -> bool {
374        matches!(self, Self::Request(_))
375    }
376
377    pub const fn is_response(&self) -> bool {
378        matches!(self, Self::Response(_))
379    }
380
381    pub const fn is_protocol(&self) -> bool {
382        matches!(self, Self::Protocol(_))
383    }
384
385    pub const fn command(&self) -> LevinCommand {
386        match self {
387            Self::Request(mes) => mes.command(),
388            Self::Response(mes) => mes.command(),
389            Self::Protocol(mes) => mes.command(),
390        }
391    }
392}
393
394impl LevinBody for Message {
395    type Command = LevinCommand;
396
397    fn decode_message<B: Buf>(
398        body: &mut B,
399        ty: MessageType,
400        command: LevinCommand,
401    ) -> Result<Self, BucketError> {
402        Ok(match ty {
403            MessageType::Request => Self::Request(AdminRequestMessage::decode(body, command)?),
404            MessageType::Response => Self::Response(AdminResponseMessage::decode(body, command)?),
405            MessageType::Notification => Self::Protocol(ProtocolMessage::decode(body, command)?),
406        })
407    }
408
409    fn encode(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
410        match self {
411            Self::Protocol(pro) => {
412                builder.set_message_type(MessageType::Notification);
413                builder.set_return_code(0);
414                pro.build(builder)
415            }
416            Self::Request(req) => {
417                builder.set_message_type(MessageType::Request);
418                builder.set_return_code(0);
419                req.build(builder)
420            }
421            Self::Response(res) => {
422                builder.set_message_type(MessageType::Response);
423                builder.set_return_code(1);
424                res.build(builder)
425            }
426        }
427    }
428}
429
430/// An internal empty message.
431///
432/// This represents P2P messages that have no fields as epee's binary format will still add a header
433/// for these objects, so we need to decode/encode a message.
434struct EmptyMessage;
435
436epee_object! {
437    EmptyMessage,
438}