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)]
132#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
133pub enum MessageType {
134    /// Request
135    Request,
136    /// Response
137    Response,
138    /// Notification
139    Notification,
140}
141
142impl MessageType {
143    /// Returns if the message requires a response
144    pub const fn have_to_return_data(&self) -> bool {
145        match self {
146            Self::Request => true,
147            Self::Response | Self::Notification => false,
148        }
149    }
150
151    /// Returns the `MessageType` given the flags and `have_to_return_data` fields
152    pub const fn from_flags_and_have_to_return(
153        flags: Flags,
154        have_to_return: bool,
155    ) -> Result<Self, BucketError> {
156        Ok(match (flags, have_to_return) {
157            (Flags::REQUEST, true) => Self::Request,
158            (Flags::REQUEST, false) => Self::Notification,
159            (Flags::RESPONSE, false) => Self::Response,
160            _ => {
161                return Err(BucketError::InvalidHeaderFlags(
162                    "Unable to assign a message type to this bucket",
163                ))
164            }
165        })
166    }
167
168    pub const fn as_flags(&self) -> Flags {
169        match self {
170            Self::Request | Self::Notification => Flags::REQUEST,
171            Self::Response => Flags::RESPONSE,
172        }
173    }
174}
175
176#[derive(Debug)]
177pub struct BucketBuilder<C> {
178    signature: Option<u64>,
179    ty: Option<MessageType>,
180    command: Option<C>,
181    return_code: Option<i32>,
182    protocol_version: Option<u32>,
183    body: Option<Bytes>,
184}
185
186impl<C: LevinCommand> BucketBuilder<C> {
187    pub const fn new(protocol: &Protocol) -> Self {
188        Self {
189            signature: Some(protocol.signature),
190            ty: None,
191            command: None,
192            return_code: None,
193            protocol_version: Some(protocol.version),
194            body: None,
195        }
196    }
197
198    pub const fn set_signature(&mut self, sig: u64) {
199        self.signature = Some(sig);
200    }
201
202    pub const fn set_message_type(&mut self, ty: MessageType) {
203        self.ty = Some(ty);
204    }
205
206    pub fn set_command(&mut self, command: C) {
207        self.command = Some(command);
208    }
209
210    pub const fn set_return_code(&mut self, code: i32) {
211        self.return_code = Some(code);
212    }
213
214    pub const fn set_protocol_version(&mut self, version: u32) {
215        self.protocol_version = Some(version);
216    }
217
218    pub fn set_body(&mut self, body: Bytes) {
219        self.body = Some(body);
220    }
221
222    pub fn finish(self) -> Bucket<C> {
223        let body = self.body.unwrap();
224        let ty = self.ty.unwrap();
225        Bucket {
226            header: BucketHead {
227                signature: self.signature.unwrap(),
228                size: usize_to_u64(body.len()),
229                have_to_return_data: ty.have_to_return_data(),
230                command: self.command.unwrap(),
231                return_code: self.return_code.unwrap(),
232                flags: ty.as_flags(),
233                protocol_version: self.protocol_version.unwrap(),
234            },
235            body,
236        }
237    }
238}
239
240/// A levin body
241pub trait LevinBody: Sized {
242    type Command: LevinCommand + Debug;
243
244    /// Decodes the message from the data in the header
245    fn decode_message<B: Buf>(
246        body: &mut B,
247        ty: MessageType,
248        command: Self::Command,
249    ) -> Result<Self, BucketError>;
250
251    /// Encodes the message
252    fn encode(self, builder: &mut BucketBuilder<Self::Command>) -> Result<(), BucketError>;
253}
254
255/// The levin commands.
256///
257/// Implementers should account for all possible u32 values, this means
258/// you will probably need some sort of `Unknown` variant.
259pub trait LevinCommand: From<u32> + Into<u32> + PartialEq + Clone {
260    /// Returns the size limit for this command.
261    ///
262    /// must be less than [`usize::MAX`]
263    fn bucket_size_limit(&self) -> u64;
264    /// Returns if this is a handshake
265    fn is_handshake(&self) -> bool;
266}