mirror of
https://github.com/hinto-janai/cuprate.git
synced 2024-12-24 20:49:23 +00:00
207 lines
5.9 KiB
Rust
207 lines
5.9 KiB
Rust
// Rust Levin Library
|
|
// Written in 2023 by
|
|
// Cuprate Contributors
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
|
|
//! # Rust Levin
|
|
//!
|
|
//! A crate for working with the Levin protocol in Rust.
|
|
//!
|
|
//! The Levin protocol is a network protocol used in the Monero cryptocurrency. It is used for
|
|
//! peer-to-peer communication between nodes. This crate provides a Rust implementation of the Levin
|
|
//! header serialization and allows developers to define their own bucket bodies, for a complete
|
|
//! monero protocol crate see: monero-wire.
|
|
//!
|
|
//! ## License
|
|
//!
|
|
//! This project is licensed under the MIT License.
|
|
|
|
// Coding conventions
|
|
#![forbid(unsafe_code)]
|
|
#![deny(non_upper_case_globals)]
|
|
#![deny(non_camel_case_types)]
|
|
#![deny(unused_mut)]
|
|
//#![deny(missing_docs)]
|
|
|
|
pub mod codec;
|
|
pub mod header;
|
|
|
|
pub use codec::LevinCodec;
|
|
pub use header::BucketHead;
|
|
|
|
use std::fmt::Debug;
|
|
|
|
use thiserror::Error;
|
|
|
|
const PROTOCOL_VERSION: u32 = 1;
|
|
const LEVIN_SIGNATURE: u64 = 0x0101010101012101;
|
|
const LEVIN_DEFAULT_MAX_PACKET_SIZE: u64 = 100_000_000; // 100MB
|
|
|
|
/// Possible Errors when working with levin buckets
|
|
#[derive(Error, Debug)]
|
|
pub enum BucketError {
|
|
/// Invalid header flags
|
|
#[error("Invalid header flags: {0}")]
|
|
InvalidHeaderFlags(&'static str),
|
|
/// Levin bucket exceeded max size
|
|
#[error("Levin bucket exceeded max size")]
|
|
BucketExceededMaxSize,
|
|
/// Invalid Fragmented Message
|
|
#[error("Levin fragmented message was invalid: {0}")]
|
|
InvalidFragmentedMessage(&'static str),
|
|
/// Error decoding the body
|
|
#[error("Error decoding bucket body: {0}")]
|
|
BodyDecodingError(Box<dyn std::error::Error>),
|
|
/// The levin command is unknown
|
|
#[error("The levin command is unknown")]
|
|
UnknownCommand,
|
|
/// I/O error
|
|
#[error("I/O error: {0}")]
|
|
IO(#[from] std::io::Error),
|
|
}
|
|
|
|
/// A levin Bucket
|
|
#[derive(Debug)]
|
|
pub struct Bucket {
|
|
/// The bucket header
|
|
pub header: BucketHead,
|
|
/// The bucket body
|
|
pub body: Vec<u8>,
|
|
}
|
|
|
|
/// An enum representing if the message is a request, response or notification.
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
pub enum MessageType {
|
|
/// Request
|
|
Request,
|
|
/// Response
|
|
Response,
|
|
/// Notification
|
|
Notification,
|
|
}
|
|
|
|
impl MessageType {
|
|
/// Returns if the message requires a response
|
|
pub fn have_to_return_data(&self) -> bool {
|
|
match self {
|
|
MessageType::Request => true,
|
|
MessageType::Response | MessageType::Notification => false,
|
|
}
|
|
}
|
|
|
|
/// Returns the `MessageType` given the flags and have_to_return_data fields
|
|
pub fn from_flags_and_have_to_return(
|
|
flags: header::Flags,
|
|
have_to_return: bool,
|
|
) -> Result<Self, BucketError> {
|
|
if flags.request && have_to_return {
|
|
Ok(MessageType::Request)
|
|
} else if flags.request {
|
|
Ok(MessageType::Notification)
|
|
} else if flags.response && !have_to_return {
|
|
Ok(MessageType::Response)
|
|
} else {
|
|
Err(BucketError::InvalidHeaderFlags(
|
|
"Unable to assign a message type to this bucket",
|
|
))
|
|
}
|
|
}
|
|
|
|
pub fn as_flags(&self) -> header::Flags {
|
|
match self {
|
|
MessageType::Request | MessageType::Notification => header::Flags {
|
|
request: true,
|
|
..Default::default()
|
|
},
|
|
MessageType::Response => header::Flags {
|
|
response: true,
|
|
..Default::default()
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct BucketBuilder {
|
|
signature: Option<u64>,
|
|
ty: Option<MessageType>,
|
|
command: Option<u32>,
|
|
return_code: Option<i32>,
|
|
protocol_version: Option<u32>,
|
|
body: Option<Vec<u8>>,
|
|
}
|
|
|
|
impl Default for BucketBuilder {
|
|
fn default() -> Self {
|
|
Self {
|
|
signature: Some(LEVIN_SIGNATURE),
|
|
ty: None,
|
|
command: None,
|
|
return_code: None,
|
|
protocol_version: Some(PROTOCOL_VERSION),
|
|
body: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl BucketBuilder {
|
|
pub fn set_signature(&mut self, sig: u64) {
|
|
self.signature = Some(sig)
|
|
}
|
|
|
|
pub fn set_message_type(&mut self, ty: MessageType) {
|
|
self.ty = Some(ty)
|
|
}
|
|
|
|
pub fn set_command(&mut self, command: u32) {
|
|
self.command = Some(command)
|
|
}
|
|
|
|
pub fn set_return_code(&mut self, code: i32) {
|
|
self.return_code = Some(code)
|
|
}
|
|
|
|
pub fn set_protocol_version(&mut self, version: u32) {
|
|
self.protocol_version = Some(version)
|
|
}
|
|
|
|
pub fn set_body(&mut self, body: Vec<u8>) {
|
|
self.body = Some(body)
|
|
}
|
|
|
|
pub fn finish(self) -> Bucket {
|
|
let body = self.body.unwrap();
|
|
let ty = self.ty.unwrap();
|
|
Bucket {
|
|
header: BucketHead {
|
|
signature: self.signature.unwrap(),
|
|
size: body.len().try_into().unwrap(),
|
|
have_to_return_data: ty.have_to_return_data(),
|
|
command: self.command.unwrap(),
|
|
return_code: self.return_code.unwrap(),
|
|
flags: ty.as_flags(),
|
|
protocol_version: self.protocol_version.unwrap(),
|
|
},
|
|
body,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A levin body
|
|
pub trait LevinBody: Sized {
|
|
/// Decodes the message from the data in the header
|
|
fn decode_message(body: &[u8], typ: MessageType, command: u32) -> Result<Self, BucketError>;
|
|
|
|
/// Encodes the message
|
|
fn encode(&self, builder: &mut BucketBuilder) -> Result<(), BucketError>;
|
|
}
|