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