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
228            C::Handshake | C::TimedSync | C::Ping | C::SupportFlags | C::Unknown(_) => {
229                return Err(BucketError::UnknownCommand);
230            }
231        })
232    }
233
234    fn build(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
235        use LevinCommand as C;
236
237        match self {
238            Self::NewBlock(val) => build_message(C::NewBlock, val, builder)?,
239            Self::NewTransactions(val) => {
240                build_message(C::NewTransactions, val, builder)?;
241            }
242            Self::GetObjectsRequest(val) => {
243                build_message(C::GetObjectsRequest, val, builder)?;
244            }
245            Self::GetObjectsResponse(val) => {
246                build_message(C::GetObjectsResponse, val, builder)?;
247            }
248            Self::ChainRequest(val) => build_message(C::ChainRequest, val, builder)?,
249            Self::ChainEntryResponse(val) => {
250                build_message(C::ChainResponse, val, builder)?;
251            }
252            Self::NewFluffyBlock(val) => build_message(C::NewFluffyBlock, val, builder)?,
253            Self::FluffyMissingTransactionsRequest(val) => {
254                build_message(C::FluffyMissingTxsRequest, val, builder)?;
255            }
256            Self::GetTxPoolCompliment(val) => {
257                build_message(C::GetTxPoolCompliment, val, builder)?;
258            }
259        }
260        Ok(())
261    }
262}
263
264#[derive(Debug, Clone)]
265pub enum AdminRequestMessage {
266    Handshake(HandshakeRequest),
267    Ping,
268    SupportFlags,
269    TimedSync(TimedSyncRequest),
270}
271
272impl AdminRequestMessage {
273    pub const fn command(&self) -> LevinCommand {
274        use LevinCommand as C;
275
276        match self {
277            Self::Handshake(_) => C::Handshake,
278            Self::Ping => C::Ping,
279            Self::SupportFlags => C::SupportFlags,
280            Self::TimedSync(_) => C::TimedSync,
281        }
282    }
283
284    fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
285        use LevinCommand as C;
286
287        Ok(match command {
288            C::Handshake => decode_message(AdminRequestMessage::Handshake, buf)?,
289            C::TimedSync => decode_message(AdminRequestMessage::TimedSync, buf)?,
290            C::Ping => {
291                cuprate_epee_encoding::from_bytes::<EmptyMessage, _>(buf)
292                    .map_err(|e| BucketError::BodyDecodingError(e.into()))?;
293
294                Self::Ping
295            }
296            C::SupportFlags => {
297                cuprate_epee_encoding::from_bytes::<EmptyMessage, _>(buf)
298                    .map_err(|e| BucketError::BodyDecodingError(e.into()))?;
299
300                Self::SupportFlags
301            }
302
303            C::NewBlock
304            | C::NewTransactions
305            | C::GetObjectsRequest
306            | C::GetObjectsResponse
307            | C::ChainRequest
308            | C::ChainResponse
309            | C::NewFluffyBlock
310            | C::FluffyMissingTxsRequest
311            | C::GetTxPoolCompliment
312            | C::Unknown(_) => return Err(BucketError::UnknownCommand),
313        })
314    }
315
316    fn build(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
317        use LevinCommand as C;
318
319        match self {
320            Self::Handshake(val) => build_message(C::Handshake, val, builder)?,
321            Self::TimedSync(val) => build_message(C::TimedSync, val, builder)?,
322            Self::Ping => build_message(C::Ping, EmptyMessage, builder)?,
323            Self::SupportFlags => {
324                build_message(C::SupportFlags, EmptyMessage, builder)?;
325            }
326        }
327        Ok(())
328    }
329}
330
331#[derive(Debug, Clone)]
332pub enum AdminResponseMessage {
333    Handshake(HandshakeResponse),
334    Ping(PingResponse),
335    SupportFlags(SupportFlagsResponse),
336    TimedSync(TimedSyncResponse),
337}
338
339impl AdminResponseMessage {
340    pub const fn command(&self) -> LevinCommand {
341        use LevinCommand as C;
342
343        match self {
344            Self::Handshake(_) => C::Handshake,
345            Self::Ping(_) => C::Ping,
346            Self::SupportFlags(_) => C::SupportFlags,
347            Self::TimedSync(_) => C::TimedSync,
348        }
349    }
350
351    fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
352        use LevinCommand as C;
353
354        Ok(match command {
355            C::Handshake => decode_message(AdminResponseMessage::Handshake, buf)?,
356            C::TimedSync => decode_message(AdminResponseMessage::TimedSync, buf)?,
357            C::Ping => decode_message(AdminResponseMessage::Ping, buf)?,
358            C::SupportFlags => decode_message(AdminResponseMessage::SupportFlags, buf)?,
359
360            C::NewBlock
361            | C::NewTransactions
362            | C::GetObjectsRequest
363            | C::GetObjectsResponse
364            | C::ChainRequest
365            | C::ChainResponse
366            | C::NewFluffyBlock
367            | C::FluffyMissingTxsRequest
368            | C::GetTxPoolCompliment
369            | C::Unknown(_) => return Err(BucketError::UnknownCommand),
370        })
371    }
372
373    fn build(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
374        use LevinCommand as C;
375
376        match self {
377            Self::Handshake(val) => build_message(C::Handshake, val, builder)?,
378            Self::TimedSync(val) => build_message(C::TimedSync, val, builder)?,
379            Self::Ping(val) => build_message(C::Ping, val, builder)?,
380            Self::SupportFlags(val) => {
381                build_message(C::SupportFlags, val, builder)?;
382            }
383        }
384        Ok(())
385    }
386}
387
388#[derive(Debug, Clone)]
389pub enum Message {
390    Request(AdminRequestMessage),
391    Response(AdminResponseMessage),
392    Protocol(ProtocolMessage),
393}
394
395impl Message {
396    pub const fn is_request(&self) -> bool {
397        matches!(self, Self::Request(_))
398    }
399
400    pub const fn is_response(&self) -> bool {
401        matches!(self, Self::Response(_))
402    }
403
404    pub const fn is_protocol(&self) -> bool {
405        matches!(self, Self::Protocol(_))
406    }
407
408    pub const fn command(&self) -> LevinCommand {
409        match self {
410            Self::Request(mes) => mes.command(),
411            Self::Response(mes) => mes.command(),
412            Self::Protocol(mes) => mes.command(),
413        }
414    }
415}
416
417impl LevinBody for Message {
418    type Command = LevinCommand;
419
420    fn decode_message<B: Buf>(
421        body: &mut B,
422        ty: MessageType,
423        command: LevinCommand,
424    ) -> Result<Self, BucketError> {
425        Ok(match ty {
426            MessageType::Request => Self::Request(AdminRequestMessage::decode(body, command)?),
427            MessageType::Response => Self::Response(AdminResponseMessage::decode(body, command)?),
428            MessageType::Notification => Self::Protocol(ProtocolMessage::decode(body, command)?),
429        })
430    }
431
432    fn encode(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
433        match self {
434            Self::Protocol(pro) => {
435                builder.set_message_type(MessageType::Notification);
436                builder.set_return_code(0);
437                pro.build(builder)
438            }
439            Self::Request(req) => {
440                builder.set_message_type(MessageType::Request);
441                builder.set_return_code(0);
442                req.build(builder)
443            }
444            Self::Response(res) => {
445                builder.set_message_type(MessageType::Response);
446                builder.set_return_code(1);
447                res.build(builder)
448            }
449        }
450    }
451}
452
453/// An internal empty message.
454///
455/// This represents P2P messages that have no fields as epee's binary format will still add a header
456/// for these objects, so we need to decode/encode a message.
457struct EmptyMessage;
458
459epee_object! {
460    EmptyMessage,
461}