diff --git a/binaries/cuprated/src/rpc.rs b/binaries/cuprated/src/rpc.rs index fe8e5f2..255d90d 100644 --- a/binaries/cuprated/src/rpc.rs +++ b/binaries/cuprated/src/rpc.rs @@ -3,6 +3,7 @@ //! Will contain the code to initiate the RPC and a request handler. mod bin; +mod constants; mod handler; mod json; mod other; diff --git a/binaries/cuprated/src/rpc/constants.rs b/binaries/cuprated/src/rpc/constants.rs new file mode 100644 index 0000000..1236269 --- /dev/null +++ b/binaries/cuprated/src/rpc/constants.rs @@ -0,0 +1,5 @@ +//! Constants used within RPC. + +/// The string message used in RPC response fields for when +/// `cuprated` does not support a field that `monerod` has. +pub(super) const FIELD_NOT_SUPPORTED: &str = "`cuprated` does not support this field."; diff --git a/binaries/cuprated/src/rpc/request/address_book.rs b/binaries/cuprated/src/rpc/request/address_book.rs index c3ffbd3..966a2de 100644 --- a/binaries/cuprated/src/rpc/request/address_book.rs +++ b/binaries/cuprated/src/rpc/request/address_book.rs @@ -14,6 +14,8 @@ use cuprate_p2p_core::{ AddressBook, NetworkZone, }; +use crate::rpc::constants::FIELD_NOT_SUPPORTED; + // FIXME: use `anyhow::Error` over `tower::BoxError` in address book. /// [`AddressBookRequest::PeerlistSize`] @@ -53,15 +55,20 @@ pub(crate) async fn connection_info( let vec = vec .into_iter() .map(|info| { - use cuprate_p2p_core::types::AddressType as A1; - use cuprate_rpc_types::misc::AddressType as A2; + /// Message to use when casting between enums with `u8` fails. + /// This should never happen. + const EXPECT: &str = "u8 repr between these types should be 1-1"; - let address_type = match info.address_type { - A1::Invalid => A2::Invalid, - A1::Ipv4 => A2::Ipv4, - A1::Ipv6 => A2::Ipv6, - A1::I2p => A2::I2p, - A1::Tor => A2::Tor, + let address_type = + cuprate_rpc_types::misc::AddressType::from_u8(info.address_type.to_u8()) + .expect(EXPECT); + + let state = cuprate_rpc_types::misc::ConnectionState::from_u8(info.state.to_u8()) + .expect(EXPECT); + + let (ip, port) = match info.socket_addr { + Some(socket) => (socket.ip().to_string(), socket.port().to_string()), + None => (String::new(), String::new()), }; ConnectionInfo { @@ -69,18 +76,18 @@ pub(crate) async fn connection_info( address_type, avg_download: info.avg_download, avg_upload: info.avg_upload, - connection_id: hex::encode(info.connection_id.to_ne_bytes()), + connection_id: String::from(FIELD_NOT_SUPPORTED), current_download: info.current_download, current_upload: info.current_upload, height: info.height, host: info.host, incoming: info.incoming, - ip: info.ip, + ip, live_time: info.live_time, localhost: info.localhost, local_ip: info.local_ip, - peer_id: info.peer_id, - port: info.port, + peer_id: hex::encode(info.peer_id.to_ne_bytes()), + port, pruning_seed: info.pruning_seed.compress(), recv_count: info.recv_count, recv_idle_time: info.recv_idle_time, @@ -88,7 +95,7 @@ pub(crate) async fn connection_info( rpc_port: info.rpc_port, send_count: info.send_count, send_idle_time: info.send_idle_time, - state: info.state, + state, support_flags: info.support_flags, } }) @@ -190,7 +197,7 @@ pub(crate) async fn spans( let vec = vec .into_iter() .map(|span| Span { - connection_id: hex::encode(span.connection_id.to_ne_bytes()), + connection_id: String::from(FIELD_NOT_SUPPORTED), nblocks: span.nblocks, rate: span.rate, remote_address: span.remote_address.to_string(), diff --git a/p2p/p2p-core/src/types.rs b/p2p/p2p-core/src/types.rs index 4a9f238..3553422 100644 --- a/p2p/p2p-core/src/types.rs +++ b/p2p/p2p-core/src/types.rs @@ -81,26 +81,85 @@ impl AddressType { } } +/// An enumeration of P2P connection states. +/// +/// Used [`ConnectionInfo::state`]. +/// +/// Original definition: +/// - +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum ConnectionState { + BeforeHandshake, + Synchronizing, + Standby, + Idle, + #[default] + Normal, +} + +impl ConnectionState { + /// Convert [`Self`] to a [`u8`]. + /// + /// ```rust + /// use cuprate_p2p_core::types::ConnectionState as C; + /// + /// assert_eq!(C::BeforeHandshake.to_u8(), 0); + /// assert_eq!(C::Synchronizing.to_u8(), 1); + /// assert_eq!(C::Standby.to_u8(), 2); + /// assert_eq!(C::Idle.to_u8(), 3); + /// assert_eq!(C::Normal.to_u8(), 4); + /// ``` + pub const fn to_u8(self) -> u8 { + self as u8 + } + + /// Convert a [`u8`] to a [`Self`]. + /// + /// # Errors + /// This returns [`None`] if `u > 4`. + /// + /// ```rust + /// use cuprate_p2p_core::types::ConnectionState as C; + /// + /// assert_eq!(C::from_u8(0), Some(C::BeforeHandShake)); + /// assert_eq!(C::from_u8(1), Some(C::Synchronizing)); + /// assert_eq!(C::from_u8(2), Some(C::Standby)); + /// assert_eq!(C::from_u8(3), Some(C::Idle)); + /// assert_eq!(C::from_u8(4), Some(C::Normal)); + /// assert_eq!(C::from_u8(5), None); + /// ``` + pub const fn from_u8(u: u8) -> Option { + Some(match u { + 0 => Self::BeforeHandshake, + 1 => Self::Synchronizing, + 2 => Self::Standby, + 3 => Self::Idle, + 4 => Self::Normal, + _ => return None, + }) + } +} + // TODO: reduce fields and map to RPC type. // /// Data within [`crate::services::AddressBookResponse::ConnectionInfo`]. pub struct ConnectionInfo { + // The following fields are mostly the same as `monerod`. pub address: A, pub address_type: AddressType, pub avg_download: u64, pub avg_upload: u64, - pub connection_id: u64, // TODO: boost::uuids::uuid pub current_download: u64, pub current_upload: u64, pub height: u64, + /// Either a domain or an IP without the port. pub host: String, pub incoming: bool, - pub ip: String, pub live_time: u64, pub localhost: bool, pub local_ip: bool, - pub peer_id: String, - pub port: String, + pub peer_id: u64, pub pruning_seed: PruningSeed, pub recv_count: u64, pub recv_idle_time: u64, @@ -108,8 +167,16 @@ pub struct ConnectionInfo { pub rpc_port: u16, pub send_count: u64, pub send_idle_time: u64, - pub state: String, // TODO: what type is this? + pub state: ConnectionState, pub support_flags: u32, + + // The following fields are slightly different than `monerod`. + // + /// [`None`] if Tor/i2p or unknown. + pub socket_addr: Option, + // This field does not exist for Cuprate: + // + // pub connection_id: u128, } /// Used in RPC's `sync_info`. @@ -117,7 +184,6 @@ pub struct ConnectionInfo { /// Data within [`crate::services::AddressBookResponse::Spans`]. #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Span { - pub connection_id: u64, // TODO: boost::uuids::uuid pub nblocks: u64, pub rate: u32, pub remote_address: A, diff --git a/rpc/types/src/misc/connection_state.rs b/rpc/types/src/misc/connection_state.rs new file mode 100644 index 0000000..aa8bb68 --- /dev/null +++ b/rpc/types/src/misc/connection_state.rs @@ -0,0 +1,107 @@ +//! Types of network addresses; used in P2P. + +use cuprate_epee_encoding::Marker; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + error, + macros::bytes::{Buf, BufMut}, + EpeeValue, +}; + +/// Used in [`crate::misc::ConnectionInfo::address_type`]. +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "cryptonote_basic/connection_context.h", + 49..=56 +)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged, try_from = "u8", into = "u8"))] +#[repr(u8)] +pub enum ConnectionState { + BeforeHandshake, + Synchronizing, + Standby, + Idle, + #[default] + Normal, +} + +impl ConnectionState { + /// Convert [`Self`] to a [`u8`]. + /// + /// ```rust + /// use cuprate_p2p_core::types::ConnectionState as C; + /// + /// assert_eq!(C::BeforeHandshake.to_u8(), 0); + /// assert_eq!(C::Synchronizing.to_u8(), 1); + /// assert_eq!(C::Standby.to_u8(), 2); + /// assert_eq!(C::Idle.to_u8(), 3); + /// assert_eq!(C::Normal.to_u8(), 4); + /// ``` + pub const fn to_u8(self) -> u8 { + self as u8 + } + + /// Convert a [`u8`] to a [`Self`]. + /// + /// # Errors + /// This returns [`None`] if `u > 4`. + /// + /// ```rust + /// use cuprate_p2p_core::types::ConnectionState as C; + /// + /// assert_eq!(C::from_u8(0), Some(C::BeforeHandShake)); + /// assert_eq!(C::from_u8(1), Some(C::Synchronizing)); + /// assert_eq!(C::from_u8(2), Some(C::Standby)); + /// assert_eq!(C::from_u8(3), Some(C::Idle)); + /// assert_eq!(C::from_u8(4), Some(C::Normal)); + /// assert_eq!(C::from_u8(5), None); + /// ``` + pub const fn from_u8(u: u8) -> Option { + Some(match u { + 0 => Self::BeforeHandshake, + 1 => Self::Synchronizing, + 2 => Self::Standby, + 3 => Self::Idle, + 4 => Self::Normal, + _ => return None, + }) + } +} + +impl From for u8 { + fn from(value: ConnectionState) -> Self { + value.to_u8() + } +} + +impl TryFrom for ConnectionState { + type Error = u8; + fn try_from(value: u8) -> Result { + match Self::from_u8(value) { + Some(s) => Ok(s), + None => Err(value), + } + } +} + +#[cfg(feature = "epee")] +impl EpeeValue for ConnectionState { + const MARKER: Marker = u8::MARKER; + + fn read(r: &mut B, marker: &Marker) -> error::Result { + let u = u8::read(r, marker)?; + Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 4")) + } + + fn write(self, w: &mut B) -> error::Result<()> { + let u = self.to_u8(); + u8::write(u, w)?; + Ok(()) + } +} diff --git a/rpc/types/src/misc/misc.rs b/rpc/types/src/misc/misc.rs index 49fed6f..38f1597 100644 --- a/rpc/types/src/misc/misc.rs +++ b/rpc/types/src/misc/misc.rs @@ -135,7 +135,7 @@ define_struct_and_impl_epee! { // Exists in the original definition, but isn't // used or (de)serialized for RPC purposes. // ssl: bool, - state: String, + state: crate::misc::ConnectionState, support_flags: u32, } } diff --git a/rpc/types/src/misc/mod.rs b/rpc/types/src/misc/mod.rs index 672d493..6f16403 100644 --- a/rpc/types/src/misc/mod.rs +++ b/rpc/types/src/misc/mod.rs @@ -14,6 +14,7 @@ //---------------------------------------------------------------------------------------------------- Mod mod address_type; mod binary_string; +mod connection_state; mod distribution; mod key_image_spent_status; #[expect(clippy::module_inception)] @@ -24,6 +25,7 @@ mod tx_entry; pub use address_type::AddressType; pub use binary_string::BinaryString; +pub use connection_state::ConnectionState; pub use distribution::{Distribution, DistributionCompressedBinary, DistributionUncompressed}; pub use key_image_spent_status::KeyImageSpentStatus; pub use misc::{