cuprate_levin/
lib.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//! # Rust Levin
17//!
18//! A crate for working with the Levin protocol in Rust.
19//!
20//! The Levin protocol is a network protocol used in the Monero cryptocurrency. It is used for
21//! peer-to-peer communication between nodes. This crate provides a Rust implementation of the Levin
22//! header serialization and allows developers to define their own bucket bodies, for a complete
23//! monero protocol crate see: monero-wire.
24//!
25//! ## License
26//!
27//! This project is licensed under the MIT License.
28
29// Coding conventions
30#![forbid(unsafe_code)]
31#![deny(non_upper_case_globals)]
32#![deny(non_camel_case_types)]
33#![deny(unused_mut)]
34//#![deny(missing_docs)]
35
36cfg_if::cfg_if! {
37    // Used in `tests/`.
38    if #[cfg(test)] {
39        use futures as _;
40        use proptest as _;
41        use rand as _;
42        use tokio as _;
43    }
44}
45
46use std::fmt::Debug;
47
48use bytes::{Buf, Bytes};
49use thiserror::Error;
50
51use cuprate_helper::cast::usize_to_u64;
52
53pub mod codec;
54pub mod header;
55pub mod message;
56
57pub use codec::*;
58pub use header::BucketHead;
59pub use message::LevinMessage;
60
61use header::Flags;
62
63/// The version field for bucket headers.
64const MONERO_PROTOCOL_VERSION: u32 = 1;
65/// The signature field for bucket headers, will be constant for all peers using the Monero levin
66/// protocol.
67const MONERO_LEVIN_SIGNATURE: u64 = 0x0101010101012101;
68/// Maximum size a bucket can be before a handshake.
69const MONERO_MAX_PACKET_SIZE_BEFORE_HANDSHAKE: u64 = 256 * 1000; // 256 KiB
70/// Maximum size a bucket can be after a handshake.
71const MONERO_MAX_PACKET_SIZE: u64 = 100_000_000; // 100MB
72
73/// Possible Errors when working with levin buckets
74#[derive(Error, Debug)]
75pub enum BucketError {
76    /// Invalid header flags
77    #[error("Invalid header flags: {0}")]
78    InvalidHeaderFlags(&'static str),
79    /// Levin bucket exceeded max size
80    #[error("Levin bucket exceeded max size")]
81    BucketExceededMaxSize,
82    /// Invalid Fragmented Message
83    #[error("Levin fragmented message was invalid: {0}")]
84    InvalidFragmentedMessage(&'static str),
85    /// The Header did not have the correct signature
86    #[error("Levin header had incorrect signature")]
87    InvalidHeaderSignature,
88    /// Error decoding the body
89    #[error("Error decoding bucket body: {0}")]
90    BodyDecodingError(Box<dyn std::error::Error + Send + Sync>),
91    /// Unknown command ID
92    #[error("Unknown command ID")]
93    UnknownCommand,
94    /// I/O error
95    #[error("I/O error: {0}")]
96    IO(#[from] std::io::Error),
97}
98
99/// Levin protocol settings, allows setting custom parameters.
100///
101/// For Monero use [`Protocol::default()`]
102#[derive(Debug, Clone, Copy, Eq, PartialEq)]
103pub struct Protocol {
104    pub version: u32,
105    pub signature: u64,
106    pub max_packet_size_before_handshake: u64,
107    pub max_packet_size: u64,
108}
109
110impl Default for Protocol {
111    fn default() -> Self {
112        Self {
113            version: MONERO_PROTOCOL_VERSION,
114            signature: MONERO_LEVIN_SIGNATURE,
115            max_packet_size_before_handshake: MONERO_MAX_PACKET_SIZE_BEFORE_HANDSHAKE,
116            max_packet_size: MONERO_MAX_PACKET_SIZE,
117        }
118    }
119}
120
121/// A levin Bucket
122#[derive(Debug, Clone)]
123pub struct Bucket<C> {
124    /// The bucket header
125    pub header: BucketHead<C>,
126    /// The bucket body
127    pub body: Bytes,
128}
129
130/// An enum representing if the message is a request, response or notification.
131#[derive(Debug, Eq, PartialEq, Clone, Copy)]
132pub enum MessageType {
133    /// Request
134    Request,
135    /// Response
136    Response,
137    /// Notification
138    Notification,
139}
140
141impl MessageType {
142    /// Returns if the message requires a response
143    pub const fn have_to_return_data(&self) -> bool {
144        match self {
145            Self::Request => true,
146            Self::Response | Self::Notification => false,
147        }
148    }
149
150    /// Returns the `MessageType` given the flags and `have_to_return_data` fields
151    pub const fn from_flags_and_have_to_return(
152        flags: Flags,
153        have_to_return: bool,
154    ) -> Result<Self, BucketError> {
155        Ok(match (flags, have_to_return) {
156            (Flags::REQUEST, true) => Self::Request,
157            (Flags::REQUEST, false) => Self::Notification,
158            (Flags::RESPONSE, false) => Self::Response,
159            _ => {
160                return Err(BucketError::InvalidHeaderFlags(
161                    "Unable to assign a message type to this bucket",
162                ))
163            }
164        })
165    }
166
167    pub const fn as_flags(&self) -> Flags {
168        match self {
169            Self::Request | Self::Notification => Flags::REQUEST,
170            Self::Response => Flags::RESPONSE,
171        }
172    }
173}
174
175#[derive(Debug)]
176pub struct BucketBuilder<C> {
177    signature: Option<u64>,
178    ty: Option<MessageType>,
179    command: Option<C>,
180    return_code: Option<i32>,
181    protocol_version: Option<u32>,
182    body: Option<Bytes>,
183}
184
185impl<C: LevinCommand> BucketBuilder<C> {
186    pub const fn new(protocol: &Protocol) -> Self {
187        Self {
188            signature: Some(protocol.signature),
189            ty: None,
190            command: None,
191            return_code: None,
192            protocol_version: Some(protocol.version),
193            body: None,
194        }
195    }
196
197    pub fn set_signature(&mut self, sig: u64) {
198        self.signature = Some(sig);
199    }
200
201    pub fn set_message_type(&mut self, ty: MessageType) {
202        self.ty = Some(ty);
203    }
204
205    pub fn set_command(&mut self, command: C) {
206        self.command = Some(command);
207    }
208
209    pub fn set_return_code(&mut self, code: i32) {
210        self.return_code = Some(code);
211    }
212
213    pub fn set_protocol_version(&mut self, version: u32) {
214        self.protocol_version = Some(version);
215    }
216
217    pub fn set_body(&mut self, body: Bytes) {
218        self.body = Some(body);
219    }
220
221    pub fn finish(self) -> Bucket<C> {
222        let body = self.body.unwrap();
223        let ty = self.ty.unwrap();
224        Bucket {
225            header: BucketHead {
226                signature: self.signature.unwrap(),
227                size: usize_to_u64(body.len()),
228                have_to_return_data: ty.have_to_return_data(),
229                command: self.command.unwrap(),
230                return_code: self.return_code.unwrap(),
231                flags: ty.as_flags(),
232                protocol_version: self.protocol_version.unwrap(),
233            },
234            body,
235        }
236    }
237}
238
239/// A levin body
240pub trait LevinBody: Sized {
241    type Command: LevinCommand + Debug;
242
243    /// Decodes the message from the data in the header
244    fn decode_message<B: Buf>(
245        body: &mut B,
246        ty: MessageType,
247        command: Self::Command,
248    ) -> Result<Self, BucketError>;
249
250    /// Encodes the message
251    fn encode(self, builder: &mut BucketBuilder<Self::Command>) -> Result<(), BucketError>;
252}
253
254/// The levin commands.
255///
256/// Implementers should account for all possible u32 values, this means
257/// you will probably need some sort of `Unknown` variant.
258pub trait LevinCommand: From<u32> + Into<u32> + PartialEq + Clone {
259    /// Returns the size limit for this command.
260    ///
261    /// must be less than [`usize::MAX`]
262    fn bucket_size_limit(&self) -> u64;
263    /// Returns if this is a handshake
264    fn is_handshake(&self) -> bool;
265}